4 * Copyright 2003-2004 Oliver Feiler <kiza@kcore.de>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 /* OS X needs this, otherwise socklen_t is not defined. */
25 # define _BSD_SOCKLEN_T_
28 /* BeOS does not define socklen_t. Using uint as suggested by port creator. */
30 # define socklen_t unsigned int
40 #include <sys/types.h>
41 #include <sys/socket.h>
42 #include <netinet/in.h>
50 #include "conversions.h"
51 #include "net-support.h"
52 #include "io-internal.h"
53 #include "zlib_interface.h"
55 static int const MAX_HTTP_REDIRECTS = 10; /* Maximum number of redirects we will follow. */
56 static int const NET_TIMEOUT = 20; /* Global network timeout in sec */
57 static int const NET_READ = 1;
58 static int const NET_WRITE = 2;
60 extern char *proxyname; /* Hostname of proxyserver. */
61 extern unsigned short proxyport; /* Port on proxyserver to use. */
63 /* Masquerade as Firefox on Linux to increase the share of both in web server statistics. */
64 /* char *useragent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0"; */
65 /* On second thought, let's be honest about this. */
66 char *useragent = "Citadel RSS Service/0.1 (multiple subscribers) (+http://www.citadel.org/)";
68 /* Waits NET_TIMEOUT seconds for the socket to return data.
73 * -1 Error occured (netio_error is set)
75 int NetPoll (struct feed * cur_ptr, int * my_socket, int rw) {
79 int retval; /* FD_ISSET + assert == Heisenbug? */
81 /* Set global network timeout */
82 tv.tv_sec = NET_TIMEOUT;
89 FD_SET(*my_socket, &rfdsr);
90 if (select (*my_socket+1, &rfdsr, NULL, NULL, &tv) == 0) {
92 cur_ptr->netio_error = NET_ERR_TIMEOUT;
95 retval = FD_ISSET (*my_socket, &rfdsr);
99 cur_ptr->netio_error = NET_ERR_UNKNOWN;
102 } else if (rw == NET_WRITE) {
103 FD_SET(*my_socket, &rfdsw);
104 if (select (*my_socket+1, NULL, &rfdsw, NULL, &tv) == 0) {
106 cur_ptr->netio_error = NET_ERR_TIMEOUT;
109 retval = FD_ISSET (*my_socket, &rfdsw);
113 cur_ptr->netio_error = NET_ERR_UNKNOWN;
117 cur_ptr->netio_error = NET_ERR_UNKNOWN;
125 /* Connect network sockets.
130 * -1 Error occured (netio_error is set)
132 int NetConnect (int * my_socket, char * host, struct feed * cur_ptr, int httpproto, int suppressoutput) {
134 struct sockaddr_in address;
135 struct hostent *remotehost;
140 realhost = strdup(host);
141 if (sscanf (host, "%[^:]:%hd", realhost, &port) != 2) {
145 /* Create a inet stream TCP socket. */
146 *my_socket = socket (AF_INET, SOCK_STREAM, 0);
147 if (*my_socket == -1) {
148 cur_ptr->netio_error = NET_ERR_SOCK_ERR;
152 /* If proxyport is 0 we didn't execute the if http_proxy statement in main
153 so there is no proxy. On any other value of proxyport do proxyrequests instead. */
154 if (proxyport == 0) {
155 /* Lookup remote IP. */
156 remotehost = gethostbyname (realhost);
157 if (remotehost == NULL) {
160 cur_ptr->netio_error = NET_ERR_HOST_NOT_FOUND;
164 /* Set the remote address. */
165 address.sin_family = AF_INET;
166 address.sin_port = htons(port);
167 memcpy (&address.sin_addr.s_addr, remotehost->h_addr_list[0], remotehost->h_length);
169 /* Connect socket. */
170 cur_ptr->connectresult = connect (*my_socket, (struct sockaddr *) &address, sizeof(address));
172 /* Check if we're already connected.
173 BSDs will return 0 on connect even in nonblock if connect was fast enough. */
174 if (cur_ptr->connectresult != 0) {
175 /* If errno is not EINPROGRESS, the connect went wrong. */
176 if (errno != EINPROGRESS) {
179 cur_ptr->netio_error = NET_ERR_CONN_REFUSED;
183 if ((NetPoll (cur_ptr, my_socket, NET_WRITE)) == -1) {
189 /* We get errno of connect back via getsockopt SO_ERROR (into connectresult). */
190 len = sizeof(cur_ptr->connectresult);
191 getsockopt(*my_socket, SOL_SOCKET, SO_ERROR, &cur_ptr->connectresult, &len);
193 if (cur_ptr->connectresult != 0) {
196 cur_ptr->netio_error = NET_ERR_CONN_FAILED; /* ->strerror(cur_ptr->connectresult) */
201 /* Lookup proxyserver IP. */
202 remotehost = gethostbyname (proxyname);
203 if (remotehost == NULL) {
206 cur_ptr->netio_error = NET_ERR_HOST_NOT_FOUND;
210 /* Set the remote address. */
211 address.sin_family = AF_INET;
212 address.sin_port = htons(proxyport);
213 memcpy (&address.sin_addr.s_addr, remotehost->h_addr_list[0], remotehost->h_length);
215 /* Connect socket. */
216 cur_ptr->connectresult = connect (*my_socket, (struct sockaddr *) &address, sizeof(address));
218 /* Check if we're already connected.
219 BSDs will return 0 on connect even in nonblock if connect was fast enough. */
220 if (cur_ptr->connectresult != 0) {
221 if (errno != EINPROGRESS) {
224 cur_ptr->netio_error = NET_ERR_CONN_REFUSED;
228 if ((NetPoll (cur_ptr, my_socket, NET_WRITE)) == -1) {
234 len = sizeof(cur_ptr->connectresult);
235 getsockopt(*my_socket, SOL_SOCKET, SO_ERROR, &cur_ptr->connectresult, &len);
237 if (cur_ptr->connectresult != 0) {
240 cur_ptr->netio_error = NET_ERR_CONN_FAILED; /* ->strerror(cur_ptr->connectresult) */
252 * Main network function.
253 * (Now with a useful function description *g*)
255 * This function returns the HTTP request's body (deflating gzip encoded data
257 * Updates passed feed struct with values gathered from webserver.
258 * Handles all redirection and HTTP status decoding.
259 * Returns NULL pointer if no data was received and sets netio_error.
261 char * NetIO (int * my_socket, char * host, char * url, struct feed * cur_ptr, char * authdata, int httpproto, int suppressoutput) {
262 char netbuf[4096]; /* Network read buffer. */
263 char *body; /* XML body. */
265 FILE *stream; /* Stream socket. */
266 int chunked = 0; /* Content-Encoding: chunked received? */
267 int redirectcount; /* Number of HTTP redirects followed. */
268 char httpstatus[4]; /* HTTP status sent by server. */
269 char servreply[128]; /* First line of server reply */
271 char *savestart; /* Save start position of pointers. */
272 char *tmphost; /* Pointers needed to strsep operation. */
273 char *newhost; /* New hostname if we need to redirect. */
274 char *newurl; /* New document name ". */
276 char *tmpstring; /* Temp pointers. */
277 char *freeme, *freeme2;
278 char *redirecttarget;
282 int inflate = 0; /* Whether feed data needs decompressed with zlib. */
285 int quirksmode = 0; /* IIS operation mode. */
286 int authfailed = 0; /* Avoid repeating failed auth requests endlessly. */
289 if (!suppressoutput) {
290 if (cur_ptr->title == NULL)
291 fprintf(stderr, "Downloading http://%s%s\n", host, url);
293 fprintf(stderr, "Downloading %s\n", cur_ptr->title);
299 /* Goto label to redirect reconnect. */
302 /* Reconstruct digest authinfo for every request so we don't reuse
303 the same nonce value for more than one request.
304 This happens one superflous time on 303 redirects. */
305 if ((cur_ptr->authinfo != NULL) && (cur_ptr->servauth != NULL)) {
306 if (strstr (cur_ptr->authinfo, " Digest ") != NULL) {
307 NetSupportAuth(cur_ptr, authdata, url, cur_ptr->servauth);
312 stream = fdopen (*my_socket, "r+");
313 if (stream == NULL) {
314 /* This is a serious non-continueable OS error as it will probably not
317 BeOS will stupidly return SUCCESS here making this code silently fail on BeOS. */
318 cur_ptr->netio_error = NET_ERR_SOCK_ERR;
322 /* Again is proxyport == 0, non proxy mode, otherwise make proxy requests. */
323 if (proxyport == 0) {
324 /* Request URL from HTTP server. */
325 if (cur_ptr->lastmodified != NULL) {
327 "GET %s HTTP/1.0\r\nAccept-Encoding: gzip\r\nUser-Agent: %s\r\nConnection: close\r\nHost: %s\r\nIf-Modified-Since: %s\r\n%s%s\r\n",
331 cur_ptr->lastmodified,
332 (cur_ptr->authinfo ? cur_ptr->authinfo : ""),
333 (cur_ptr->cookies ? cur_ptr->cookies : ""));
336 "GET %s HTTP/1.0\r\nAccept-Encoding: gzip\r\nUser-Agent: %s\r\nConnection: close\r\nHost: %s\r\n%s%s\r\n",
340 (cur_ptr->authinfo ? cur_ptr->authinfo : ""),
341 (cur_ptr->cookies ? cur_ptr->cookies : ""));
343 fflush(stream); /* We love Solaris, don't we? */
345 /* Request URL from HTTP server. */
346 if (cur_ptr->lastmodified != NULL) {
348 "GET http://%s%s HTTP/1.0\r\nAccept-Encoding: gzip\r\nUser-Agent: %s\r\nConnection: close\r\nHost: %s\r\nIf-Modified-Since: %s\r\n%s%s\r\n",
353 cur_ptr->lastmodified,
354 (cur_ptr->authinfo ? cur_ptr->authinfo : ""),
355 (cur_ptr->cookies ? cur_ptr->cookies : ""));
358 "GET http://%s%s HTTP/1.0\r\nAccept-Encoding: gzip\r\nUser-Agent: %s\r\nConnection: close\r\nHost: %s\r\n%s%s\r\n",
363 (cur_ptr->authinfo ? cur_ptr->authinfo : ""),
364 (cur_ptr->cookies ? cur_ptr->cookies : ""));
366 fflush(stream); /* We love Solaris, don't we? */
369 if ((NetPoll (cur_ptr, my_socket, NET_READ)) == -1) {
374 if ((fgets (servreply, sizeof(servreply), stream)) == NULL) {
378 if (checkValidHTTPHeader(servreply, sizeof(servreply)) != 0) {
379 cur_ptr->netio_error = NET_ERR_HTTP_PROTO_ERR;
384 tmpstatus = strdup(servreply);
385 savestart = tmpstatus;
387 memset (httpstatus, 0, 4); /* Nullify string so valgrind shuts up. */
388 /* Set pointer to char after first space.
391 Copy three bytes into httpstatus. */
392 strsep (&tmpstatus, " ");
393 if (tmpstatus == NULL) {
394 cur_ptr->netio_error = NET_ERR_HTTP_PROTO_ERR;
396 free (savestart); /* Probably more leaks when doing auth and abort here. */
399 strncpy (httpstatus, tmpstatus, 3);
402 cur_ptr->lasthttpstatus = atoi (httpstatus);
404 /* If the redirectloop was run newhost and newurl were allocated.
405 We need to free them here. */
406 if ((redirectcount > 0) && (authdata == NULL)) {
411 tmphttpstatus = cur_ptr->lasthttpstatus;
413 /* Check HTTP server response and handle redirects. */
415 switch (tmphttpstatus) {
417 /* Received good status from server, clear problem field. */
418 cur_ptr->netio_error = NET_ERR_OK;
419 cur_ptr->problem = 0;
421 case 300: /* Multiple choice and everything 300 not handled is fatal. */
422 cur_ptr->netio_error = NET_ERR_HTTP_NON_200;
426 /* Permanent redirect. Change feed->feedurl to new location.
427 Done some way down when we have extracted the new url. */
428 case 302: /* Found */
429 case 303: /* See Other */
430 case 307: /* Temp redirect. This is HTTP/1.1 */
433 /* Give up if we reach MAX_HTTP_REDIRECTS to avoid loops. */
434 if (redirectcount > MAX_HTTP_REDIRECTS) {
435 cur_ptr->netio_error = NET_ERR_REDIRECT_COUNT_ERR;
440 while (!feof(stream)) {
441 if ((fgets (netbuf, sizeof(netbuf), stream)) == NULL) {
442 /* Something bad happened. Server sent stupid stuff. */
443 cur_ptr->netio_error = NET_ERR_HTTP_PROTO_ERR;
448 if (checkValidHTTPHeader(netbuf, sizeof(netbuf)) != 0) {
449 cur_ptr->netio_error = NET_ERR_HTTP_PROTO_ERR;
454 /* Split netbuf into hostname and trailing url.
455 Place hostname in *newhost and tail into *newurl.
456 Close old connection and reconnect to server.
458 Do not touch any of the following code! :P */
459 if (strncasecmp (netbuf, "Location", 8) == 0) {
460 redirecttarget = strdup (netbuf);
461 freeme = redirecttarget;
463 /* Remove trailing \r\n from line. */
464 redirecttarget[strlen(redirecttarget)-2] = 0;
466 /* In theory pointer should now be after the space char
467 after the word "Location:" */
468 strsep (&redirecttarget, " ");
470 if (redirecttarget == NULL) {
471 cur_ptr->problem = 1;
472 cur_ptr->netio_error = NET_ERR_REDIRECT_ERR;
478 /* Location must start with "http", otherwise switch on quirksmode. */
479 if (strncmp(redirecttarget, "http", 4) != 0)
482 /* If the Location header is invalid we need to construct
483 a correct one here before proceeding with the program.
484 This makes headers like
485 "Location: fuck-the-protocol.rdf" work.
486 In violalation of RFC1945, RFC2616. */
488 len = 7 + strlen(host) + strlen(redirecttarget) + 3;
489 newlocation = malloc(len);
490 memset (newlocation, 0, len);
491 strcat (newlocation, "http://");
492 strcat (newlocation, host);
493 if (redirecttarget[0] != '/')
494 strcat (newlocation, "/");
495 strcat (newlocation, redirecttarget);
497 newlocation = strdup (redirecttarget);
499 /* This also frees redirecttarget. */
502 /* Change cur_ptr->feedurl on 301. */
503 if (cur_ptr->lasthttpstatus == 301) {
504 /* Check for valid redirection URL */
505 if (checkValidHTTPURL(newlocation) != 0) {
506 cur_ptr->problem = 1;
507 cur_ptr->netio_error = NET_ERR_REDIRECT_ERR;
511 if (!suppressoutput) {
512 fprintf(stderr, "URL points to permanent redirect, updating with new location...\n");
514 free (cur_ptr->feedurl);
515 if (authdata == NULL)
516 cur_ptr->feedurl = strdup (newlocation);
518 /* Include authdata in newly constructed URL. */
519 len = strlen(authdata) + strlen(newlocation) + 2;
520 cur_ptr->feedurl = malloc (len);
521 newurl = strdup(newlocation);
523 strsep (&newurl, "/");
524 strsep (&newurl, "/");
525 snprintf (cur_ptr->feedurl, len, "http://%s@%s", authdata, newurl);
530 freeme = newlocation;
531 strsep (&newlocation, "/");
532 strsep (&newlocation, "/");
533 tmphost = newlocation;
534 /* The following line \0-terminates tmphost in overwriting the first
535 / after the hostname. */
536 strsep (&newlocation, "/");
538 /* newlocation must now be the absolute path on newhost.
539 If not we've been redirected to somewhere totally stupid
540 (oh yeah, no offsite linking, go to our fucking front page).
541 Say goodbye to the webserver in this case. In fact, we don't
542 even say goodbye, but just drop the connection. */
543 if (newlocation == NULL) {
544 cur_ptr->netio_error = NET_ERR_REDIRECT_ERR;
549 newhost = strdup (tmphost);
551 newlocation[0] = '/';
552 newurl = strdup (newlocation);
556 /* Close connection. */
559 /* Reconnect to server. */
560 if ((NetConnect (my_socket, newhost, cur_ptr, httpproto, suppressoutput)) != 0) {
572 /* Not modified received. We can close stream and return from here.
573 Not very friendly though. :) */
575 /* Received good status from server, clear problem field. */
576 cur_ptr->netio_error = NET_ERR_OK;
577 cur_ptr->problem = 0;
579 /* This should be freed everywhere where we return
580 and current feed uses auth. */
581 if ((redirectcount > 0) && (authdata != NULL)) {
588 Parse rest of header and rerequest URL from server using auth mechanism
589 requested in WWW-Authenticate header field. (Basic or Digest) */
592 cur_ptr->netio_error = NET_ERR_HTTP_404;
595 case 410: /* The feed is gone. Politely remind the user to unsubscribe. */
596 cur_ptr->netio_error = NET_ERR_HTTP_410;
600 cur_ptr->netio_error = NET_ERR_HTTP_NON_200;
604 /* unknown error codes have to be treated like the base class */
606 /* first pass, modify error code to base class */
608 tmphttpstatus -= tmphttpstatus % 100;
610 /* second pass, give up on unknown error base class */
611 cur_ptr->netio_error = NET_ERR_HTTP_NON_200;
618 /* Read rest of HTTP header and parse what we need. */
619 while (!feof(stream)) {
620 if ((NetPoll (cur_ptr, my_socket, NET_READ)) == -1) {
625 if ((fgets (netbuf, sizeof(netbuf), stream)) == NULL)
628 if (checkValidHTTPHeader(netbuf, sizeof(netbuf)) != 0) {
629 cur_ptr->netio_error = NET_ERR_HTTP_PROTO_ERR;
634 if (strncasecmp (netbuf, "Transfer-Encoding", 17) == 0) {
635 /* Chunked transfer encoding. HTTP/1.1 extension.
636 http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1 */
637 if (strstr (netbuf, "chunked") != NULL)
640 /* Get last modified date. This is only relevant on HTTP 200. */
641 if ((strncasecmp (netbuf, "Last-Modified", 13) == 0) &&
642 (cur_ptr->lasthttpstatus == 200)) {
643 tmpstring = strdup(netbuf);
645 strsep (&tmpstring, " ");
646 if (tmpstring == NULL)
649 free(cur_ptr->lastmodified);
650 cur_ptr->lastmodified = strdup(tmpstring);
651 if (cur_ptr->lastmodified[strlen(cur_ptr->lastmodified)-1] == '\n')
652 cur_ptr->lastmodified[strlen(cur_ptr->lastmodified)-1] = '\0';
653 if (cur_ptr->lastmodified[strlen(cur_ptr->lastmodified)-1] == '\r')
654 cur_ptr->lastmodified[strlen(cur_ptr->lastmodified)-1] = '\0';
658 if (strncasecmp (netbuf, "Content-Encoding", 16) == 0) {
659 if (strstr (netbuf, "gzip") != NULL)
662 if (strncasecmp (netbuf, "Content-Type", 12) == 0) {
663 tmpstring = strdup(netbuf);
665 strsep(&tmpstring, " ");
666 if (tmpstring == NULL)
670 freeme2 = strstr(tmpstring, ";");
673 free(cur_ptr->content_type);
674 cur_ptr->content_type = strdup(tmpstring);
675 if (cur_ptr->content_type[strlen(cur_ptr->content_type)-1] == '\n')
676 cur_ptr->content_type[strlen(cur_ptr->content_type)-1] = '\0';
677 if (cur_ptr->content_type[strlen(cur_ptr->content_type)-1] == '\r')
678 cur_ptr->content_type[strlen(cur_ptr->content_type)-1] = '\0';
682 /* HTTP authentication
685 if ((strncasecmp (netbuf, "WWW-Authenticate", 16) == 0) &&
686 (cur_ptr->lasthttpstatus == 401)) {
688 /* Don't repeat authrequest if it already failed before! */
689 cur_ptr->netio_error = NET_ERR_AUTH_FAILED;
694 /* Remove trailing \r\n from line. */
695 if (netbuf[strlen(netbuf)-1] == '\n')
696 netbuf[strlen(netbuf)-1] = '\0';
697 if (netbuf[strlen(netbuf)-1] == '\r')
698 netbuf[strlen(netbuf)-1] = '\0';
702 /* Make a copy of the WWW-Authenticate header. We use it to
703 reconstruct a new auth reply on every loop. */
704 free (cur_ptr->servauth);
706 cur_ptr->servauth = strdup (netbuf);
708 /* Load authinfo into cur_ptr->authinfo. */
709 retval = NetSupportAuth(cur_ptr, authdata, url, netbuf);
713 cur_ptr->netio_error = NET_ERR_AUTH_NO_AUTHINFO;
718 cur_ptr->netio_error = NET_ERR_AUTH_GEN_AUTH_ERR;
723 cur_ptr->netio_error = NET_ERR_AUTH_UNSUPPORTED;
731 /* Close current connection and reconnect to server. */
733 if ((NetConnect (my_socket, host, cur_ptr, httpproto, suppressoutput)) != 0) {
737 /* Now that we have an authinfo, repeat the current request. */
740 /* This seems to be optional and probably not worth the effort since we
741 don't issue a lot of consecutive requests. */
742 /*if ((strncasecmp (netbuf, "Authentication-Info", 19) == 0) ||
743 (cur_ptr->lasthttpstatus == 200)) {
747 /* HTTP RFC 2616, Section 19.3 Tolerant Applications.
748 Accept CRLF and LF line ends in the header field. */
749 if ((strcmp(netbuf, "\r\n") == 0) || (strcmp(netbuf, "\n") == 0))
753 /* If the redirectloop was run newhost and newurl were allocated.
754 We need to free them here.
755 But _after_ the authentication code since it needs these values! */
756 if ((redirectcount > 0) && (authdata != NULL)) {
761 /**********************
762 * End of HTTP header *
763 **********************/
765 /* Init pointer so strncat works.
766 Workaround class hack. */
772 /* Read stream until EOF and return it to parent. */
773 while (!feof(stream)) {
774 if ((NetPoll (cur_ptr, my_socket, NET_READ)) == -1) {
779 /* Since we handle binary data if we read compressed input we
780 need to use fread instead of fgets after reading the header. */
781 retval = fread (netbuf, 1, sizeof(netbuf), stream);
784 body = realloc (body, length+retval);
785 memcpy (body+length, netbuf, retval);
790 body = realloc(body, length+1);
793 cur_ptr->content_length = length;
795 /* Close connection. */
799 if (decodechunked(body, &length) == NULL) {
801 cur_ptr->netio_error = NET_ERR_HTTP_PROTO_ERR;
806 /* If inflate==1 we need to decompress the content.. */
809 /*inflatedbody = gzip_uncompress (body, length, &cur_ptr->content_length);
810 if (inflatedbody == NULL) {
812 cur_ptr->netio_error = NET_ERR_GZIP_ERR;
815 if (jg_gzip_uncompress (body, length, (void **)&inflatedbody, &cur_ptr->content_length) != 0) {
817 cur_ptr->netio_error = NET_ERR_GZIP_ERR;
821 /* Copy uncompressed data back to body. */
829 /* Returns allocated string with body of webserver reply.
830 Various status info put into struct feed *cur_ptr.
831 Set suppressoutput=1 to disable diagnostic output. */
832 char *DownloadFeed(char *url, struct feed *cur_ptr, int suppressoutput) {
835 char *host; /* Needs to freed. */
839 char *authdata = NULL;
841 int httpproto = 0; /* 0: http; 1: https */
843 if (checkValidHTTPURL(url) != 0) {
844 cur_ptr->problem = 1;
845 cur_ptr->netio_error = NET_ERR_HTTP_PROTO_ERR;
848 /* strstr will match _any_ substring. Not good, use strncasecmp with length 5! */
849 if (strncasecmp (url, "https", 5) == 0)
859 /* Assume "/" is input is exhausted. */
864 /* If tmphost contains an '@', extract username and pwd. */
865 if (strchr (tmphost, '@') != NULL) {
867 strsep (&tmphost, "@");
868 authdata = strdup (tmpstr);
871 host = strdup (tmphost);
873 /* netio() might change pointer of host to something else if redirect
874 loop is executed. Make a copy so we can correctly free everything. */
876 /* Only run if url was != NULL above. */
880 if (url[strlen(url)-1] == '\n') {
881 url[strlen(url)-1] = '\0';
885 if ((NetConnect (&my_socket, host, cur_ptr, httpproto, suppressoutput)) != 0) {
890 cur_ptr->problem = 1;
893 returndata = NetIO (&my_socket, host, url, cur_ptr, authdata, httpproto, suppressoutput);
894 if ((returndata == NULL) && (cur_ptr->netio_error != NET_ERR_OK)) {
895 cur_ptr->problem = 1;
898 /* url will be freed in the calling function. */
899 free (freeme); /* This is *host. */