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