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