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