1 | /* $Id: miniwget.c,v 1.82 2020/05/29 21:14:22 nanard Exp $ */ |
2 | /* Project : miniupnp |
3 | * Website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ |
4 | * Author : Thomas Bernard |
5 | * Copyright (c) 2005-2020 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 | #include "win32_snprintf.h" |
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 * |
68 | getHTTPResponse(SOCKET s, int * size, int * status_code) |
69 | { |
70 | char buf[2048]; |
71 | int n; |
72 | int = 0; |
73 | int chunked = 0; |
74 | int content_length = -1; |
75 | unsigned int chunksize = 0; |
76 | unsigned int bytestocopy = 0; |
77 | /* buffers : */ |
78 | char * ; |
79 | unsigned int = 2048; |
80 | unsigned int = 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 - 1; sp++) |
180 | if(header_buf[sp] == ' ') |
181 | { |
182 | if(*status_code < 0) |
183 | { |
184 | if (header_buf[sp+1] >= '1' && header_buf[sp+1] <= '9') |
185 | *status_code = atoi(header_buf + sp + 1); |
186 | } |
187 | else |
188 | { |
189 | #ifdef DEBUG |
190 | reason_phrase = header_buf + sp + 1; |
191 | reason_phrase_len = i - sp - 1; |
192 | #endif |
193 | break; |
194 | } |
195 | } |
196 | #ifdef DEBUG |
197 | printf("HTTP status code = %d, Reason phrase = %.*s\n" , |
198 | *status_code, reason_phrase_len, reason_phrase); |
199 | #endif |
200 | } |
201 | else if(colon > linestart && valuestart > colon) |
202 | { |
203 | #ifdef DEBUG |
204 | printf("header='%.*s', value='%.*s'\n" , |
205 | colon-linestart, header_buf+linestart, |
206 | i-valuestart, header_buf+valuestart); |
207 | #endif |
208 | if(0==strncasecmp(header_buf+linestart, "content-length" , colon-linestart)) |
209 | { |
210 | content_length = atoi(header_buf+valuestart); |
211 | #ifdef DEBUG |
212 | printf("Content-Length: %d\n" , content_length); |
213 | #endif |
214 | } |
215 | else if(0==strncasecmp(header_buf+linestart, "transfer-encoding" , colon-linestart) |
216 | && 0==strncasecmp(header_buf+valuestart, "chunked" , 7)) |
217 | { |
218 | #ifdef DEBUG |
219 | printf("chunked transfer-encoding!\n" ); |
220 | #endif |
221 | chunked = 1; |
222 | } |
223 | } |
224 | while((i < (int)header_buf_used) && (header_buf[i]=='\r' || header_buf[i] == '\n')) |
225 | i++; |
226 | linestart = i; |
227 | colon = linestart; |
228 | valuestart = 0; |
229 | } |
230 | } |
231 | /* copy the remaining of the received data back to buf */ |
232 | n = header_buf_used - endofheaders; |
233 | memcpy(buf, header_buf + endofheaders, n); |
234 | /* if(headers) */ |
235 | } |
236 | /* if we get there, endofheaders != 0. |
237 | * In the other case, there was a continue above */ |
238 | /* content */ |
239 | if(chunked) |
240 | { |
241 | int i = 0; |
242 | while(i < n) |
243 | { |
244 | if(chunksize == 0) |
245 | { |
246 | /* reading chunk size */ |
247 | if(chunksize_buf_index == 0) { |
248 | /* skipping any leading CR LF */ |
249 | if(buf[i] == '\r') i++; |
250 | if(i<n && buf[i] == '\n') i++; |
251 | } |
252 | while(i<n && isxdigit(buf[i]) |
253 | && chunksize_buf_index < (sizeof(chunksize_buf)-1)) |
254 | { |
255 | chunksize_buf[chunksize_buf_index++] = buf[i]; |
256 | chunksize_buf[chunksize_buf_index] = '\0'; |
257 | i++; |
258 | } |
259 | while(i<n && buf[i] != '\r' && buf[i] != '\n') |
260 | i++; /* discarding chunk-extension */ |
261 | if(i<n && buf[i] == '\r') i++; |
262 | if(i<n && buf[i] == '\n') { |
263 | unsigned int j; |
264 | for(j = 0; j < chunksize_buf_index; j++) { |
265 | if(chunksize_buf[j] >= '0' |
266 | && chunksize_buf[j] <= '9') |
267 | chunksize = (chunksize << 4) + (chunksize_buf[j] - '0'); |
268 | else |
269 | chunksize = (chunksize << 4) + ((chunksize_buf[j] | 32) - 'a' + 10); |
270 | } |
271 | chunksize_buf[0] = '\0'; |
272 | chunksize_buf_index = 0; |
273 | i++; |
274 | } else { |
275 | /* not finished to get chunksize */ |
276 | continue; |
277 | } |
278 | #ifdef DEBUG |
279 | printf("chunksize = %u (%x)\n" , chunksize, chunksize); |
280 | #endif |
281 | if(chunksize == 0) |
282 | { |
283 | #ifdef DEBUG |
284 | printf("end of HTTP content - %d %d\n" , i, n); |
285 | /*printf("'%.*s'\n", n-i, buf+i);*/ |
286 | #endif |
287 | goto end_of_stream; |
288 | } |
289 | } |
290 | /* it is guaranteed that (n >= i) */ |
291 | bytestocopy = (chunksize < (unsigned int)(n - i))?chunksize:(unsigned int)(n - i); |
292 | if((content_buf_used + bytestocopy) > content_buf_len) |
293 | { |
294 | char * tmp; |
295 | if((content_length >= 0) && ((unsigned int)content_length >= (content_buf_used + bytestocopy))) { |
296 | content_buf_len = content_length; |
297 | } else { |
298 | content_buf_len = content_buf_used + bytestocopy; |
299 | } |
300 | tmp = realloc(content_buf, content_buf_len); |
301 | if(tmp == NULL) { |
302 | /* memory allocation error */ |
303 | free(content_buf); |
304 | free(header_buf); |
305 | *size = -1; |
306 | return NULL; |
307 | } |
308 | content_buf = tmp; |
309 | } |
310 | memcpy(content_buf + content_buf_used, buf + i, bytestocopy); |
311 | content_buf_used += bytestocopy; |
312 | i += bytestocopy; |
313 | chunksize -= bytestocopy; |
314 | } |
315 | } |
316 | else |
317 | { |
318 | /* not chunked */ |
319 | if(content_length > 0 |
320 | && (content_buf_used + n) > (unsigned int)content_length) { |
321 | /* skipping additional bytes */ |
322 | n = content_length - content_buf_used; |
323 | } |
324 | if(content_buf_used + n > content_buf_len) |
325 | { |
326 | char * tmp; |
327 | if(content_length >= 0 |
328 | && (unsigned int)content_length >= (content_buf_used + n)) { |
329 | content_buf_len = content_length; |
330 | } else { |
331 | content_buf_len = content_buf_used + n; |
332 | } |
333 | tmp = realloc(content_buf, content_buf_len); |
334 | if(tmp == NULL) { |
335 | /* memory allocation error */ |
336 | free(content_buf); |
337 | free(header_buf); |
338 | *size = -1; |
339 | return NULL; |
340 | } |
341 | content_buf = tmp; |
342 | } |
343 | memcpy(content_buf + content_buf_used, buf, n); |
344 | content_buf_used += n; |
345 | } |
346 | /* use the Content-Length header value if available */ |
347 | if(content_length > 0 && content_buf_used >= (unsigned int)content_length) |
348 | { |
349 | #ifdef DEBUG |
350 | printf("End of HTTP content\n" ); |
351 | #endif |
352 | break; |
353 | } |
354 | } |
355 | end_of_stream: |
356 | free(header_buf); |
357 | *size = content_buf_used; |
358 | if(content_buf_used == 0) |
359 | { |
360 | free(content_buf); |
361 | content_buf = NULL; |
362 | } |
363 | return content_buf; |
364 | } |
365 | |
366 | /* miniwget3() : |
367 | * do all the work. |
368 | * Return NULL if something failed. */ |
369 | static void * |
370 | miniwget3(const char * host, |
371 | unsigned short port, const char * path, |
372 | int * size, char * addr_str, int addr_str_len, |
373 | const char * httpversion, unsigned int scope_id, |
374 | int * status_code) |
375 | { |
376 | char buf[2048]; |
377 | SOCKET s; |
378 | int n; |
379 | int len; |
380 | int sent; |
381 | void * content; |
382 | |
383 | *size = 0; |
384 | s = connecthostport(host, port, scope_id); |
385 | if(ISINVALID(s)) |
386 | return NULL; |
387 | |
388 | /* get address for caller ! */ |
389 | if(addr_str) |
390 | { |
391 | struct sockaddr_storage saddr; |
392 | socklen_t saddrlen; |
393 | |
394 | saddrlen = sizeof(saddr); |
395 | if(getsockname(s, (struct sockaddr *)&saddr, &saddrlen) < 0) |
396 | { |
397 | perror("getsockname" ); |
398 | } |
399 | else |
400 | { |
401 | #if defined(__amigaos__) && !defined(__amigaos4__) |
402 | /* using INT WINAPI WSAAddressToStringA(LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFOA, LPSTR, LPDWORD); |
403 | * But his function make a string with the port : nn.nn.nn.nn:port */ |
404 | /* if(WSAAddressToStringA((SOCKADDR *)&saddr, sizeof(saddr), |
405 | NULL, addr_str, (DWORD *)&addr_str_len)) |
406 | { |
407 | printf("WSAAddressToStringA() failed : %d\n", WSAGetLastError()); |
408 | }*/ |
409 | /* the following code is only compatible with ip v4 addresses */ |
410 | strncpy(addr_str, inet_ntoa(((struct sockaddr_in *)&saddr)->sin_addr), addr_str_len); |
411 | #else |
412 | #if 0 |
413 | if(saddr.sa_family == AF_INET6) { |
414 | inet_ntop(AF_INET6, |
415 | &(((struct sockaddr_in6 *)&saddr)->sin6_addr), |
416 | addr_str, addr_str_len); |
417 | } else { |
418 | inet_ntop(AF_INET, |
419 | &(((struct sockaddr_in *)&saddr)->sin_addr), |
420 | addr_str, addr_str_len); |
421 | } |
422 | #endif |
423 | /* getnameinfo return ip v6 address with the scope identifier |
424 | * such as : 2a01:e35:8b2b:7330::%4281128194 */ |
425 | n = getnameinfo((const struct sockaddr *)&saddr, saddrlen, |
426 | addr_str, addr_str_len, |
427 | NULL, 0, |
428 | NI_NUMERICHOST | NI_NUMERICSERV); |
429 | if(n != 0) { |
430 | #ifdef _WIN32 |
431 | fprintf(stderr, "getnameinfo() failed : %d\n" , n); |
432 | #else |
433 | fprintf(stderr, "getnameinfo() failed : %s\n" , gai_strerror(n)); |
434 | #endif |
435 | } |
436 | #endif |
437 | } |
438 | #ifdef DEBUG |
439 | printf("address miniwget : %s\n" , addr_str); |
440 | #endif |
441 | } |
442 | |
443 | len = snprintf(buf, sizeof(buf), |
444 | "GET %s HTTP/%s\r\n" |
445 | "Host: %s:%d\r\n" |
446 | "Connection: Close\r\n" |
447 | "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" |
448 | |
449 | "\r\n" , |
450 | path, httpversion, host, port); |
451 | if ((unsigned int)len >= sizeof(buf)) |
452 | { |
453 | closesocket(s); |
454 | return NULL; |
455 | } |
456 | sent = 0; |
457 | /* sending the HTTP request */ |
458 | while(sent < len) |
459 | { |
460 | n = send(s, buf+sent, len-sent, 0); |
461 | if(n < 0) |
462 | { |
463 | perror("send" ); |
464 | closesocket(s); |
465 | return NULL; |
466 | } |
467 | else |
468 | { |
469 | sent += n; |
470 | } |
471 | } |
472 | content = getHTTPResponse(s, size, status_code); |
473 | closesocket(s); |
474 | return content; |
475 | } |
476 | |
477 | /* miniwget2() : |
478 | * Call miniwget3(); retry with HTTP/1.1 if 1.0 fails. */ |
479 | static void * |
480 | miniwget2(const char * host, |
481 | unsigned short port, const char * path, |
482 | int * size, char * addr_str, int addr_str_len, |
483 | unsigned int scope_id, int * status_code) |
484 | { |
485 | char * respbuffer; |
486 | |
487 | #if 1 |
488 | respbuffer = miniwget3(host, port, path, size, |
489 | addr_str, addr_str_len, "1.1" , |
490 | scope_id, status_code); |
491 | #else |
492 | respbuffer = miniwget3(host, port, path, size, |
493 | addr_str, addr_str_len, "1.0" , |
494 | scope_id, status_code); |
495 | if (*size == 0) |
496 | { |
497 | #ifdef DEBUG |
498 | printf("Retrying with HTTP/1.1\n" ); |
499 | #endif |
500 | free(respbuffer); |
501 | respbuffer = miniwget3(host, port, path, size, |
502 | addr_str, addr_str_len, "1.1" , |
503 | scope_id, status_code); |
504 | } |
505 | #endif |
506 | return respbuffer; |
507 | } |
508 | |
509 | |
510 | |
511 | |
512 | /* parseURL() |
513 | * arguments : |
514 | * url : source string not modified |
515 | * hostname : hostname destination string (size of MAXHOSTNAMELEN+1) |
516 | * port : port (destination) |
517 | * path : pointer to the path part of the URL |
518 | * |
519 | * Return values : |
520 | * 0 - Failure |
521 | * 1 - Success */ |
522 | int |
523 | parseURL(const char * url, |
524 | char * hostname, unsigned short * port, |
525 | char * * path, unsigned int * scope_id) |
526 | { |
527 | char * p1, *p2, *p3; |
528 | if(!url) |
529 | return 0; |
530 | p1 = strstr(url, "://" ); |
531 | if(!p1) |
532 | return 0; |
533 | p1 += 3; |
534 | if( (url[0]!='h') || (url[1]!='t') |
535 | ||(url[2]!='t') || (url[3]!='p')) |
536 | return 0; |
537 | memset(hostname, 0, MAXHOSTNAMELEN + 1); |
538 | if(*p1 == '[') |
539 | { |
540 | /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */ |
541 | char * scope; |
542 | scope = strchr(p1, '%'); |
543 | p2 = strchr(p1, ']'); |
544 | if(p2 && scope && scope < p2 && scope_id) { |
545 | /* parse scope */ |
546 | #ifdef IF_NAMESIZE |
547 | char tmp[IF_NAMESIZE]; |
548 | int l; |
549 | scope++; |
550 | /* "%25" is just '%' in URL encoding */ |
551 | if(scope[0] == '2' && scope[1] == '5') |
552 | scope += 2; /* skip "25" */ |
553 | l = p2 - scope; |
554 | if(l >= IF_NAMESIZE) |
555 | l = IF_NAMESIZE - 1; |
556 | memcpy(tmp, scope, l); |
557 | tmp[l] = '\0'; |
558 | *scope_id = if_nametoindex(tmp); |
559 | if(*scope_id == 0) { |
560 | *scope_id = (unsigned int)strtoul(tmp, NULL, 10); |
561 | } |
562 | #else |
563 | /* under windows, scope is numerical */ |
564 | char tmp[8]; |
565 | size_t l; |
566 | scope++; |
567 | /* "%25" is just '%' in URL encoding */ |
568 | if(scope[0] == '2' && scope[1] == '5') |
569 | scope += 2; /* skip "25" */ |
570 | l = p2 - scope; |
571 | if(l >= sizeof(tmp)) |
572 | l = sizeof(tmp) - 1; |
573 | memcpy(tmp, scope, l); |
574 | tmp[l] = '\0'; |
575 | *scope_id = (unsigned int)strtoul(tmp, NULL, 10); |
576 | #endif |
577 | } |
578 | p3 = strchr(p1, '/'); |
579 | if(p2 && p3) |
580 | { |
581 | p2++; |
582 | strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); |
583 | if(*p2 == ':') |
584 | { |
585 | *port = 0; |
586 | p2++; |
587 | while( (*p2 >= '0') && (*p2 <= '9')) |
588 | { |
589 | *port *= 10; |
590 | *port += (unsigned short)(*p2 - '0'); |
591 | p2++; |
592 | } |
593 | } |
594 | else |
595 | { |
596 | *port = 80; |
597 | } |
598 | *path = p3; |
599 | return 1; |
600 | } |
601 | } |
602 | p2 = strchr(p1, ':'); |
603 | p3 = strchr(p1, '/'); |
604 | if(!p3) |
605 | return 0; |
606 | if(!p2 || (p2>p3)) |
607 | { |
608 | strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1))); |
609 | *port = 80; |
610 | } |
611 | else |
612 | { |
613 | strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); |
614 | *port = 0; |
615 | p2++; |
616 | while( (*p2 >= '0') && (*p2 <= '9')) |
617 | { |
618 | *port *= 10; |
619 | *port += (unsigned short)(*p2 - '0'); |
620 | p2++; |
621 | } |
622 | } |
623 | *path = p3; |
624 | return 1; |
625 | } |
626 | |
627 | void * |
628 | miniwget(const char * url, int * size, |
629 | unsigned int scope_id, int * status_code) |
630 | { |
631 | unsigned short port; |
632 | char * path; |
633 | /* protocol://host:port/chemin */ |
634 | char hostname[MAXHOSTNAMELEN+1]; |
635 | *size = 0; |
636 | if(!parseURL(url, hostname, &port, &path, &scope_id)) |
637 | return NULL; |
638 | #ifdef DEBUG |
639 | printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n" , |
640 | hostname, port, path, scope_id); |
641 | #endif |
642 | return miniwget2(hostname, port, path, size, 0, 0, scope_id, status_code); |
643 | } |
644 | |
645 | void * |
646 | miniwget_getaddr(const char * url, int * size, |
647 | char * addr, int addrlen, unsigned int scope_id, |
648 | int * status_code) |
649 | { |
650 | unsigned short port; |
651 | char * path; |
652 | /* protocol://host:port/path */ |
653 | char hostname[MAXHOSTNAMELEN+1]; |
654 | *size = 0; |
655 | if(addr) |
656 | addr[0] = '\0'; |
657 | if(!parseURL(url, hostname, &port, &path, &scope_id)) |
658 | return NULL; |
659 | #ifdef DEBUG |
660 | printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n" , |
661 | hostname, port, path, scope_id); |
662 | #endif |
663 | return miniwget2(hostname, port, path, size, addr, addrlen, scope_id, status_code); |
664 | } |
665 | |