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