RSS client now uses libcurl instead of the crappy built-in
[citadel.git] / webcit / httpfetch.c
1 /*
2  * mini http client
3  */
4
5
6 #include "webcit.h"
7 #include "webserver.h"
8 #define CLIENT_TIMEOUT 15
9 #define sock_close(sock) close(sock)
10
11 int sock_connect(char *host, char *service, char *protocol)
12 {
13         struct hostent *phe;
14         struct servent *pse;
15         struct protoent *ppe;
16         struct sockaddr_in sin;
17         struct sockaddr_in egress_sin;
18         int s, type;
19
20         if ((host == NULL) || IsEmptyStr(host)) 
21                 return(-1);
22         if ((service == NULL) || IsEmptyStr(service)) 
23                 return(-1);
24         if ((protocol == NULL) || IsEmptyStr(protocol)) 
25                 return(-1);
26
27         memset(&sin, 0, sizeof(sin));
28         sin.sin_family = AF_INET;
29
30         pse = getservbyname(service, protocol);
31         if (pse) {
32                 sin.sin_port = pse->s_port;
33         } else if ((sin.sin_port = htons((u_short) atoi(service))) == 0) {
34                 lprintf(CTDL_CRIT, "Can't get %s service entry: %s\n",
35                         service, strerror(errno));
36                 return(-1);
37         }
38         phe = gethostbyname(host);
39         if (phe) {
40                 memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
41         } else if ((sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) {
42                 lprintf(CTDL_ERR, "Can't get %s host entry: %s\n",
43                         host, strerror(errno));
44                 return(-1);
45         }
46         if ((ppe = getprotobyname(protocol)) == 0) {
47                 lprintf(CTDL_CRIT, "Can't get %s protocol entry: %s\n",
48                         protocol, strerror(errno));
49                 return(-1);
50         }
51         if (!strcmp(protocol, "udp")) {
52                 type = SOCK_DGRAM;
53         } else {
54                 type = SOCK_STREAM;
55         }
56
57         s = socket(PF_INET, type, ppe->p_proto);
58         if (s < 0) {
59                 lprintf(CTDL_CRIT, "Can't create socket: %s\n", strerror(errno));
60                 return(-1);
61         }
62
63         /* If citserver is bound to a specific IP address on the host, make
64          * sure we use that address for outbound connections.  FIXME make this work in webcit
65          */
66         memset(&egress_sin, 0, sizeof(egress_sin));
67         egress_sin.sin_family = AF_INET;
68         //      if (!IsEmptyStr(config.c_ip_addr)) {
69                 //      egress_sin.sin_addr.s_addr = inet_addr(config.c_ip_addr);
70                 //      if (egress_sin.sin_addr.s_addr == !INADDR_ANY) {
71                         //      egress_sin.sin_addr.s_addr = INADDR_ANY;
72                 //      }
73
74                 /* If this bind fails, no problem; we can still use INADDR_ANY */
75                 bind(s, (struct sockaddr *)&egress_sin, sizeof(egress_sin));
76         //      }
77
78         /* Now try to connect to the remote host. */
79         if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
80                 lprintf(CTDL_ERR, "Can't connect to %s:%s: %s\n",
81                         host, service, strerror(errno));
82                 close(s);
83                 return(-1);
84         }
85
86         return (s);
87 }
88
89
90
91 /*
92  * sock_read_to() - input binary data from socket, with a settable timeout.
93  * Returns the number of bytes read, or -1 for error.
94  * If keep_reading_until_full is nonzero, we keep reading until we get the number of requested bytes
95  */
96 int sock_read_to(int sock, char *buf, int bytes, int timeout, int keep_reading_until_full)
97 {
98         int len,rlen;
99         fd_set rfds;
100         struct timeval tv;
101         int retval;
102
103         len = 0;
104         while (len<bytes) {
105                 FD_ZERO(&rfds);
106                 FD_SET(sock, &rfds);
107                 tv.tv_sec = timeout;
108                 tv.tv_usec = 0;
109
110                 retval = select(sock+1, &rfds, NULL, NULL, &tv);
111
112                 if (FD_ISSET(sock, &rfds) == 0) {       /* timed out */
113                         lprintf(CTDL_ERR, "sock_read_to() timed out.\n");
114                         return(-1);
115                 }
116
117                 rlen = read(sock, &buf[len], bytes-len);
118                 if (rlen<1) {
119                         lprintf(CTDL_ERR, "sock_read_to() failed: %s\n",
120                                 strerror(errno));
121                         return(-1);
122                 }
123                 len = len + rlen;
124                 if (!keep_reading_until_full) return(len);
125         }
126         return(bytes);
127 }
128
129
130 /*
131  * sock_write() - send binary to server.
132  * Returns the number of bytes written, or -1 for error.
133  */
134 int sock_write(int sock, char *buf, int nbytes)
135 {
136         int bytes_written = 0;
137         int retval;
138         while (bytes_written < nbytes) {
139                 retval = write(sock, &buf[bytes_written],
140                                nbytes - bytes_written);
141                 if (retval < 1) {
142                         return (-1);
143                 }
144                 bytes_written = bytes_written + retval;
145         }
146         return (bytes_written);
147 }
148
149
150
151 /*
152  * Input string from socket - implemented in terms of sock_read_to()
153  * 
154  */
155 int sock_getln(int sock, char *buf, int bufsize)
156 {
157         int i;
158
159         /* Read one character at a time.
160          */
161         for (i = 0;; i++) {
162                 if (sock_read_to(sock, &buf[i], 1, CLIENT_TIMEOUT, 1) < 0) return(-1);
163                 if (buf[i] == '\n' || i == (bufsize-1))
164                         break;
165         }
166
167         /* If we got a long line, discard characters until the newline.
168          */
169         if (i == (bufsize-1))
170                 while (buf[i] != '\n')
171                         if (sock_read_to(sock, &buf[i], 1, CLIENT_TIMEOUT, 1) < 0) return(-1);
172
173         /* Strip any trailing CR and LF characters.
174          */
175         buf[i] = 0;
176         while ( (i > 0)
177                 && ( (buf[i - 1]==13)
178                      || ( buf[i - 1]==10)) ) {
179                 i--;
180                 buf[i] = 0;
181         }
182         return(i);
183 }
184
185
186
187 /*
188  * sock_puts() - send line to server - implemented in terms of serv_write()
189  * Returns the number of bytes written, or -1 for error.
190  */
191 int sock_puts(int sock, char *buf)
192 {
193         int i, j;
194
195         i = sock_write(sock, buf, strlen(buf));
196         if (i<0) return(i);
197         j = sock_write(sock, "\n", 1);
198         if (j<0) return(j);
199         return(i+j);
200 }
201
202
203
204
205
206
207 /*
208  * Fallback handler for fetch_http() that uses our built-in mini client
209  */
210 int fetch_http_using_mini_client(char *url, char *target_buf, int maxbytes)
211 {
212         char buf[1024];
213         char httphost[1024];
214         int httpport = 80;
215         char httpurl[1024];
216         int sock = (-1);
217         int got_bytes = (-1);
218         int redirect_count = 0;
219         int total_bytes_received = 0;
220         int i = 0;
221
222         /* Parse the URL */
223         snprintf(buf, (sizeof buf)-1, "%s", url);
224         i = parse_url(buf, httphost, &httpport, httpurl);
225         if (i == 1) {
226                 snprintf(buf, (sizeof buf)-1, "http://%s", url);
227                 i = parse_url(buf, httphost, &httpport, httpurl);
228         }
229         if (i == 4) {
230                 strcat(buf, "/");
231                 i = parse_url(buf, httphost, &httpport, httpurl);
232         }
233         if (i != 0) {
234                 lprintf(CTDL_ALERT, "Invalid URL: %s (%d)\n", url, i);
235                 return(-1);
236         }
237
238 retry:  lprintf(CTDL_NOTICE, "Connecting to <%s>\n", httphost);
239         sprintf(buf, "%d", httpport);
240         sock = sock_connect(httphost, buf, "tcp");
241         if (sock >= 0) {
242                 lprintf(CTDL_DEBUG, "Connected!\n");
243
244                 snprintf(buf, sizeof buf, "GET %s HTTP/1.0", httpurl);
245                 lprintf(CTDL_DEBUG, "<%s\n", buf);
246                 sock_puts(sock, buf);
247
248                 snprintf(buf, sizeof buf, "Host: %s", httphost);
249                 lprintf(CTDL_DEBUG, "<%s\n", buf);
250                 sock_puts(sock, buf);
251
252                 snprintf(buf, sizeof buf, "User-Agent: WebCit");
253                 lprintf(CTDL_DEBUG, "<%s\n", buf);
254                 sock_puts(sock, buf);
255
256                 snprintf(buf, sizeof buf, "Accept: */*");
257                 lprintf(CTDL_DEBUG, "<%s\n", buf);
258                 sock_puts(sock, buf);
259
260                 sock_puts(sock, "");
261
262                 if (sock_getln(sock, buf, sizeof buf) >= 0) {
263                         lprintf(CTDL_DEBUG, ">%s\n", buf);
264                         remove_token(buf, 0, ' ');
265
266                         /* 200 OK */
267                         if (buf[0] == '2') {
268
269                                 while (got_bytes = sock_getln(sock, buf, sizeof buf),
270                                       (got_bytes >= 0 && (strcmp(buf, "")) && (strcmp(buf, "\r"))) ) {
271                                         /* discard headers */
272                                 }
273
274                                 while (got_bytes = sock_read_to(sock, buf, sizeof buf, CLIENT_TIMEOUT, 0),
275                                       (got_bytes>0)  ) {
276
277                                         if (total_bytes_received + got_bytes > maxbytes) {
278                                                 got_bytes = maxbytes - total_bytes_received;
279                                         }
280                                         if (got_bytes > 0) {
281                                                 memcpy(&target_buf[total_bytes_received], buf, got_bytes);
282                                                 total_bytes_received += got_bytes;
283                                         }
284
285                                 }
286                         }
287
288                         /* 30X redirect */
289                         else if ( (!strncmp(buf, "30", 2)) && (redirect_count < 16) ) {
290                                 while (got_bytes = sock_getln(sock, buf, sizeof buf),
291                                       (got_bytes >= 0 && (strcmp(buf, "")) && (strcmp(buf, "\r"))) ) {
292                                         if (!strncasecmp(buf, "Location:", 9)) {
293                                                 ++redirect_count;
294                                                 strcpy(buf, &buf[9]);
295                                                 striplt(buf);
296                                                 if (parse_url(buf, httphost, &httpport, httpurl) == 0) {
297                                                         sock_close(sock);
298                                                         goto retry;
299                                                 }
300                                                 else {
301                                                         lprintf(CTDL_ALERT, "Invalid URL: %s\n", buf);
302                                                 }
303                                         }
304                                 }
305                         }
306
307                 }
308                 sock_close(sock);
309         }
310         else {
311                 lprintf(CTDL_ERR, "Could not connect: %s\n", strerror(errno));
312         }
313
314         return total_bytes_received;
315 }
316
317
318
319 /*
320  * Begin an HTTP fetch (returns number of bytes actually fetched, or -1 for error)
321  * We first try 'curl' or 'wget' because they have more robust HTTP handling, and also
322  * support HTTPS.  If neither one works, we fall back to a built in mini HTTP client.
323  */
324 int fetch_http(char *url, char *target_buf, int maxbytes)
325 {
326         FILE *fp;
327         char cmd[1024];
328         int bytes_received = 0;
329         char ch;
330
331         memset(target_buf, 0, maxbytes);
332
333         /* First try curl */
334         snprintf(cmd, sizeof cmd, "curl -L %s </dev/null 2>/dev/null", url);
335         fp = popen(cmd, "r");
336
337         /* Then try wget */
338         if (!fp) {
339                 snprintf(cmd, sizeof cmd, "wget -q -O - %s </dev/null 2>/dev/null", url);
340                 fp = popen(cmd, "r");
341         }
342
343         if (fp) {
344                 while ( (!feof(fp)) && (bytes_received < maxbytes) ) {
345                         ch = fgetc(fp);
346                         if (ch != EOF) {
347                                 target_buf[bytes_received++] = ch;
348                         }
349                 }
350                 pclose(fp);
351                 return bytes_received;
352         }
353
354         /* Fall back to the built-in mini handler */
355         return fetch_http_using_mini_client(url, target_buf, maxbytes);
356 }