Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/miniupnpc/src/miniwget.c
9904 views
1
/* $Id: miniwget.c,v 1.88 2025/05/25 21:56:49 nanard Exp $ */
2
/* Project : miniupnp
3
* Website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
4
* Author : Thomas Bernard
5
* Copyright (c) 2005-2025 Thomas Bernard
6
* This software is subject to the conditions detailed in the
7
* LICENCE file provided in this distribution. */
8
9
#include <stdio.h>
10
#include <stdlib.h>
11
#include <string.h>
12
#include <ctype.h>
13
#ifdef _WIN32
14
#define WIN32_LEAN_AND_MEAN
15
#include <winsock2.h>
16
#include <ws2tcpip.h>
17
#include <io.h>
18
#define MAXHOSTNAMELEN 64
19
#include "win32_snprintf.h"
20
#define socklen_t int
21
#ifndef strncasecmp
22
#if defined(_MSC_VER) && (_MSC_VER >= 1400)
23
#define strncasecmp _memicmp
24
#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */
25
#define strncasecmp memicmp
26
#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */
27
#endif /* #ifndef strncasecmp */
28
#else /* #ifdef _WIN32 */
29
#include <unistd.h>
30
#include <sys/param.h>
31
#if defined(__amigaos__) && !defined(__amigaos4__)
32
#define socklen_t int
33
#else /* #if defined(__amigaos__) && !defined(__amigaos4__) */
34
#include <sys/select.h>
35
#endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */
36
#include <sys/socket.h>
37
#include <netinet/in.h>
38
#include <arpa/inet.h>
39
#include <net/if.h>
40
#include <netdb.h>
41
#define closesocket close
42
#include <strings.h>
43
#endif /* #else _WIN32 */
44
#ifdef __GNU__
45
#define MAXHOSTNAMELEN 64
46
#endif /* __GNU__ */
47
48
#ifndef MIN
49
#define MIN(x,y) (((x)<(y))?(x):(y))
50
#endif /* MIN */
51
52
53
#include "miniupnpcstrings.h"
54
#include "miniwget.h"
55
#include "connecthostport.h"
56
#include "receivedata.h"
57
58
#ifndef MAXHOSTNAMELEN
59
#define MAXHOSTNAMELEN 64
60
#endif
61
62
/*
63
* Read a HTTP response from a socket.
64
* Process Content-Length and Transfer-encoding headers.
65
* return a pointer to the content buffer, which length is saved
66
* to the length parameter.
67
*/
68
void *
69
getHTTPResponse(SOCKET s, int * size, int * status_code)
70
{
71
char buf[2048];
72
int n;
73
int endofheaders = 0;
74
int chunked = 0;
75
int content_length = -1;
76
unsigned int chunksize = 0;
77
unsigned int bytestocopy = 0;
78
/* buffers : */
79
char * header_buf;
80
unsigned int header_buf_len = 2048;
81
unsigned int header_buf_used = 0;
82
char * content_buf;
83
unsigned int content_buf_len = 2048;
84
unsigned int content_buf_used = 0;
85
char chunksize_buf[32];
86
unsigned int chunksize_buf_index;
87
#ifdef DEBUG
88
char * reason_phrase = NULL;
89
int reason_phrase_len = 0;
90
#endif
91
92
if(status_code) *status_code = -1;
93
header_buf = malloc(header_buf_len);
94
if(header_buf == NULL)
95
{
96
#ifdef DEBUG
97
fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse");
98
#endif /* DEBUG */
99
*size = -1;
100
return NULL;
101
}
102
content_buf = malloc(content_buf_len);
103
if(content_buf == NULL)
104
{
105
free(header_buf);
106
#ifdef DEBUG
107
fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse");
108
#endif /* DEBUG */
109
*size = -1;
110
return NULL;
111
}
112
chunksize_buf[0] = '\0';
113
chunksize_buf_index = 0;
114
115
while((n = receivedata(s, buf, sizeof(buf), 5000, NULL)) > 0)
116
{
117
if(endofheaders == 0)
118
{
119
int i;
120
int linestart=0;
121
int colon=0;
122
int valuestart=0;
123
if(header_buf_used + n > header_buf_len) {
124
char * tmp = realloc(header_buf, header_buf_used + n);
125
if(tmp == NULL) {
126
/* memory allocation error */
127
free(header_buf);
128
free(content_buf);
129
*size = -1;
130
return NULL;
131
}
132
header_buf = tmp;
133
header_buf_len = header_buf_used + n;
134
}
135
memcpy(header_buf + header_buf_used, buf, n);
136
header_buf_used += n;
137
/* search for CR LF CR LF (end of headers)
138
* recognize also LF LF */
139
i = 0;
140
while(i < ((int)header_buf_used-1) && (endofheaders == 0)) {
141
if(header_buf[i] == '\r') {
142
i++;
143
if(header_buf[i] == '\n') {
144
i++;
145
if(i < (int)header_buf_used && header_buf[i] == '\r') {
146
i++;
147
if(i < (int)header_buf_used && header_buf[i] == '\n') {
148
endofheaders = i+1;
149
}
150
}
151
}
152
} else if(header_buf[i] == '\n') {
153
i++;
154
if(header_buf[i] == '\n') {
155
endofheaders = i+1;
156
}
157
}
158
i++;
159
}
160
if(endofheaders == 0)
161
continue;
162
/* parse header lines */
163
for(i = 0; i < endofheaders - 1; i++) {
164
if(linestart > 0 && colon <= linestart && header_buf[i]==':')
165
{
166
colon = i;
167
while(i < (endofheaders-1)
168
&& (header_buf[i+1] == ' ' || header_buf[i+1] == '\t'))
169
i++;
170
valuestart = i + 1;
171
}
172
/* detecting end of line */
173
else if(header_buf[i]=='\r' || header_buf[i]=='\n')
174
{
175
if(linestart == 0 && status_code)
176
{
177
/* Status line
178
* HTTP-Version SP Status-Code SP Reason-Phrase CRLF */
179
int sp;
180
for(sp = 0; sp < i - 1; sp++)
181
if(header_buf[sp] == ' ')
182
{
183
if(*status_code < 0)
184
{
185
if (header_buf[sp+1] >= '1' && header_buf[sp+1] <= '9')
186
*status_code = atoi(header_buf + sp + 1);
187
}
188
else
189
{
190
#ifdef DEBUG
191
reason_phrase = header_buf + sp + 1;
192
reason_phrase_len = i - sp - 1;
193
#endif
194
break;
195
}
196
}
197
#ifdef DEBUG
198
printf("HTTP status code = %d, Reason phrase = %.*s\n",
199
*status_code, reason_phrase_len, reason_phrase);
200
#endif
201
}
202
else if(colon > linestart && valuestart > colon)
203
{
204
#ifdef DEBUG
205
printf("header='%.*s', value='%.*s'\n",
206
colon-linestart, header_buf+linestart,
207
i-valuestart, header_buf+valuestart);
208
#endif
209
if(0==strncasecmp(header_buf+linestart, "content-length", colon-linestart))
210
{
211
content_length = atoi(header_buf+valuestart);
212
#ifdef DEBUG
213
printf("Content-Length: %d\n", content_length);
214
#endif
215
}
216
else if(0==strncasecmp(header_buf+linestart, "transfer-encoding", colon-linestart)
217
&& 0==strncasecmp(header_buf+valuestart, "chunked", 7))
218
{
219
#ifdef DEBUG
220
printf("chunked transfer-encoding!\n");
221
#endif
222
chunked = 1;
223
}
224
}
225
while((i < (int)header_buf_used) && (header_buf[i]=='\r' || header_buf[i] == '\n'))
226
i++;
227
linestart = i;
228
colon = linestart;
229
valuestart = 0;
230
}
231
}
232
/* copy the remaining of the received data back to buf */
233
n = header_buf_used - endofheaders;
234
memcpy(buf, header_buf + endofheaders, n);
235
/* if(headers) */
236
}
237
/* if we get there, endofheaders != 0.
238
* In the other case, there was a continue above */
239
/* content */
240
if(chunked)
241
{
242
int i = 0;
243
while(i < n)
244
{
245
if(chunksize == 0)
246
{
247
/* reading chunk size */
248
if(chunksize_buf_index == 0) {
249
/* skipping any leading CR LF */
250
if(buf[i] == '\r') i++;
251
if(i<n && buf[i] == '\n') i++;
252
}
253
while(i<n && isxdigit(buf[i])
254
&& chunksize_buf_index < (sizeof(chunksize_buf)-1))
255
{
256
chunksize_buf[chunksize_buf_index++] = buf[i];
257
chunksize_buf[chunksize_buf_index] = '\0';
258
i++;
259
}
260
while(i<n && buf[i] != '\r' && buf[i] != '\n')
261
i++; /* discarding chunk-extension */
262
if(i<n && buf[i] == '\r') i++;
263
if(i<n && buf[i] == '\n') {
264
unsigned int j;
265
for(j = 0; j < chunksize_buf_index; j++) {
266
if(chunksize_buf[j] >= '0'
267
&& chunksize_buf[j] <= '9')
268
chunksize = (chunksize << 4) + (chunksize_buf[j] - '0');
269
else
270
chunksize = (chunksize << 4) + ((chunksize_buf[j] | 32) - 'a' + 10);
271
}
272
chunksize_buf[0] = '\0';
273
chunksize_buf_index = 0;
274
i++;
275
} else {
276
/* not finished to get chunksize */
277
continue;
278
}
279
#ifdef DEBUG
280
printf("chunksize = %u (%x)\n", chunksize, chunksize);
281
#endif
282
if(chunksize == 0)
283
{
284
#ifdef DEBUG
285
printf("end of HTTP content - %d %d\n", i, n);
286
/*printf("'%.*s'\n", n-i, buf+i);*/
287
#endif
288
goto end_of_stream;
289
}
290
}
291
/* it is guaranteed that (n >= i) */
292
bytestocopy = (chunksize < (unsigned int)(n - i))?chunksize:(unsigned int)(n - i);
293
if((content_buf_used + bytestocopy) > content_buf_len)
294
{
295
char * tmp;
296
if((content_length >= 0) && ((unsigned int)content_length >= (content_buf_used + bytestocopy))) {
297
content_buf_len = content_length;
298
} else {
299
content_buf_len = content_buf_used + bytestocopy;
300
}
301
tmp = realloc(content_buf, content_buf_len);
302
if(tmp == NULL) {
303
/* memory allocation error */
304
free(content_buf);
305
free(header_buf);
306
*size = -1;
307
return NULL;
308
}
309
content_buf = tmp;
310
}
311
memcpy(content_buf + content_buf_used, buf + i, bytestocopy);
312
content_buf_used += bytestocopy;
313
i += bytestocopy;
314
chunksize -= bytestocopy;
315
}
316
}
317
else
318
{
319
/* not chunked */
320
if(content_length > 0
321
&& (content_buf_used + n) > (unsigned int)content_length) {
322
/* skipping additional bytes */
323
n = content_length - content_buf_used;
324
}
325
if(content_buf_used + n > content_buf_len)
326
{
327
char * tmp;
328
if(content_length >= 0
329
&& (unsigned int)content_length >= (content_buf_used + n)) {
330
content_buf_len = content_length;
331
} else {
332
content_buf_len = content_buf_used + n;
333
}
334
tmp = realloc(content_buf, content_buf_len);
335
if(tmp == NULL) {
336
/* memory allocation error */
337
free(content_buf);
338
free(header_buf);
339
*size = -1;
340
return NULL;
341
}
342
content_buf = tmp;
343
}
344
memcpy(content_buf + content_buf_used, buf, n);
345
content_buf_used += n;
346
}
347
/* use the Content-Length header value if available */
348
if(content_length > 0 && content_buf_used >= (unsigned int)content_length)
349
{
350
#ifdef DEBUG
351
printf("End of HTTP content\n");
352
#endif
353
break;
354
}
355
}
356
end_of_stream:
357
free(header_buf);
358
*size = content_buf_used;
359
if(content_buf_used == 0)
360
{
361
free(content_buf);
362
content_buf = NULL;
363
}
364
return content_buf;
365
}
366
367
/* miniwget3() :
368
* do all the work.
369
* Return NULL if something failed. */
370
static void *
371
miniwget3(const char * host,
372
unsigned short port, const char * path,
373
int * size, char * addr_str, int addr_str_len,
374
const char * httpversion, unsigned int scope_id,
375
int * status_code)
376
{
377
char buf[2048];
378
SOCKET s;
379
int n;
380
int len;
381
int sent;
382
void * content;
383
384
*size = 0;
385
s = connecthostport(host, port, scope_id);
386
if(ISINVALID(s))
387
return NULL;
388
389
/* get address for caller ! */
390
if(addr_str)
391
{
392
struct sockaddr_storage saddr;
393
socklen_t saddrlen;
394
395
saddrlen = sizeof(saddr);
396
if(getsockname(s, (struct sockaddr *)&saddr, &saddrlen) < 0)
397
{
398
perror("getsockname");
399
}
400
else
401
{
402
#if defined(__amigaos__) && !defined(__amigaos4__)
403
/* using INT WINAPI WSAAddressToStringA(LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFOA, LPSTR, LPDWORD);
404
* But his function make a string with the port : nn.nn.nn.nn:port */
405
/* if(WSAAddressToStringA((SOCKADDR *)&saddr, sizeof(saddr),
406
NULL, addr_str, (DWORD *)&addr_str_len))
407
{
408
printf("WSAAddressToStringA() failed : %d\n", WSAGetLastError());
409
}*/
410
/* the following code is only compatible with ip v4 addresses */
411
strncpy(addr_str, inet_ntoa(((struct sockaddr_in *)&saddr)->sin_addr), addr_str_len);
412
#else
413
#if 0
414
if(saddr.sa_family == AF_INET6) {
415
inet_ntop(AF_INET6,
416
&(((struct sockaddr_in6 *)&saddr)->sin6_addr),
417
addr_str, addr_str_len);
418
} else {
419
inet_ntop(AF_INET,
420
&(((struct sockaddr_in *)&saddr)->sin_addr),
421
addr_str, addr_str_len);
422
}
423
#endif
424
/* getnameinfo return ip v6 address with the scope identifier
425
* such as : 2a01:e35:8b2b:7330::%4281128194 */
426
n = getnameinfo((const struct sockaddr *)&saddr, saddrlen,
427
addr_str, addr_str_len,
428
NULL, 0,
429
NI_NUMERICHOST | NI_NUMERICSERV);
430
if(n != 0) {
431
#ifdef _WIN32
432
fprintf(stderr, "getnameinfo() failed : %d\n", n);
433
#else
434
fprintf(stderr, "getnameinfo() failed : %s\n", gai_strerror(n));
435
#endif
436
}
437
#endif
438
}
439
#ifdef DEBUG
440
printf("address miniwget : %s\n", addr_str);
441
#endif
442
}
443
444
len = snprintf(buf, sizeof(buf),
445
"GET %s HTTP/%s\r\n"
446
"Host: %s:%d\r\n"
447
"Connection: close\r\n"
448
"User-Agent: " OS_STRING " " UPNP_VERSION_STRING " MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n"
449
"\r\n",
450
path, httpversion, host, port);
451
if ((unsigned int)len >= sizeof(buf))
452
{
453
closesocket(s);
454
return NULL;
455
}
456
sent = 0;
457
/* sending the HTTP request */
458
while(sent < len)
459
{
460
n = send(s, buf+sent, len-sent, 0);
461
if(n < 0)
462
{
463
perror("send");
464
closesocket(s);
465
return NULL;
466
}
467
else
468
{
469
sent += n;
470
}
471
}
472
content = getHTTPResponse(s, size, status_code);
473
closesocket(s);
474
return content;
475
}
476
477
/* parseURL()
478
* arguments :
479
* url : source string not modified
480
* hostname : hostname destination string (size of MAXHOSTNAMELEN+1)
481
* port : port (destination)
482
* path : pointer to the path part of the URL
483
*
484
* Return values :
485
* 0 - Failure
486
* 1 - Success */
487
int
488
parseURL(const char * url,
489
char * hostname, unsigned short * port,
490
char * * path, unsigned int * scope_id)
491
{
492
char * p1, *p2, *p3;
493
if(!url)
494
return 0;
495
p1 = strstr(url, "://");
496
if(!p1)
497
return 0;
498
p1 += 3;
499
if( (url[0]!='h') || (url[1]!='t')
500
||(url[2]!='t') || (url[3]!='p'))
501
return 0;
502
memset(hostname, 0, MAXHOSTNAMELEN + 1);
503
if(*p1 == '[')
504
{
505
/* IP v6 : http://[2a00:1450:8002::6a]/path/abc */
506
char * scope;
507
scope = strchr(p1, '%');
508
p2 = strchr(p1, ']');
509
if(p2 && scope && scope < p2 && scope_id) {
510
/* parse scope */
511
#ifdef IF_NAMESIZE
512
char tmp[IF_NAMESIZE];
513
int l;
514
scope++;
515
/* "%25" is just '%' in URL encoding */
516
if(scope[0] == '2' && scope[1] == '5')
517
scope += 2; /* skip "25" */
518
l = p2 - scope;
519
if(l >= IF_NAMESIZE)
520
l = IF_NAMESIZE - 1;
521
memcpy(tmp, scope, l);
522
tmp[l] = '\0';
523
*scope_id = if_nametoindex(tmp);
524
if(*scope_id == 0) {
525
*scope_id = (unsigned int)strtoul(tmp, NULL, 10);
526
}
527
#else
528
/* under windows, scope is numerical */
529
char tmp[8];
530
size_t l;
531
scope++;
532
/* "%25" is just '%' in URL encoding */
533
if(scope[0] == '2' && scope[1] == '5')
534
scope += 2; /* skip "25" */
535
l = p2 - scope;
536
if(l >= sizeof(tmp))
537
l = sizeof(tmp) - 1;
538
memcpy(tmp, scope, l);
539
tmp[l] = '\0';
540
*scope_id = (unsigned int)strtoul(tmp, NULL, 10);
541
#endif
542
}
543
p3 = strchr(p1, '/');
544
if(p2 && p3)
545
{
546
p2++;
547
strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
548
if(*p2 == ':')
549
{
550
*port = 0;
551
p2++;
552
while( (*p2 >= '0') && (*p2 <= '9'))
553
{
554
*port *= 10;
555
*port += (unsigned short)(*p2 - '0');
556
p2++;
557
}
558
}
559
else
560
{
561
*port = 80;
562
}
563
*path = p3;
564
return 1;
565
}
566
}
567
p2 = strchr(p1, ':');
568
p3 = strchr(p1, '/');
569
if(!p3)
570
return 0;
571
if(!p2 || (p2>p3))
572
{
573
strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1)));
574
*port = 80;
575
}
576
else
577
{
578
strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
579
*port = 0;
580
p2++;
581
while( (*p2 >= '0') && (*p2 <= '9'))
582
{
583
*port *= 10;
584
*port += (unsigned short)(*p2 - '0');
585
p2++;
586
}
587
}
588
*path = p3;
589
return 1;
590
}
591
592
void *
593
miniwget(const char * url, int * size,
594
unsigned int scope_id, int * status_code)
595
{
596
unsigned short port;
597
char * path;
598
/* protocol://host:port/chemin */
599
char hostname[MAXHOSTNAMELEN+1];
600
*size = 0;
601
if(!parseURL(url, hostname, &port, &path, &scope_id))
602
return NULL;
603
#ifdef DEBUG
604
printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n",
605
hostname, port, path, scope_id);
606
#endif
607
return miniwget3(hostname, port, path, size, 0, 0, "1.1", scope_id, status_code);
608
}
609
610
void *
611
miniwget_getaddr(const char * url, int * size,
612
char * addr, int addrlen, unsigned int scope_id,
613
int * status_code)
614
{
615
unsigned short port;
616
char * path;
617
/* protocol://host:port/path */
618
char hostname[MAXHOSTNAMELEN+1];
619
*size = 0;
620
if(addr)
621
addr[0] = '\0';
622
if(!parseURL(url, hostname, &port, &path, &scope_id))
623
return NULL;
624
#ifdef DEBUG
625
printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n",
626
hostname, port, path, scope_id);
627
#endif
628
return miniwget3(hostname, port, path, size, addr, addrlen, "1.1", scope_id, status_code);
629
}
630
631