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