Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-http-stream.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
4  *
5  *  Copyright 2002 Ximian, Inc. (www.ximian.com)
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU Lesser General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
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 Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <ctype.h>
28 #include <errno.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include "camel-exception.h"
34 #include "camel-http-stream.h"
35 #include "camel-mime-utils.h"
36 #include "camel-net-utils.h"
37 #include "camel-service.h" /* for hostname stuff */
38 #include "camel-session.h"
39 #include "camel-stream-buffer.h"
40 #include "camel-tcp-stream-raw.h"
41
42 #ifdef HAVE_SSL
43 #include "camel-tcp-stream-ssl.h"
44 #endif
45
46 #define d(x) 
47
48 static CamelStreamClass *parent_class = NULL;
49
50 static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n);
51 static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n);
52 static int stream_flush  (CamelStream *stream);
53 static int stream_close  (CamelStream *stream);
54 static int stream_reset  (CamelStream *stream);
55
56 static void
57 camel_http_stream_class_init (CamelHttpStreamClass *camel_http_stream_class)
58 {
59         CamelStreamClass *camel_stream_class =
60                 CAMEL_STREAM_CLASS (camel_http_stream_class);
61         
62         parent_class = CAMEL_STREAM_CLASS (camel_type_get_global_classfuncs (camel_stream_get_type ()));
63         
64         /* virtual method overload */
65         camel_stream_class->read = stream_read;
66         camel_stream_class->write = stream_write;
67         camel_stream_class->flush = stream_flush;
68         camel_stream_class->close = stream_close;
69         camel_stream_class->reset = stream_reset;
70 }
71
72 static void
73 camel_http_stream_init (gpointer object, gpointer klass)
74 {
75         CamelHttpStream *http = CAMEL_HTTP_STREAM (object);
76         
77         http->parser = NULL;
78         http->content_type = NULL;
79         http->headers = NULL;
80         http->session = NULL;
81         http->url = NULL;
82         http->proxy = NULL;
83         http->authrealm = NULL;
84         http->authpass = NULL;
85         http->statuscode = 0;
86         http->raw = NULL;
87 }
88
89 static void
90 camel_http_stream_finalize (CamelObject *object)
91 {
92         CamelHttpStream *http = CAMEL_HTTP_STREAM (object);
93         
94         if (http->parser)
95                 camel_object_unref(http->parser);
96         
97         if (http->content_type)
98                 camel_content_type_unref (http->content_type);
99         
100         if (http->headers)
101                 camel_header_raw_clear (&http->headers);
102         
103         if (http->session)
104                 camel_object_unref(http->session);
105         
106         if (http->url)
107                 camel_url_free (http->url);
108         
109         if (http->proxy)
110                 camel_url_free (http->proxy);
111         
112         g_free (http->authrealm);
113         g_free (http->authpass);
114         
115         if (http->raw)
116                 camel_object_unref(http->raw);
117         if (http->read)
118                 camel_object_unref(http->read);
119 }
120
121 CamelType
122 camel_http_stream_get_type (void)
123 {
124         static CamelType type = CAMEL_INVALID_TYPE;
125         
126         if (type == CAMEL_INVALID_TYPE) {
127                 type = camel_type_register (camel_stream_get_type (),
128                                             "CamelHttpStream",
129                                             sizeof (CamelHttpStream),
130                                             sizeof (CamelHttpStreamClass),
131                                             (CamelObjectClassInitFunc) camel_http_stream_class_init,
132                                             NULL,
133                                             (CamelObjectInitFunc) camel_http_stream_init,
134                                             (CamelObjectFinalizeFunc) camel_http_stream_finalize);
135         }
136         
137         return type;
138 }
139
140 /**
141  * camel_http_stream_new:
142  * @method: HTTP method
143  * @session: active session
144  * @url: URL to act upon
145  *
146  * Return value: a http stream
147  **/
148 CamelStream *
149 camel_http_stream_new (CamelHttpMethod method, struct _CamelSession *session, CamelURL *url)
150 {
151         CamelHttpStream *stream;
152         char *str;
153         
154         g_return_val_if_fail(CAMEL_IS_SESSION(session), NULL);
155         g_return_val_if_fail(url != NULL, NULL);
156         
157         stream = CAMEL_HTTP_STREAM (camel_object_new (camel_http_stream_get_type ()));
158         
159         stream->method = method;
160         stream->session = session;
161         camel_object_ref(session);
162         
163         str = camel_url_to_string (url, 0);
164         stream->url = camel_url_new (str, NULL);
165         g_free (str);
166         
167         return (CamelStream *)stream;
168 }
169
170 #define SSL_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
171
172 static CamelStream *
173 http_connect (CamelHttpStream *http, CamelURL *url)
174 {
175         CamelStream *stream = NULL;
176         struct addrinfo *ai, hints = { 0 };
177         int errsave;
178         char *serv;
179
180         d(printf("connecting to http stream @ '%s'\n", url->host));
181
182         if (!g_ascii_strcasecmp (url->protocol, "https")) {
183 #ifdef HAVE_SSL
184                 stream = camel_tcp_stream_ssl_new (http->session, url->host, SSL_FLAGS);
185 #endif
186         } else {
187                 stream = camel_tcp_stream_raw_new ();
188         }
189         
190         if (stream == NULL) {
191                 errno = EINVAL;
192                 return NULL;
193         }
194
195         if (url->port) {
196                 serv = g_alloca(16);
197                 sprintf(serv, "%d", url->port);
198         } else {
199                 serv = url->protocol;
200         }
201         hints.ai_socktype = SOCK_STREAM;
202
203         ai = camel_getaddrinfo(url->host, serv, &hints, NULL);
204         if (ai == NULL) {
205                 camel_object_unref (stream);
206                 return NULL;
207         }
208         
209         if (camel_tcp_stream_connect (CAMEL_TCP_STREAM (stream), ai) == -1) {
210                 errsave = errno;
211                 camel_object_unref (stream);
212                 camel_freeaddrinfo(ai);
213                 errno = errsave;
214                 return NULL;
215         }
216         
217         camel_freeaddrinfo(ai);
218
219         http->raw = stream;
220         http->read = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_READ);
221         
222         return stream;
223 }
224
225 static void
226 http_disconnect(CamelHttpStream *http)
227 {
228         if (http->raw) {
229                 camel_object_unref(http->raw);
230                 http->raw = NULL;
231         }
232                         
233         if (http->read) {
234                 camel_object_unref(http->read);
235                 http->read = NULL;
236         }
237
238         if (http->parser) {
239                 camel_object_unref(http->parser);
240                 http->parser = NULL;
241         }
242 }
243
244 static const char *
245 http_next_token (const unsigned char *in)
246 {
247         const unsigned char *inptr = in;
248         
249         while (*inptr && !isspace ((int) *inptr))
250                 inptr++;
251         
252         while (*inptr && isspace ((int) *inptr))
253                 inptr++;
254         
255         return (const char *) inptr;
256 }
257
258 static int
259 http_get_statuscode (CamelHttpStream *http)
260 {
261         const char *token;
262         char buffer[4096];
263         
264         if (camel_stream_buffer_gets ((CamelStreamBuffer *)http->read, buffer, sizeof (buffer)) <= 0)
265                 return -1;
266
267         d(printf("HTTP Status: %s\n", buffer));
268
269         /* parse the HTTP status code */
270         if (!g_ascii_strncasecmp (buffer, "HTTP/", 5)) {
271                 token = http_next_token (buffer);
272                 http->statuscode = camel_header_decode_int (&token);
273                 return http->statuscode;
274         }
275
276         http_disconnect(http);
277         
278         return -1;
279 }
280
281 static int
282 http_get_headers (CamelHttpStream *http)
283 {
284         struct _camel_header_raw *headers, *node, *tail;
285         const char *type;
286         char *buf;
287         size_t len;
288         int err;
289         
290         if (http->parser)
291                 camel_object_unref (http->parser);
292         
293         http->parser = camel_mime_parser_new ();
294         camel_mime_parser_init_with_stream (http->parser, http->read);
295         
296         switch (camel_mime_parser_step (http->parser, &buf, &len)) {
297         case CAMEL_MIME_PARSER_STATE_MESSAGE:
298         case CAMEL_MIME_PARSER_STATE_HEADER:
299                 headers = camel_mime_parser_headers_raw (http->parser);
300                 if (http->content_type)
301                         camel_content_type_unref (http->content_type);
302                 type = camel_header_raw_find (&headers, "Content-Type", NULL);
303                 if (type)
304                         http->content_type = camel_content_type_decode (type);
305                 else
306                         http->content_type = NULL;
307                 
308                 if (http->headers)
309                         camel_header_raw_clear (&http->headers);
310                 
311                 http->headers = NULL;
312                 tail = (struct _camel_header_raw *) &http->headers;
313                 
314                 d(printf("HTTP Headers:\n"));
315                 while (headers) {
316                         d(printf(" %s:%s\n", headers->name, headers->value));
317                         node = g_new (struct _camel_header_raw, 1);
318                         node->next = NULL;
319                         node->name = g_strdup (headers->name);
320                         node->value = g_strdup (headers->value);
321                         node->offset = headers->offset;
322                         tail->next = node;
323                         tail = node;
324                         headers = headers->next;
325                 }
326                 
327                 break;
328         default:
329                 g_warning ("Invalid state encountered???: %u", camel_mime_parser_state (http->parser));
330         }
331         
332         err = camel_mime_parser_errno (http->parser);
333         
334         if (err != 0) {
335                 camel_object_unref (http->parser);
336                 http->parser = NULL;
337                 goto exception;
338         }
339         
340         camel_mime_parser_drop_step (http->parser);
341         
342         return 0;
343         
344  exception:
345         http_disconnect(http);
346         
347         return -1;
348 }
349
350 static int
351 http_method_invoke (CamelHttpStream *http)
352 {
353         const char *method = NULL;
354         char *url;
355         
356         switch (http->method) {
357         case CAMEL_HTTP_METHOD_GET:
358                 method = "GET";
359                 break;
360         case CAMEL_HTTP_METHOD_HEAD:
361                 method = "HEAD";
362                 break;
363         default:
364                 g_assert_not_reached ();
365         }
366         
367         url = camel_url_to_string (http->url, 0);
368         d(printf("HTTP Stream Sending: %s %s HTTP/1.0\r\nUser-Agent: %s\r\nHost: %s\r\n",
369                  method,
370                  http->proxy ? url : http->url->path,
371                  http->user_agent ? http->user_agent : "CamelHttpStream/1.0",
372                  http->url->host));
373         if (camel_stream_printf (http->raw, "%s %s HTTP/1.0\r\nUser-Agent: %s\r\nHost: %s\r\n",
374                                  method,
375                                  http->proxy ? url : http->url->path,
376                                  http->user_agent ? http->user_agent : "CamelHttpStream/1.0",
377                                  http->url->host) == -1) {
378                 http_disconnect(http);
379                 g_free (url);
380                 return -1;
381         }
382         g_free (url);
383
384         if (http->authrealm)
385                 d(printf("HTTP Stream Sending: WWW-Authenticate: %s\n", http->authrealm));
386         
387         if (http->authrealm && camel_stream_printf (http->raw, "WWW-Authenticate: %s\r\n", http->authrealm) == -1) {
388                 http_disconnect(http);
389                 return -1;
390         }
391
392         if (http->authpass && http->proxy)
393                 d(printf("HTTP Stream Sending: Proxy-Aurhorization: Basic %s\n", http->authpass));
394         
395         if (http->authpass && http->proxy && camel_stream_printf (http->raw, "Proxy-Authorization: Basic %s\r\n",
396                                                                   http->authpass) == -1) {
397                 http_disconnect(http);
398                 return -1;
399         }
400         
401         /* end the headers */
402         if (camel_stream_write (http->raw, "\r\n", 2) == -1 || camel_stream_flush (http->raw) == -1) {
403                 http_disconnect(http);
404                 return -1;
405         }
406         
407         return 0;
408 }
409
410 static ssize_t
411 stream_read (CamelStream *stream, char *buffer, size_t n)
412 {
413         CamelHttpStream *http = CAMEL_HTTP_STREAM (stream);
414         const char *parser_buf;
415         ssize_t nread;
416         
417         if (http->method != CAMEL_HTTP_METHOD_GET && http->method != CAMEL_HTTP_METHOD_HEAD) {
418                 errno = EIO;
419                 return -1;
420         }
421         
422  redirect:
423         
424         if (!http->raw) {
425                 if (http_connect (http, http->proxy ? http->proxy : http->url) == NULL)
426                         return -1;
427                 
428                 if (http_method_invoke (http) == -1)
429                         return -1;
430                 
431                 if (http_get_statuscode (http) == -1)
432                         return -1;
433                 
434                 if (http_get_headers (http) == -1)
435                         return -1;
436                 
437                 switch (http->statuscode) {
438                 case 200:
439                 case 206:
440                         /* we are OK to go... */
441                         break;
442                 case 301:
443                 case 302: {
444                         char *loc;
445                         CamelURL *url;
446
447                         camel_content_type_unref (http->content_type);
448                         http->content_type = NULL;
449                         http_disconnect(http);
450
451                         loc = g_strdup(camel_header_raw_find(&http->headers, "Location", NULL));
452                         if (loc == NULL) {
453                                 camel_header_raw_clear(&http->headers);
454                                 return -1;
455                         }
456
457                         /* redirect... */
458                         g_strstrip(loc);
459                         d(printf("HTTP redirect, location = %s\n", loc));
460                         url = camel_url_new_with_base(http->url, loc);
461                         camel_url_free (http->url);
462                         http->url = url;
463                         if (url == NULL)
464                                 http->url = camel_url_new(loc, NULL);
465                         g_free(loc);
466                         if (http->url == NULL) {
467                                 camel_header_raw_clear (&http->headers);
468                                 return -1;
469                         }
470                         d(printf(" redirect url = %p\n", http->url));
471                         camel_header_raw_clear (&http->headers);
472
473                         goto redirect;
474                         break; }
475                 case 407:
476                         /* failed proxy authentication? */
477                 default:
478                         /* unknown error */
479                         http_disconnect(http);
480                         return -1;
481                 }
482         }
483         
484         nread = camel_mime_parser_read (http->parser, &parser_buf, n);
485         
486         if (nread > 0)
487                 memcpy (buffer, parser_buf, nread);
488         else if (nread == 0)
489                 stream->eos = TRUE;
490         
491         return nread;
492 }
493
494 static ssize_t
495 stream_write (CamelStream *stream, const char *buffer, size_t n)
496 {
497         return -1;
498 }
499
500 static int
501 stream_flush (CamelStream *stream)
502 {
503         CamelHttpStream *http = (CamelHttpStream *) stream;
504         
505         if (http->raw)
506                 return camel_stream_flush (http->raw);
507         else
508                 return 0;
509 }
510
511 static int
512 stream_close (CamelStream *stream)
513 {
514         CamelHttpStream *http = (CamelHttpStream *) stream;
515         
516         if (http->raw) {
517                 if (camel_stream_close (http->raw) == -1)
518                         return -1;
519
520                 http_disconnect(http);
521         }
522         
523         return 0;
524 }
525
526 static int
527 stream_reset (CamelStream *stream)
528 {
529         CamelHttpStream *http = CAMEL_HTTP_STREAM (stream);
530         
531         if (http->raw)
532                 http_disconnect(http);
533
534         return 0;
535 }
536
537 CamelContentType *
538 camel_http_stream_get_content_type (CamelHttpStream *http_stream)
539 {
540         g_return_val_if_fail (CAMEL_IS_HTTP_STREAM (http_stream), NULL);
541         
542         if (!http_stream->content_type && !http_stream->raw) {
543                 if (http_connect (http_stream, http_stream->url) == NULL)
544                         return NULL;
545                 
546                 if (http_method_invoke (http_stream) == -1)
547                         return NULL;
548                 
549                 if (http_get_headers (http_stream) == -1)
550                         return NULL;
551         }
552         
553         if (http_stream->content_type)
554                 camel_content_type_ref (http_stream->content_type);
555         
556         return http_stream->content_type;
557 }
558
559 void
560 camel_http_stream_set_user_agent (CamelHttpStream *http_stream, const char *user_agent)
561 {
562         g_return_if_fail (CAMEL_IS_HTTP_STREAM (http_stream));
563         
564         g_free (http_stream->user_agent);
565         http_stream->user_agent = g_strdup (user_agent);
566 }
567
568 void
569 camel_http_stream_set_proxy (CamelHttpStream *http_stream, const char *proxy_url)
570 {
571         g_return_if_fail (CAMEL_IS_HTTP_STREAM (http_stream));
572         
573         if (http_stream->proxy)
574                 camel_url_free (http_stream->proxy);
575
576         if (proxy_url == NULL)
577                 http_stream->proxy = NULL;
578         else
579                 http_stream->proxy = camel_url_new (proxy_url, NULL);
580
581         if (http_stream->proxy) {
582                 char *basic, *basic64;
583
584                 basic = g_strdup_printf("%s:%s", http_stream->proxy->user?http_stream->proxy->user:"",
585                                         http_stream->proxy->passwd?http_stream->proxy->passwd:"");
586                 basic64 = camel_base64_encode_simple(basic, strlen(basic));
587                 memset(basic, 0, strlen(basic));
588                 g_free(basic);
589                 camel_http_stream_set_proxy_authpass(http_stream, basic64);
590                 memset(basic64, 0, strlen(basic64));
591                 g_free(basic64);
592         } else {
593                 camel_http_stream_set_proxy_authpass(http_stream, NULL);
594         }
595 }
596
597 void
598 camel_http_stream_set_proxy_authrealm (CamelHttpStream *http_stream, const char *proxy_authrealm)
599 {
600         g_return_if_fail (CAMEL_IS_HTTP_STREAM (http_stream));
601         
602         g_free (http_stream->authrealm);
603         http_stream->authrealm = g_strdup (proxy_authrealm);
604 }
605
606 void
607 camel_http_stream_set_proxy_authpass (CamelHttpStream *http_stream, const char *proxy_authpass)
608 {
609         g_return_if_fail (CAMEL_IS_HTTP_STREAM (http_stream));
610         
611         g_free (http_stream->authpass);
612         http_stream->authpass = g_strdup (proxy_authpass);
613 }