1 /* $Id: miniwget.c,v 1.78 2018/03/13 23:22:18 nanard Exp $ */
2 /* Project : miniupnp
3 * Website : http://miniupnp.free.fr/
4 * Author : Thomas Bernard
5 * Copyright (c) 2005-2018 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 #define snprintf _snprintf
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 */
67 void *
getHTTPResponse(SOCKET s,int * size,int * status_code)68 getHTTPResponse(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; sp++)
180 if(header_buf[sp] == ' ')
181 {
182 if(*status_code < 0)
183 *status_code = atoi(header_buf + sp + 1);
184 else
185 {
186 #ifdef DEBUG
187 reason_phrase = header_buf + sp + 1;
188 reason_phrase_len = i - sp - 1;
189 #endif
190 break;
191 }
192 }
193 #ifdef DEBUG
194 printf("HTTP status code = %d, Reason phrase = %.*s\n",
195 *status_code, reason_phrase_len, reason_phrase);
196 #endif
197 }
198 else if(colon > linestart && valuestart > colon)
199 {
200 #ifdef DEBUG
201 printf("header='%.*s', value='%.*s'\n",
202 colon-linestart, header_buf+linestart,
203 i-valuestart, header_buf+valuestart);
204 #endif
205 if(0==strncasecmp(header_buf+linestart, "content-length", colon-linestart))
206 {
207 content_length = atoi(header_buf+valuestart);
208 #ifdef DEBUG
209 printf("Content-Length: %d\n", content_length);
210 #endif
211 }
212 else if(0==strncasecmp(header_buf+linestart, "transfer-encoding", colon-linestart)
213 && 0==strncasecmp(header_buf+valuestart, "chunked", 7))
214 {
215 #ifdef DEBUG
216 printf("chunked transfer-encoding!\n");
217 #endif
218 chunked = 1;
219 }
220 }
221 while((i < (int)header_buf_used) && (header_buf[i]=='\r' || header_buf[i] == '\n'))
222 i++;
223 linestart = i;
224 colon = linestart;
225 valuestart = 0;
226 }
227 }
228 /* copy the remaining of the received data back to buf */
229 n = header_buf_used - endofheaders;
230 memcpy(buf, header_buf + endofheaders, n);
231 /* if(headers) */
232 }
233 /* if we get there, endofheaders != 0.
234 * In the other case, there was a continue above */
235 /* content */
236 if(chunked)
237 {
238 int i = 0;
239 while(i < n)
240 {
241 if(chunksize == 0)
242 {
243 /* reading chunk size */
244 if(chunksize_buf_index == 0) {
245 /* skipping any leading CR LF */
246 if(buf[i] == '\r') i++;
247 if(i<n && buf[i] == '\n') i++;
248 }
249 while(i<n && isxdigit(buf[i])
250 && chunksize_buf_index < (sizeof(chunksize_buf)-1))
251 {
252 chunksize_buf[chunksize_buf_index++] = buf[i];
253 chunksize_buf[chunksize_buf_index] = '\0';
254 i++;
255 }
256 while(i<n && buf[i] != '\r' && buf[i] != '\n')
257 i++; /* discarding chunk-extension */
258 if(i<n && buf[i] == '\r') i++;
259 if(i<n && buf[i] == '\n') {
260 unsigned int j;
261 for(j = 0; j < chunksize_buf_index; j++) {
262 if(chunksize_buf[j] >= '0'
263 && chunksize_buf[j] <= '9')
264 chunksize = (chunksize << 4) + (chunksize_buf[j] - '0');
265 else
266 chunksize = (chunksize << 4) + ((chunksize_buf[j] | 32) - 'a' + 10);
267 }
268 chunksize_buf[0] = '\0';
269 chunksize_buf_index = 0;
270 i++;
271 } else {
272 /* not finished to get chunksize */
273 continue;
274 }
275 #ifdef DEBUG
276 printf("chunksize = %u (%x)\n", chunksize, chunksize);
277 #endif
278 if(chunksize == 0)
279 {
280 #ifdef DEBUG
281 printf("end of HTTP content - %d %d\n", i, n);
282 /*printf("'%.*s'\n", n-i, buf+i);*/
283 #endif
284 goto end_of_stream;
285 }
286 }
287 /* it is guaranteed that (n >= i) */
288 bytestocopy = (chunksize < (unsigned int)(n - i))?chunksize:(unsigned int)(n - i);
289 if((content_buf_used + bytestocopy) > content_buf_len)
290 {
291 char * tmp;
292 if((content_length >= 0) && ((unsigned int)content_length >= (content_buf_used + bytestocopy))) {
293 content_buf_len = content_length;
294 } else {
295 content_buf_len = content_buf_used + bytestocopy;
296 }
297 tmp = realloc(content_buf, content_buf_len);
298 if(tmp == NULL) {
299 /* memory allocation error */
300 free(content_buf);
301 free(header_buf);
302 *size = -1;
303 return NULL;
304 }
305 content_buf = tmp;
306 }
307 memcpy(content_buf + content_buf_used, buf + i, bytestocopy);
308 content_buf_used += bytestocopy;
309 i += bytestocopy;
310 chunksize -= bytestocopy;
311 }
312 }
313 else
314 {
315 /* not chunked */
316 if(content_length > 0
317 && (content_buf_used + n) > (unsigned int)content_length) {
318 /* skipping additional bytes */
319 n = content_length - content_buf_used;
320 }
321 if(content_buf_used + n > content_buf_len)
322 {
323 char * tmp;
324 if(content_length >= 0
325 && (unsigned int)content_length >= (content_buf_used + n)) {
326 content_buf_len = content_length;
327 } else {
328 content_buf_len = content_buf_used + n;
329 }
330 tmp = realloc(content_buf, content_buf_len);
331 if(tmp == NULL) {
332 /* memory allocation error */
333 free(content_buf);
334 free(header_buf);
335 *size = -1;
336 return NULL;
337 }
338 content_buf = tmp;
339 }
340 memcpy(content_buf + content_buf_used, buf, n);
341 content_buf_used += n;
342 }
343 /* use the Content-Length header value if available */
344 if(content_length > 0 && content_buf_used >= (unsigned int)content_length)
345 {
346 #ifdef DEBUG
347 printf("End of HTTP content\n");
348 #endif
349 break;
350 }
351 }
352 end_of_stream:
353 free(header_buf);
354 *size = content_buf_used;
355 if(content_buf_used == 0)
356 {
357 free(content_buf);
358 content_buf = NULL;
359 }
360 return content_buf;
361 }
362
363 /* miniwget3() :
364 * do all the work.
365 * Return NULL if something failed. */
366 static void *
miniwget3(const char * host,unsigned short port,const char * path,int * size,char * addr_str,int addr_str_len,const char * httpversion,unsigned int scope_id,int * status_code)367 miniwget3(const char * host,
368 unsigned short port, const char * path,
369 int * size, char * addr_str, int addr_str_len,
370 const char * httpversion, unsigned int scope_id,
371 int * status_code)
372 {
373 char buf[2048];
374 SOCKET s;
375 int n;
376 int len;
377 int sent;
378 void * content;
379
380 *size = 0;
381 s = connecthostport(host, port, scope_id);
382 if(ISINVALID(s))
383 return NULL;
384
385 /* get address for caller ! */
386 if(addr_str)
387 {
388 struct sockaddr_storage saddr;
389 socklen_t saddrlen;
390
391 saddrlen = sizeof(saddr);
392 if(getsockname(s, (struct sockaddr *)&saddr, &saddrlen) < 0)
393 {
394 perror("getsockname");
395 }
396 else
397 {
398 #if defined(__amigaos__) && !defined(__amigaos4__)
399 /* using INT WINAPI WSAAddressToStringA(LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFOA, LPSTR, LPDWORD);
400 * But his function make a string with the port : nn.nn.nn.nn:port */
401 /* if(WSAAddressToStringA((SOCKADDR *)&saddr, sizeof(saddr),
402 NULL, addr_str, (DWORD *)&addr_str_len))
403 {
404 printf("WSAAddressToStringA() failed : %d\n", WSAGetLastError());
405 }*/
406 /* the following code is only compatible with ip v4 addresses */
407 strncpy(addr_str, inet_ntoa(((struct sockaddr_in *)&saddr)->sin_addr), addr_str_len);
408 #else
409 #if 0
410 if(saddr.sa_family == AF_INET6) {
411 inet_ntop(AF_INET6,
412 &(((struct sockaddr_in6 *)&saddr)->sin6_addr),
413 addr_str, addr_str_len);
414 } else {
415 inet_ntop(AF_INET,
416 &(((struct sockaddr_in *)&saddr)->sin_addr),
417 addr_str, addr_str_len);
418 }
419 #endif
420 /* getnameinfo return ip v6 address with the scope identifier
421 * such as : 2a01:e35:8b2b:7330::%4281128194 */
422 n = getnameinfo((const struct sockaddr *)&saddr, saddrlen,
423 addr_str, addr_str_len,
424 NULL, 0,
425 NI_NUMERICHOST | NI_NUMERICSERV);
426 if(n != 0) {
427 #ifdef _WIN32
428 fprintf(stderr, "getnameinfo() failed : %d\n", n);
429 #else
430 fprintf(stderr, "getnameinfo() failed : %s\n", gai_strerror(n));
431 #endif
432 }
433 #endif
434 }
435 #ifdef DEBUG
436 printf("address miniwget : %s\n", addr_str);
437 #endif
438 }
439
440 len = snprintf(buf, sizeof(buf),
441 "GET %s HTTP/%s\r\n"
442 "Host: %s:%d\r\n"
443 "Connection: Close\r\n"
444 "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n"
445
446 "\r\n",
447 path, httpversion, host, port);
448 if ((unsigned int)len >= sizeof(buf))
449 {
450 closesocket(s);
451 return NULL;
452 }
453 sent = 0;
454 /* sending the HTTP request */
455 while(sent < len)
456 {
457 n = send(s, buf+sent, len-sent, 0);
458 if(n < 0)
459 {
460 perror("send");
461 closesocket(s);
462 return NULL;
463 }
464 else
465 {
466 sent += n;
467 }
468 }
469 content = getHTTPResponse(s, size, status_code);
470 closesocket(s);
471 return content;
472 }
473
474 /* miniwget2() :
475 * Call miniwget3(); retry with HTTP/1.1 if 1.0 fails. */
476 static void *
miniwget2(const char * host,unsigned short port,const char * path,int * size,char * addr_str,int addr_str_len,unsigned int scope_id,int * status_code)477 miniwget2(const char * host,
478 unsigned short port, const char * path,
479 int * size, char * addr_str, int addr_str_len,
480 unsigned int scope_id, int * status_code)
481 {
482 char * respbuffer;
483
484 #if 1
485 respbuffer = miniwget3(host, port, path, size,
486 addr_str, addr_str_len, "1.1",
487 scope_id, status_code);
488 #else
489 respbuffer = miniwget3(host, port, path, size,
490 addr_str, addr_str_len, "1.0",
491 scope_id, status_code);
492 if (*size == 0)
493 {
494 #ifdef DEBUG
495 printf("Retrying with HTTP/1.1\n");
496 #endif
497 free(respbuffer);
498 respbuffer = miniwget3(host, port, path, size,
499 addr_str, addr_str_len, "1.1",
500 scope_id, status_code);
501 }
502 #endif
503 return respbuffer;
504 }
505
506
507
508
509 /* parseURL()
510 * arguments :
511 * url : source string not modified
512 * hostname : hostname destination string (size of MAXHOSTNAMELEN+1)
513 * port : port (destination)
514 * path : pointer to the path part of the URL
515 *
516 * Return values :
517 * 0 - Failure
518 * 1 - Success */
519 int
parseURL(const char * url,char * hostname,unsigned short * port,char ** path,unsigned int * scope_id)520 parseURL(const char * url,
521 char * hostname, unsigned short * port,
522 char * * path, unsigned int * scope_id)
523 {
524 char * p1, *p2, *p3;
525 if(!url)
526 return 0;
527 p1 = strstr(url, "://");
528 if(!p1)
529 return 0;
530 p1 += 3;
531 if( (url[0]!='h') || (url[1]!='t')
532 ||(url[2]!='t') || (url[3]!='p'))
533 return 0;
534 memset(hostname, 0, MAXHOSTNAMELEN + 1);
535 if(*p1 == '[')
536 {
537 /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */
538 char * scope;
539 scope = strchr(p1, '%');
540 p2 = strchr(p1, ']');
541 if(p2 && scope && scope < p2 && scope_id) {
542 /* parse scope */
543 #ifdef IF_NAMESIZE
544 char tmp[IF_NAMESIZE];
545 int l;
546 scope++;
547 /* "%25" is just '%' in URL encoding */
548 if(scope[0] == '2' && scope[1] == '5')
549 scope += 2; /* skip "25" */
550 l = p2 - scope;
551 if(l >= IF_NAMESIZE)
552 l = IF_NAMESIZE - 1;
553 memcpy(tmp, scope, l);
554 tmp[l] = '\0';
555 *scope_id = if_nametoindex(tmp);
556 if(*scope_id == 0) {
557 *scope_id = (unsigned int)strtoul(tmp, NULL, 10);
558 }
559 #else
560 /* under windows, scope is numerical */
561 char tmp[8];
562 size_t l;
563 scope++;
564 /* "%25" is just '%' in URL encoding */
565 if(scope[0] == '2' && scope[1] == '5')
566 scope += 2; /* skip "25" */
567 l = p2 - scope;
568 if(l >= sizeof(tmp))
569 l = sizeof(tmp) - 1;
570 memcpy(tmp, scope, l);
571 tmp[l] = '\0';
572 *scope_id = (unsigned int)strtoul(tmp, NULL, 10);
573 #endif
574 }
575 p3 = strchr(p1, '/');
576 if(p2 && p3)
577 {
578 p2++;
579 strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
580 if(*p2 == ':')
581 {
582 *port = 0;
583 p2++;
584 while( (*p2 >= '0') && (*p2 <= '9'))
585 {
586 *port *= 10;
587 *port += (unsigned short)(*p2 - '0');
588 p2++;
589 }
590 }
591 else
592 {
593 *port = 80;
594 }
595 *path = p3;
596 return 1;
597 }
598 }
599 p2 = strchr(p1, ':');
600 p3 = strchr(p1, '/');
601 if(!p3)
602 return 0;
603 if(!p2 || (p2>p3))
604 {
605 strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1)));
606 *port = 80;
607 }
608 else
609 {
610 strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
611 *port = 0;
612 p2++;
613 while( (*p2 >= '0') && (*p2 <= '9'))
614 {
615 *port *= 10;
616 *port += (unsigned short)(*p2 - '0');
617 p2++;
618 }
619 }
620 *path = p3;
621 return 1;
622 }
623
624 void *
miniwget(const char * url,int * size,unsigned int scope_id,int * status_code)625 miniwget(const char * url, int * size,
626 unsigned int scope_id, int * status_code)
627 {
628 unsigned short port;
629 char * path;
630 /* protocol://host:port/chemin */
631 char hostname[MAXHOSTNAMELEN+1];
632 *size = 0;
633 if(!parseURL(url, hostname, &port, &path, &scope_id))
634 return NULL;
635 #ifdef DEBUG
636 printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n",
637 hostname, port, path, scope_id);
638 #endif
639 return miniwget2(hostname, port, path, size, 0, 0, scope_id, status_code);
640 }
641
642 void *
miniwget_getaddr(const char * url,int * size,char * addr,int addrlen,unsigned int scope_id,int * status_code)643 miniwget_getaddr(const char * url, int * size,
644 char * addr, int addrlen, unsigned int scope_id,
645 int * status_code)
646 {
647 unsigned short port;
648 char * path;
649 /* protocol://host:port/path */
650 char hostname[MAXHOSTNAMELEN+1];
651 *size = 0;
652 if(addr)
653 addr[0] = '\0';
654 if(!parseURL(url, hostname, &port, &path, &scope_id))
655 return NULL;
656 #ifdef DEBUG
657 printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n",
658 hostname, port, path, scope_id);
659 #endif
660 return miniwget2(hostname, port, path, size, addr, addrlen, scope_id, status_code);
661 }
662