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