Fixed crash on failed HTTP request.
[gnome-vfs-httpcaptive.git] / modules / http-method.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* http-method.c - The HTTP method implementation for the GNOME Virtual File
3    System.
4
5    Copyright (C) 1999 Free Software Foundation
6    Copyright (C) 2000-2001 Eazel, Inc
7
8    The Gnome Library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Library General Public License as
10    published by the Free Software Foundation; either version 2 of the
11    License, or (at your option) any later version.
12
13    The Gnome Library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Library General Public License for more details.
17
18    You should have received a copy of the GNU Library General Public
19    License along with the Gnome Library; see the file COPYING.LIB.  If not,
20    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21    Boston, MA 02111-1307, USA.
22
23    Authors: 
24                  Ettore Perazzoli <ettore@gnu.org> (core HTTP)
25                  Ian McKellar <yakk@yakk.net> (WebDAV/PUT)
26                  Michael Fleming <mfleming@eazel.com> (Caching, Cleanup)
27                  The friendly GNU Wget sources
28         */
29
30 /* TODO:
31    - Handle redirection.
32    - Handle persistent connections.  */
33
34 #include <config.h>
35 #include "http-method.h"
36
37 #include "http-authn.h"
38 #include "http-cache.h"
39 /* Keep <sys/types.h> above any network includes for FreeBSD. */
40 #include <sys/types.h>
41 /* Keep <netinet/in.h> above <arpa/inet.h> for FreeBSD. */
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
44 #include <gconf/gconf-client.h>
45 #include <libgnomevfs/gnome-vfs-inet-connection.h>
46 #include <libgnomevfs/gnome-vfs-mime-sniff-buffer.h>
47 #include <libgnomevfs/gnome-vfs-mime.h>
48 #include <libgnomevfs/gnome-vfs-module-callback-module-api.h>
49 #include <libgnomevfs/gnome-vfs-module.h>
50 #include <libgnomevfs/gnome-vfs-private-utils.h>
51 #include <libgnomevfs/gnome-vfs-socket-buffer.h>
52 #include <libgnomevfs/gnome-vfs-socket.h>
53 #include <libgnomevfs/gnome-vfs-ssl.h>
54 #include <libgnomevfs/gnome-vfs-standard-callbacks.h>
55 #include <libxml/parser.h>
56 #include <libxml/tree.h>
57 #include <libxml/xmlmemory.h>
58 #include <stdarg.h>
59 #include <stdio.h>
60 #include <stdlib.h> /* for atoi */
61 #include <string.h>
62 #include <sys/socket.h>
63 #include <sys/time.h>
64 #include <unistd.h>
65 #include <netdb.h>
66
67 #ifdef DEBUG_HTTP_ENABLE
68 void
69 http_debug_printf (char *fmt, ...)
70 {
71         va_list args;
72         gchar * out;
73
74         g_assert (fmt);
75
76         va_start (args, fmt);
77
78         out = g_strdup_vprintf (fmt, args);
79
80         fprintf (stderr, "HTTP: [0x%08x] [%p] %s\n",
81                  (unsigned int) http_util_get_utime (),
82                  g_thread_self (), out);
83
84         g_free (out);
85         va_end (args);
86 }
87 #endif /* DEBUG_HTTP_ENABLE */
88
89 /* What do we qualify ourselves as?  */
90 /* FIXME bugzilla.gnome.org 41160: "gnome-vfs/1.0.0" may not be good. */
91 #define USER_AGENT_STRING       "gnome-vfs/" VERSION
92
93 /* Custom User-Agent environment variable */
94 #define CUSTOM_USER_AGENT_VARIABLE "GNOME_VFS_HTTP_USER_AGENT"
95
96 /* Standard HTTP[S] port.  */
97 #define DEFAULT_HTTP_PORT       80
98 #define DEFAULT_HTTPS_PORT      443
99
100 /* Standard HTTP proxy port */
101 #define DEFAULT_HTTP_PROXY_PORT 8080
102
103 /* Maximum amount of data to read if seek()ed forward. Otherwise make a new connection. */
104 #define MAX_BUFFER_SEEK_SKIP_READ       0x10000
105
106 /* GConf paths and keys */
107 #define PATH_GCONF_GNOME_VFS "/system/http_proxy"
108 #define ITEM_GCONF_HTTP_PROXY_PORT "port"
109 #define ITEM_GCONF_HTTP_PROXY_HOST "host"
110 #define KEY_GCONF_HTTP_PROXY_PORT (PATH_GCONF_GNOME_VFS "/" ITEM_GCONF_HTTP_PROXY_PORT)
111 #define KEY_GCONF_HTTP_PROXY_HOST (PATH_GCONF_GNOME_VFS "/" ITEM_GCONF_HTTP_PROXY_HOST)
112
113 #define ITEM_GCONF_USE_HTTP_PROXY "use_http_proxy"
114 #define KEY_GCONF_USE_HTTP_PROXY (PATH_GCONF_GNOME_VFS "/" ITEM_GCONF_USE_HTTP_PROXY)
115
116 #define KEY_GCONF_HTTP_AUTH_USER (PATH_GCONF_GNOME_VFS "/" "authentication_user")
117 #define KEY_GCONF_HTTP_AUTH_PW (PATH_GCONF_GNOME_VFS "/" "authentication_password")
118 #define KEY_GCONF_HTTP_USE_AUTH (PATH_GCONF_GNOME_VFS "/" "use_authentication")
119
120 #define KEY_GCONF_HTTP_PROXY_IGNORE_HOSTS (PATH_GCONF_GNOME_VFS "/" "ignore_hosts")
121
122
123 /* Some status code validation macros.  */
124 #define HTTP_20X(x)        (((x) >= 200) && ((x) < 300))
125 #define HTTP_PARTIAL(x)    ((x) == HTTP_STATUS_PARTIAL_CONTENTS)
126 #define HTTP_REDIRECTED(x) (((x) == HTTP_STATUS_MOVED_PERMANENTLY)      \
127                             || ((x) == HTTP_STATUS_MOVED_TEMPORARILY))
128
129 /* HTTP/1.1 status codes from RFC2068, provided for reference.  */
130 /* Successful 2xx.  */
131 #define HTTP_STATUS_OK                  200
132 #define HTTP_STATUS_CREATED             201
133 #define HTTP_STATUS_ACCEPTED            202
134 #define HTTP_STATUS_NON_AUTHORITATIVE   203
135 #define HTTP_STATUS_NO_CONTENT          204
136 #define HTTP_STATUS_RESET_CONTENT       205
137 #define HTTP_STATUS_PARTIAL_CONTENTS    206
138
139 /* Redirection 3xx.  */
140 #define HTTP_STATUS_MULTIPLE_CHOICES    300
141 #define HTTP_STATUS_MOVED_PERMANENTLY   301
142 #define HTTP_STATUS_MOVED_TEMPORARILY   302
143 #define HTTP_STATUS_SEE_OTHER           303
144 #define HTTP_STATUS_NOT_MODIFIED        304
145 #define HTTP_STATUS_USE_PROXY           305
146
147 /* Client error 4xx.  */
148 #define HTTP_STATUS_BAD_REQUEST         400
149 #define HTTP_STATUS_UNAUTHORIZED        401
150 #define HTTP_STATUS_PAYMENT_REQUIRED    402
151 #define HTTP_STATUS_FORBIDDEN           403
152 #define HTTP_STATUS_NOT_FOUND           404
153 #define HTTP_STATUS_METHOD_NOT_ALLOWED  405
154 #define HTTP_STATUS_NOT_ACCEPTABLE      406
155 #define HTTP_STATUS_PROXY_AUTH_REQUIRED 407
156 #define HTTP_STATUS_REQUEST_TIMEOUT     408
157 #define HTTP_STATUS_CONFLICT            409
158 #define HTTP_STATUS_GONE                410
159 #define HTTP_STATUS_LENGTH_REQUIRED     411
160 #define HTTP_STATUS_PRECONDITION_FAILED 412
161 #define HTTP_STATUS_REQENTITY_TOO_LARGE 413
162 #define HTTP_STATUS_REQURI_TOO_LARGE    414
163 #define HTTP_STATUS_UNSUPPORTED_MEDIA   415
164 #define HTTP_STATUS_LOCKED              423
165
166 /* Server errors 5xx.  */
167 #define HTTP_STATUS_INTERNAL            500
168 #define HTTP_STATUS_NOT_IMPLEMENTED     501
169 #define HTTP_STATUS_BAD_GATEWAY         502
170 #define HTTP_STATUS_UNAVAILABLE         503
171 #define HTTP_STATUS_GATEWAY_TIMEOUT     504
172 #define HTTP_STATUS_UNSUPPORTED_VERSION 505
173 #define HTTP_STATUS_INSUFFICIENT_STORAGE 507
174
175 /*
176  * Static Variables
177  */
178
179 /* Global variables used by the HTTP proxy config */ 
180 static GConfClient * gl_client = NULL;
181 static GMutex *gl_mutex = NULL;         /* This mutex protects preference values
182                                          * and ensures serialization of authentication
183                                          * hook callbacks
184                                          */
185 static gchar *gl_http_proxy = NULL;
186 static gchar *gl_http_proxy_auth = NULL;
187 static GSList *gl_ignore_hosts = NULL;  /* Elements are strings. */
188 static GSList *gl_ignore_addrs = NULL;  /* Elements are ProxyHostAddrs */
189
190 /* Store IP addresses that may represent network or host addresses and may be
191  * IPv4 or IPv6. */
192 typedef enum {
193         PROXY_IPv4 = 4,
194         PROXY_IPv6 = 6
195 } ProxyAddrType;
196
197 typedef struct {
198         ProxyAddrType type;
199         struct in_addr addr;
200         struct in_addr mask;
201 #ifdef ENABLE_IPV6
202         struct in6_addr addr6;
203         struct in6_addr mask6;
204 #endif
205 } ProxyHostAddr;
206
207 typedef struct {
208         GnomeVFSSocketBuffer *socket_buffer;
209         char *uri_string;
210         GnomeVFSURI *uri;
211         /* The list of headers returned with this response, newlines removed */
212         GList *response_headers;
213
214         /* File info for this file */
215         GnomeVFSFileInfo *file_info;
216
217         /* File offset of current pointer of 'socket_buffer'. */
218         GnomeVFSFileOffset socket_buffer_offset;
219
220         /* Offset; Current file position. */
221         GnomeVFSFileOffset offset;
222
223         /* Bytes to be written... */
224         GByteArray *to_be_written;
225
226         /* List of GnomeVFSFileInfo from a directory listing */
227         GList *files;
228
229         /* The last HTTP status code returned */
230         guint server_status;
231 } HttpFileHandle;
232
233 static GnomeVFSResult resolve_409                (GnomeVFSMethod *method,
234                                                   GnomeVFSURI *uri,
235                                                   GnomeVFSContext *context);
236 static void     proxy_set_authn                  (const char *username,
237                                                   const char *password);
238 static void     proxy_unset_authn                (void);
239 static gboolean invoke_callback_send_additional_headers (GnomeVFSURI *uri,
240                                                          GList **list);
241 static gboolean invoke_callback_headers_received (HttpFileHandle *handle);
242 static gboolean invoke_callback_basic_authn      (HttpFileHandle *handle, 
243                                                   enum AuthnHeaderType authn_which,
244                                                   gboolean previous_attempt_failed);
245 static gboolean check_authn_retry_request        (HttpFileHandle * http_handle,
246                                                   enum AuthnHeaderType authn_which,
247                                                   const char *prev_authn_header);
248 static void parse_ignore_host                    (gpointer data,
249                                                   gpointer user_data);
250 #ifdef ENABLE_IPV6
251 static void ipv6_network_addr                    (const struct in6_addr *addr,
252                                                   const struct in6_addr *mask,
253                                                   struct in6_addr *res);
254
255 /*Check whether the node is IPv6 enabled.*/
256 static gboolean
257 have_ipv6 (void)
258 {
259         int s;
260
261         s = socket (AF_INET6, SOCK_STREAM, 0);
262         if (s != -1) {
263                 close (s);
264                 return TRUE;
265         }
266
267         return FALSE;
268 }
269 #endif
270
271 static GnomeVFSFileInfo *
272 defaults_file_info_new (void)
273 {
274         GnomeVFSFileInfo *ret;
275
276         /* Fill up the file info structure with default values */
277         /* Default to REGULAR unless we find out later via a PROPFIND that it's a collection */
278
279         ret = gnome_vfs_file_info_new();
280
281         ret->type = GNOME_VFS_FILE_TYPE_REGULAR;
282         ret->flags = GNOME_VFS_FILE_FLAGS_NONE;
283
284         ret->valid_fields |= 
285                 GNOME_VFS_FILE_INFO_FIELDS_TYPE
286                         | GNOME_VFS_FILE_INFO_FIELDS_FLAGS;
287
288
289         return ret;
290 }
291
292 /* Does not allocate the 'handle' memory. */
293 static void
294 http_file_handle_new (HttpFileHandle *handle,
295                       GnomeVFSSocketBuffer *socket_buffer,
296                       GnomeVFSURI *uri)
297 {
298         memset (handle, 0, sizeof (*handle));
299
300         handle->socket_buffer = socket_buffer;
301         handle->uri_string = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE );
302         handle->uri = uri;
303         gnome_vfs_uri_ref(handle->uri);
304
305         handle->file_info = defaults_file_info_new();
306         handle->file_info->name = gnome_vfs_uri_extract_short_name (uri);
307 }
308
309 /* Does not free the 'handle' memory. */
310 static void
311 http_file_handle_destroy (HttpFileHandle *handle)
312 {
313         if (handle == NULL) {
314                 return;
315         }
316
317         gnome_vfs_uri_unref(handle->uri);
318         gnome_vfs_file_info_unref (handle->file_info);
319         g_free (handle->uri_string);
320         if (handle->to_be_written) {
321                 g_byte_array_free(handle->to_be_written, TRUE);
322         }
323
324         g_list_foreach (handle->response_headers, (GFunc) g_free, NULL);
325         g_list_free (handle->response_headers);
326
327         g_list_foreach(handle->files, (GFunc)gnome_vfs_file_info_unref, NULL);
328         g_list_free(handle->files);
329 }
330
331 /* The following comes from GNU Wget with minor changes by myself.
332    Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc.  */
333 /* Parse the HTTP status line, which is of format:
334
335    HTTP-Version SP Status-Code SP Reason-Phrase
336
337    The function returns the status-code, or -1 if the status line is
338    malformed.  The pointer to reason-phrase is returned in RP.  */
339 static gboolean
340 parse_status (const char *cline,
341               guint *status_return)
342 {
343         /* (the variables must not be named `major' and `minor', because
344            that breaks compilation with SunOS4 cc.)  */
345         guint mjr, mnr;
346         guint statcode;
347         const guchar *p, *line;
348
349         line = (const guchar *)cline;
350
351         /* The standard format of HTTP-Version is: `HTTP/X.Y', where X is
352            major version, and Y is minor version.  */
353         if (strncmp (line, "HTTP/", 5) == 0) {
354                 line += 5;
355                 
356                 /* Calculate major HTTP version.  */
357                 p = line;
358                 for (mjr = 0; g_ascii_isdigit (*line); line++)
359                         mjr = 10 * mjr + (*line - '0');
360                 if (*line != '.' || p == line)
361                         return FALSE;
362                 ++line;
363                 
364                 /* Calculate minor HTTP version.  */
365                 p = line;
366                 for (mnr = 0; g_ascii_isdigit (*line); line++)
367                         mnr = 10 * mnr + (*line - '0');
368                 if (*line != ' ' || p == line)
369                         return -1;
370                 /* Wget will accept only 1.0 and higher HTTP-versions.  The value of
371                    minor version can be safely ignored.  */
372                 if (mjr < 1)
373                         return FALSE;
374                 ++line;
375         } else if (strncmp (line, "ICY ", 4) == 0) {
376                 /* FIXME: workaround for broken ShoutCast and IceCast status replies.
377                  * They send things like "ICY 200 OK" instead of "HTTP/1.0 200 OK".
378                  * Is there a better way to handle this? 
379                  */
380                 mjr = 1;
381                 mnr = 0;
382                 line += 4;
383         } else {
384                 return FALSE;
385         }
386         
387         /* Calculate status code.  */
388         if (!(g_ascii_isdigit (*line) && g_ascii_isdigit (line[1]) && g_ascii_isdigit (line[2])))
389                 return -1;
390         statcode = 100 * (*line - '0') + 10 * (line[1] - '0') + (line[2] - '0');
391
392         *status_return = statcode;
393         return TRUE;
394 }
395
396 static GnomeVFSResult
397 http_status_to_vfs_result (guint status)
398 {
399         if (HTTP_20X (status))
400                 return GNOME_VFS_OK;
401
402         /* FIXME bugzilla.gnome.org 41163 */
403         /* mfleming--I've improved the situation slightly, but more
404          * test cases need to be written to ensure that HTTP (esp DAV) does compatibile
405          * things with the normal file method
406          */
407
408         switch (status) {
409         case HTTP_STATUS_PRECONDITION_FAILED:
410                 /* This mapping is certainly true for MOVE with Overwrite: F, otherwise not so true */
411                 return GNOME_VFS_ERROR_FILE_EXISTS;
412         case HTTP_STATUS_UNAUTHORIZED:
413         case HTTP_STATUS_PROXY_AUTH_REQUIRED:
414         case HTTP_STATUS_FORBIDDEN:
415                 /* Note that FORBIDDEN can also be returned on a MOVE in a case which
416                  * should be VFS_ERROR_BAD_PARAMETERS
417                  */
418                 return GNOME_VFS_ERROR_ACCESS_DENIED;
419         case HTTP_STATUS_NOT_FOUND:
420                 return GNOME_VFS_ERROR_NOT_FOUND;
421         case HTTP_STATUS_METHOD_NOT_ALLOWED:
422                 /* Note that METHOD_NOT_ALLOWED is also returned in a PROPFIND in a case which
423                  * should be FILE_EXISTS.  This is handled in do_make_directory
424                  */
425         case HTTP_STATUS_BAD_REQUEST:
426         case HTTP_STATUS_NOT_IMPLEMENTED:
427         case HTTP_STATUS_UNSUPPORTED_VERSION:
428                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
429         case HTTP_STATUS_CONFLICT:
430                 /* _CONFLICT's usually happen when collection paths don't exist */
431                 return GNOME_VFS_ERROR_NOT_FOUND;
432         case HTTP_STATUS_LOCKED:
433                 /* Maybe we need a separate GNOME_VFS_ERROR_LOCKED? */
434                 return GNOME_VFS_ERROR_DIRECTORY_BUSY;
435         case HTTP_STATUS_INSUFFICIENT_STORAGE:
436                 return GNOME_VFS_ERROR_NO_SPACE;
437         default:
438                 return GNOME_VFS_ERROR_GENERIC;
439         }
440 }
441
442 /* Header parsing routines.  */
443
444 static gboolean
445 header_value_to_number (const char *header_value,
446                         gulong *number)
447 {
448         const char *p;
449         gulong result;
450
451         p = header_value;
452
453         for (result = 0; g_ascii_isdigit (*p); p++)
454                 result = 10 * result + (*p - '0');
455         if (*p)
456                 return FALSE;
457
458         *number = result;
459
460         return TRUE;
461 }
462
463 static gboolean
464 set_content_length (HttpFileHandle *handle,
465                     const char *value)
466 {
467         gboolean result;
468         gulong size;
469
470         result = header_value_to_number (value, &size);
471         if (! result)
472                 return FALSE;
473
474         DEBUG_HTTP (("Expected size is %lu.", size));
475         handle->file_info->size = size;
476         handle->file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_SIZE;
477         return TRUE;
478 }
479
480 static char *
481 strip_semicolon (const char *value)
482 {
483         char *p;
484
485         p = strchr (value, ';');
486
487         if (p != NULL) {
488                 return g_strndup (value, p - value);
489         }
490         else {
491                 return g_strdup (value);
492         }
493 }
494
495 static gboolean
496 set_content_type (HttpFileHandle *handle,
497                   const char *value)
498 {
499         g_free (handle->file_info->mime_type);
500
501         handle->file_info->mime_type = strip_semicolon (value);
502         handle->file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
503         
504         return TRUE;
505 }
506
507 static gboolean
508 set_last_modified (HttpFileHandle *handle,
509                    const char *value)
510 {
511         time_t time;
512
513         if (! gnome_vfs_atotm (value, &time))
514                 return FALSE;
515
516         handle->file_info->mtime = time;
517         handle->file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MTIME;
518         return TRUE;
519 }
520
521 static gboolean
522 set_access_time (HttpFileHandle *handle,
523                  const char *value)
524 {
525         time_t time;
526
527         if (! gnome_vfs_atotm (value, &time))
528                 return FALSE;
529
530         handle->file_info->atime = time;
531         handle->file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_ATIME;
532         return TRUE;
533 }
534
535 struct _Header {
536         const char *name;
537         gboolean (* set_func) (HttpFileHandle *handle, const char *value);
538 };
539 typedef struct _Header Header;
540
541 static Header headers[] = {
542         { "Content-Length", set_content_length },
543         { "Content-Type", set_content_type },
544         { "Last-Modified", set_last_modified },
545         { "Date", set_access_time },
546         { NULL, NULL }
547 };
548
549 static const char *
550 check_header (const char *header,
551               const char *name)
552 {
553         const char *p, *q;
554
555         for (p = header, q = name; *p != '\0' && *q != '\0'; p++, q++) {
556                 if (g_ascii_tolower (*p) != g_ascii_tolower (*q))
557                         break;
558         }
559
560         if (*q != '\0' || *p != ':')
561                 return NULL;
562
563         p++;                    /* Skip ':'.  */
564         while (*p == ' ' || *p == '\t')
565                 p++;
566
567         return p;
568 }
569
570 static gboolean
571 parse_header (HttpFileHandle *handle,
572               const char *header)
573 {
574         guint i;
575
576         for (i = 0; headers[i].name != NULL; i++) {
577                 const char *value;
578
579                 value = check_header (header, headers[i].name);
580                 if (value != NULL)
581                         return (* headers[i].set_func) (handle, value);
582         }
583
584         /* Simply ignore headers we don't know.  */
585         return TRUE;
586 }
587
588 /* Header/status reading.  */
589
590 static GnomeVFSResult
591 get_header (GnomeVFSSocketBuffer *socket_buffer,
592             GString *s)
593 {
594         GnomeVFSResult result;
595         GnomeVFSFileSize bytes_read;
596         guint count;
597
598         ANALYZE_HTTP ("==> +get_header");
599
600         g_string_truncate (s, 0);
601
602         count = 0;
603         while (1) {
604                 char c;
605
606                 /* ANALYZE_HTTP ("==> +get_header read"); */
607                 result = gnome_vfs_socket_buffer_read (socket_buffer, &c, 1,
608                                 &bytes_read);
609                 /* ANALYZE_HTTP ("==> -get_header read"); */
610
611                 if (result != GNOME_VFS_OK) {
612                         return result;
613                 }
614                 if (bytes_read == 0) {
615                         return GNOME_VFS_ERROR_EOF;
616                 }
617
618                 if (c == '\n') {
619                         /* Handle continuation lines.  */
620                         if (count != 0 && (count != 1 || s->str[0] != '\r')) {
621                                 char next;
622
623                                 result = gnome_vfs_socket_buffer_peekc (
624                                                 socket_buffer, &next);
625                                 if (result != GNOME_VFS_OK) {
626                                         return result;
627                                 }
628                                 
629                                 if (next == '\t' || next == ' ') {
630                                         if (count > 0
631                                             && s->str[count - 1] == '\r')
632                                                 s->str[count - 1] = '\0';
633                                         continue;
634                                 }
635                         }
636
637                         if (count > 0 && s->str[count - 1] == '\r')
638                                 s->str[count - 1] = '\0';
639                         break;
640                 } else {
641                         g_string_append_c (s, c);
642                 }
643
644                 count++;
645         }
646         
647
648         ANALYZE_HTTP ("==> -get_header");
649
650         return GNOME_VFS_OK;
651 }
652
653 /* rename this function? */
654 static GnomeVFSResult
655 create_handle (GnomeVFSURI *uri,
656                GnomeVFSSocketBuffer *socket_buffer,
657                GnomeVFSContext *context,
658                /* OUT */ HttpFileHandle *handle)
659 {
660         GString *header_string;
661         GnomeVFSResult result;
662         guint server_status;
663
664         g_return_val_if_fail (handle != NULL, GNOME_VFS_ERROR_INTERNAL);
665
666         http_file_handle_new (handle, socket_buffer, uri);
667
668         header_string = g_string_new (NULL);
669
670         ANALYZE_HTTP ("==> +create_handle");
671
672         /* This is the status report string, which is the first header.  */
673         result = get_header (socket_buffer, header_string);
674         if (result != GNOME_VFS_OK) {
675                 goto error;
676         }
677
678         if (!parse_status (header_string->str, &server_status)) {
679                 /* An unparsable status line is fatal */
680                 result = GNOME_VFS_ERROR_GENERIC;
681                 goto error;
682         }
683
684         handle->server_status = server_status;
685
686         ANALYZE_HTTP ("==> +create_handle: fetching headers");
687
688         /* Header fetching loop.  */
689         for (;;) {
690                 result = get_header (socket_buffer, header_string);
691                 if (result != GNOME_VFS_OK) {
692                         break;
693                 }
694
695                 /* Empty header ends header section.  */
696                 if (header_string->str[0] == '\0') {
697                         break;
698                 }
699
700                 handle->response_headers = g_list_prepend (handle->response_headers, 
701                                                         g_strdup (header_string->str));
702
703                 /* We don't really care if we successfully parse the
704                  * header or not. It might be nice to tell someone we
705                  * found a header we can't parse, but it's not clear
706                  * who would be interested or how we tell them. In the
707                  * past we would return NOT_FOUND if any header could
708                  * not be parsed, but that seems wrong.
709                  */
710                 parse_header (handle, header_string->str);
711         }
712
713         invoke_callback_headers_received (handle);
714
715         ANALYZE_HTTP ("==> -create_handle: fetching headers");
716
717         if (result != GNOME_VFS_OK) {
718                 goto error;
719         }
720
721         if (! HTTP_20X (server_status) && !HTTP_REDIRECTED(server_status)) {
722                 result = http_status_to_vfs_result (server_status);
723                 goto error;
724         }
725
726         result = GNOME_VFS_OK;
727  error:
728         g_string_free (header_string, TRUE);
729
730         ANALYZE_HTTP ("==> -create_handle");
731         return result;
732 }
733
734 /*
735  * Here's how the gconf gnome-vfs HTTP proxy variables
736  * are intended to be used
737  *
738  * /system/http_proxy/use_http_proxy    
739  *      Type: boolean
740  *      If set to TRUE, the client should use an HTTP proxy to connect to all
741  *      servers (except those specified in the ignore_hosts key -- see below).
742  *      The proxy is specified in other gconf variables below.
743  *
744  * /system/http_proxy/host
745  *      Type: string
746  *      The hostname of the HTTP proxy this client should use.  If
747  *      use-http-proxy is TRUE, this should be set.  If it is not set, the
748  *      application should behave as if use-http-proxy is was set to FALSE.
749  *
750  * /system/http_proxy/port
751  *      Type: int
752  *      The port number on the HTTP proxy host that the client should connect to
753  *      If use_http_proxy and host are set but this is not set, the application
754  *      should use a default port value of 8080
755  *
756  * /system/http_proxy/authentication-user
757  *      Type: string
758  *      Username to pass to an authenticating HTTP proxy.
759  *
760  * /system/http_proxy/authentication_password
761  *      Type: string
762  *      Password to pass to an authenticating HTTP proxy.
763  *  
764  * /system/http_proxy/use-authentication
765  *      Type: boolean
766  *      TRUE if the client should pass http-proxy-authorization-user and
767  *      http-proxy-authorization-password an HTTP proxy
768  *
769  * /system/http_proxy/ignore_hosts
770  *      Type: list of strings
771  *      A list of hosts (hostnames, wildcard domains, IP addresses, and CIDR
772  *      network addresses) that should be accessed directly.
773  */
774
775 static void
776 construct_gl_http_proxy (gboolean use_proxy)
777 {
778         g_free (gl_http_proxy);
779         gl_http_proxy = NULL;
780
781         g_slist_foreach (gl_ignore_hosts, (GFunc) g_free, NULL);
782         g_slist_free (gl_ignore_hosts);
783         gl_ignore_hosts = NULL;
784         g_slist_foreach (gl_ignore_addrs, (GFunc) g_free, NULL);
785         g_slist_free (gl_ignore_addrs);
786         gl_ignore_addrs = NULL;
787
788         if (use_proxy) {
789                 char *proxy_host;
790                 int   proxy_port;
791                 GSList *ignore;
792
793                 proxy_host = gconf_client_get_string (gl_client, KEY_GCONF_HTTP_PROXY_HOST, NULL);
794                 proxy_port = gconf_client_get_int (gl_client, KEY_GCONF_HTTP_PROXY_PORT, NULL);
795
796                 if (proxy_host) {
797                         if (0 != proxy_port && 0xffff >= (unsigned) proxy_port) {
798                                 gl_http_proxy = g_strdup_printf ("%s:%u", proxy_host, (unsigned)proxy_port);
799                         } else {
800                                 gl_http_proxy = g_strdup_printf ("%s:%u", proxy_host, (unsigned)DEFAULT_HTTP_PROXY_PORT);
801                         }
802                         DEBUG_HTTP (("New HTTP proxy: '%s'", gl_http_proxy));
803                 } else {
804                         DEBUG_HTTP (("HTTP proxy unset"));
805                 }
806                 
807                 g_free (proxy_host);
808                 proxy_host = NULL;
809
810                 ignore = gconf_client_get_list (gl_client, KEY_GCONF_HTTP_PROXY_IGNORE_HOSTS, GCONF_VALUE_STRING, NULL);
811                 g_slist_foreach (ignore, (GFunc) parse_ignore_host, NULL);
812                 g_slist_foreach (ignore, (GFunc) g_free, NULL);
813                 g_slist_free (ignore);
814                 ignore = NULL;
815         }
816 }
817
818 static void
819 parse_ignore_host (gpointer data, gpointer user_data)
820 {
821         gchar *hostname, *input, *netmask;
822         gboolean ip_addr = FALSE, has_error = FALSE;
823         struct in_addr host, mask;
824 #ifdef ENABLE_IPV6
825         struct in6_addr host6, mask6;
826 #endif
827         ProxyHostAddr *elt;
828         gint i;
829
830         input = (gchar*) data;
831         elt = g_new0 (ProxyHostAddr, 1);
832         if ((netmask = strchr (input, '/')) != NULL) {
833                 hostname = g_strndup (input, netmask - input);
834                 ++netmask;
835         }
836         else {
837                 hostname = g_ascii_strdown (input, -1);
838         }
839         if (inet_pton (AF_INET, hostname, &host) > 0) {
840                 ip_addr = TRUE;
841                 elt->type = PROXY_IPv4;
842                 elt->addr.s_addr = host.s_addr;
843                 if (netmask) {
844                         gchar *endptr;
845                         gint width = strtol (netmask, &endptr, 10);
846
847                         if (*endptr != '\0' || width < 0 || width > 32) {
848                                 has_error = TRUE;
849                         }
850                         elt->mask.s_addr = htonl(~0 << width);
851                         elt->addr.s_addr &= mask.s_addr;
852                 }
853                 else {
854                         elt->mask.s_addr = 0xffffffff;
855                 }
856         }
857 #ifdef ENABLE_IPV6
858         else if (have_ipv6 () && inet_pton (AF_INET6, hostname, &host6) > 0) {
859                 ip_addr = TRUE;
860                 elt->type = PROXY_IPv6;
861                 for (i = 0; i < 16; ++i) {
862                         elt->addr6.s6_addr[i] = host6.s6_addr[i];
863                 }
864                 if (netmask) {
865                         gchar *endptr;
866                         gint width = strtol (netmask, &endptr, 10);
867
868                         if (*endptr != '\0' || width < 0 || width > 128) {
869                                 has_error = TRUE;
870                         }
871                         for (i = 0; i < 16; ++i) {
872                                 elt->mask6.s6_addr[i] = 0;
873                         }
874                         for (i=0; i < width/8; i++) {
875                                 elt->mask6.s6_addr[i] = 0xff;
876                         }
877                         elt->mask6.s6_addr[i] = (0xff << (8 - width % 8)) & 0xff;
878                         ipv6_network_addr (&elt->addr6, &mask6, &elt->addr6);
879                 }
880                 else {
881                         for (i = 0; i < 16; ++i) {
882                                 elt->mask6.s6_addr[i] = 0xff;
883                         }
884                 }
885         }
886 #endif
887
888         if (ip_addr) {
889                 if (!has_error) {
890                         gchar *dst = g_new0 (gchar, INET_ADDRSTRLEN);
891         
892                         gl_ignore_addrs = g_slist_append (gl_ignore_addrs, elt);
893                         DEBUG_HTTP (("Host %s/%s does not go through proxy.",
894                                         hostname,
895                                         inet_ntop(AF_INET, &elt->mask, dst, INET_ADDRSTRLEN)));
896                         g_free (dst);
897                 }
898         }
899         else {
900                 /* It is a hostname. */
901                 gl_ignore_hosts = g_slist_append (gl_ignore_hosts, hostname);
902                 DEBUG_HTTP (("Host %s does not go through proxy.", hostname));
903         }
904 }
905
906 static void
907 set_proxy_auth (gboolean use_proxy_auth)
908 {
909         char *auth_user;
910         char *auth_pw;
911
912         auth_user = gconf_client_get_string (gl_client, KEY_GCONF_HTTP_AUTH_USER, NULL);
913         auth_pw = gconf_client_get_string (gl_client, KEY_GCONF_HTTP_AUTH_PW, NULL);
914
915         if (use_proxy_auth) {
916                 proxy_set_authn (auth_user, auth_pw);
917                 DEBUG_HTTP (("New HTTP proxy auth user: '%s'", auth_user));
918         } else {
919                 proxy_unset_authn ();
920                 DEBUG_HTTP (("HTTP proxy auth unset"));
921         }
922
923         g_free (auth_user);
924         g_free (auth_pw);
925 }
926
927 /**
928  * sig_gconf_value_changed 
929  * GGconf notify function for when HTTP proxy GConf key has changed.
930  */
931 static void
932 notify_gconf_value_changed (GConfClient *client,
933                             guint        cnxn_id,
934                             GConfEntry  *entry,
935                             gpointer     data)
936 {
937         const char *key;
938
939         key = gconf_entry_get_key (entry);
940
941         if (strcmp (key, KEY_GCONF_USE_HTTP_PROXY) == 0
942             || strcmp (key, KEY_GCONF_HTTP_PROXY_IGNORE_HOSTS) == 0
943             || strcmp (key, KEY_GCONF_HTTP_PROXY_HOST) == 0
944             || strcmp (key, KEY_GCONF_HTTP_PROXY_PORT) == 0) {
945                 gboolean use_proxy_value;
946                 
947                 g_mutex_lock (gl_mutex);
948                 
949                 /* Check and see if we are using the proxy */
950                 use_proxy_value = gconf_client_get_bool (gl_client, KEY_GCONF_USE_HTTP_PROXY, NULL);
951                 construct_gl_http_proxy (use_proxy_value);
952                 
953                 g_mutex_unlock (gl_mutex);
954         } else if (strcmp (key, KEY_GCONF_HTTP_AUTH_USER) == 0
955             || strcmp (key, KEY_GCONF_HTTP_AUTH_PW) == 0
956             || strcmp (key, KEY_GCONF_HTTP_USE_AUTH) == 0) {
957                 gboolean use_proxy_auth;
958
959                 g_mutex_lock (gl_mutex);
960                 
961                 use_proxy_auth = gconf_client_get_bool (gl_client, KEY_GCONF_HTTP_USE_AUTH, NULL);
962                 set_proxy_auth (use_proxy_auth);
963
964                 g_mutex_unlock (gl_mutex);
965         }
966 }
967
968 /**
969  * host_port_from_string
970  * splits a <host>:<port> formatted string into its separate components
971  */
972 static gboolean
973 host_port_from_string (const char *http_proxy,
974                        char **p_proxy_host, 
975                        guint *p_proxy_port)
976 {
977         char *port_part;
978         
979         port_part = strchr (http_proxy, ':');
980         
981         if (port_part && '\0' != ++port_part && p_proxy_port) {
982                 *p_proxy_port = (guint) strtoul (port_part, NULL, 10);
983         } else if (p_proxy_port) {
984                 *p_proxy_port = DEFAULT_HTTP_PROXY_PORT;
985         }
986         
987         if (p_proxy_host) {
988                 if ( port_part != http_proxy ) {
989                         *p_proxy_host = g_strndup (http_proxy, port_part - http_proxy - 1);
990                 } else {
991                         return FALSE;
992                 }
993         }
994
995         return TRUE;
996 }
997
998 /* FIXME: should be done using AC_REPLACE_FUNCS */
999 #ifndef HAVE_INET_PTON
1000 static int
1001 inet_pton(int af, const char *hostname, void *pton)
1002 {
1003         struct in_addr in;
1004         if (!inet_aton(hostname, &in))
1005             return 0;
1006         memcpy(pton, &in, sizeof(in));
1007         return 1;
1008 }
1009 #endif
1010
1011 #ifdef ENABLE_IPV6
1012 static void
1013 ipv6_network_addr (const struct in6_addr *addr, const struct in6_addr *mask, struct in6_addr *res)
1014 {
1015         gint i;
1016
1017         for (i = 0; i < 16; ++i) {
1018                 res->s6_addr[i] = addr->s6_addr[i] & mask->s6_addr[i];
1019         }
1020 }
1021 #endif
1022
1023 static gboolean
1024 proxy_should_for_hostname (const char *hostname)
1025 {
1026 #ifdef ENABLE_IPV6
1027         struct in6_addr in6, net6;
1028 #endif
1029         struct in_addr in;
1030         GSList *elt;
1031         ProxyHostAddr *addr;
1032
1033
1034         /* IPv4 address */
1035         if (inet_pton (AF_INET, hostname, &in) > 0) {
1036                 for (elt = gl_ignore_addrs; elt; elt = g_slist_next (elt)) {
1037                         addr = (ProxyHostAddr*) (elt->data);
1038                         if (addr->type == PROXY_IPv4
1039                             && (in.s_addr & addr->mask.s_addr) == addr->addr.s_addr) {
1040                                 DEBUG_HTTP (("Host %s using direct connection.", hostname)); 
1041                                 return FALSE;
1042                         }
1043                 }
1044         }
1045 #ifdef ENABLE_IPV6
1046         else if (have_ipv6 () && inet_pton (AF_INET6, hostname, &in6)) {
1047                 for (elt = gl_ignore_addrs; elt; elt = g_slist_next (elt)) {
1048                         addr = (ProxyHostAddr*) (elt->data);
1049                         ipv6_network_addr (&in6, &addr->mask6, &net6);
1050                         if (addr->type == PROXY_IPv6 
1051                             && IN6_ARE_ADDR_EQUAL (&net6, &addr->addr6)) {
1052                                 DEBUG_HTTP (("Host %s using direct connection.", hostname)); 
1053                                 return FALSE;
1054                         }
1055                         /* Handle IPv6-wrapped IPv4 addresses. */
1056                         else if (addr->type == PROXY_IPv4
1057                                  && IN6_IS_ADDR_V4MAPPED (&net6)) {
1058                                 guint32 v4addr;
1059
1060                                 v4addr = net6.s6_addr[12] << 24 | net6.s6_addr[13] << 16 | net6.s6_addr[14] << 8 | net6.s6_addr[15];
1061                                 if ((v4addr & addr->mask.s_addr) != addr->addr.s_addr) {
1062                                         DEBUG_HTTP (("Host %s using direct connection.", hostname)); 
1063                                         return FALSE;
1064                                 }
1065                         }
1066                 }
1067         }
1068 #endif
1069         /* All hostnames (foo.bar.com) -- independent of IPv4 or IPv6 */
1070
1071         /* If there are IPv6 addresses in the ignore_hosts list but we do not
1072          * have IPv6 available at runtime, then those addresses will also fall
1073          * through to here (and harmlessly fail to match). */
1074         else {
1075                 gchar *hn = g_ascii_strdown (hostname, -1);
1076
1077                 for (elt = gl_ignore_hosts; elt; elt = g_slist_next (elt)) {
1078                         if (*(gchar*) (elt->data) == '*' ) {
1079                                 if (g_str_has_suffix (hn,
1080                                                 (gchar*) (elt->data) + 1)) {
1081                                         DEBUG_HTTP (("Host %s using direct connection.", hn));
1082                                         g_free (hn);
1083                                         return FALSE;
1084                                 }
1085                         }
1086                         else if (strcmp (hn, elt->data) == 0) {
1087                                 DEBUG_HTTP (("Host %s using direct connection.", hn));
1088                                 g_free (hn);
1089                                 return FALSE;
1090                         }
1091                 }
1092         }
1093
1094         return TRUE;
1095 }
1096
1097 static char *
1098 proxy_get_authn_header_for_uri_nolock (GnomeVFSURI * uri)
1099 {
1100         char * ret;
1101
1102         ret = NULL;
1103
1104         /* FIXME this needs to be atomic */     
1105         if (gl_http_proxy_auth != NULL) {
1106                 ret = g_strdup_printf ("Proxy-Authorization: Basic %s\r\n", gl_http_proxy_auth);
1107         }
1108
1109         return ret;
1110 }
1111
1112 static char *
1113 proxy_get_authn_header_for_uri (GnomeVFSURI * uri)
1114 {
1115         char * ret;
1116
1117         g_mutex_lock (gl_mutex);
1118
1119         ret = proxy_get_authn_header_for_uri_nolock (uri);
1120
1121         g_mutex_unlock (gl_mutex);
1122         
1123         return ret;
1124 }
1125
1126 /**
1127  * proxy_for_uri
1128  * Retrives an appropriate HTTP proxy for a given toplevel uri
1129  * Currently, only a single HTTP proxy is implemented (there's no way for
1130  * specifying non-proxy domain names's).  Returns FALSE if the connect should
1131  * take place directly
1132  */
1133 static gboolean
1134 proxy_for_uri (
1135         GnomeVFSToplevelURI * toplevel_uri,
1136         gchar **p_proxy_host,           /* Callee must free */
1137         guint *p_proxy_port)            /* Callee must free */
1138 {
1139         gboolean ret;
1140         
1141         ret = proxy_should_for_hostname (toplevel_uri->host_name);
1142
1143         g_mutex_lock (gl_mutex);
1144
1145         if (ret && gl_http_proxy != NULL) {
1146                 ret = host_port_from_string (gl_http_proxy, p_proxy_host, p_proxy_port);
1147         } else {
1148                 p_proxy_host = NULL;
1149                 p_proxy_port = NULL;
1150                 ret = FALSE;
1151         }
1152
1153         g_mutex_unlock (gl_mutex);
1154
1155         return ret;
1156 }
1157
1158 static void
1159 proxy_set_authn (const char *username, const char *password)
1160 {
1161         char * credentials;
1162
1163         g_free (gl_http_proxy_auth);
1164         gl_http_proxy_auth = NULL;
1165
1166         credentials = g_strdup_printf ("%s:%s", 
1167                         username == NULL ? "" : username, 
1168                         password == NULL ? "" : password);
1169
1170         gl_http_proxy_auth = http_util_base64 (credentials);
1171
1172         g_free (credentials);
1173 }
1174
1175 static void
1176 proxy_unset_authn (void)
1177 {
1178         g_free (gl_http_proxy_auth);
1179         gl_http_proxy_auth = NULL;
1180 }
1181
1182
1183 static GnomeVFSResult
1184 https_proxy (GnomeVFSSocket **socket_return,
1185              gchar *proxy_host,
1186              gint proxy_port,
1187              gchar *server_host,
1188              gint server_port)
1189 {
1190         /* use CONNECT to do https proxying. It goes something like this:
1191          * >CONNECT server:port HTTP/1.0
1192          * >
1193          * <HTTP/1.0 200 Connection-established
1194          * <Proxy-agent: Apache/1.3.19 (Unix) Debian/GNU
1195          * <
1196          * and then we've got an open connection.
1197          *
1198          * So we sent "CONNECT server:port HTTP/1.0\r\n\r\n"
1199          * Check the HTTP status.
1200          * Wait for "\r\n\r\n"
1201          * Start doing the SSL dance.
1202          */
1203
1204         GnomeVFSResult result;
1205         GnomeVFSInetConnection *http_connection;
1206         GnomeVFSSocket *http_socket;
1207         GnomeVFSSocket *https_socket;
1208         GnomeVFSSSL *ssl;
1209         char *buffer;
1210         GnomeVFSFileSize bytes;
1211         guint status_code;
1212         gint fd;
1213
1214         result = gnome_vfs_inet_connection_create (&http_connection, 
1215                         proxy_host, proxy_port, NULL);
1216
1217         if (result != GNOME_VFS_OK) {
1218                 return result;
1219         }
1220
1221         fd = gnome_vfs_inet_connection_get_fd (http_connection);
1222
1223         http_socket = gnome_vfs_inet_connection_to_socket (http_connection);
1224
1225         buffer = g_strdup_printf ("CONNECT %s:%d HTTP/1.0\r\n\r\n",
1226                         server_host, server_port);
1227         result = gnome_vfs_socket_write (http_socket, buffer, strlen(buffer),
1228                         &bytes);
1229         g_free (buffer);
1230
1231         if (result != GNOME_VFS_OK) {
1232                 gnome_vfs_socket_close (http_socket);
1233                 return result;
1234         }
1235
1236         buffer = proxy_get_authn_header_for_uri (NULL); /* FIXME need uri */
1237         if (buffer != NULL) {
1238                 result = gnome_vfs_socket_write (http_socket, buffer, 
1239                                 strlen(buffer), &bytes);
1240                 g_free (buffer);
1241         }
1242
1243         if (result != GNOME_VFS_OK) {
1244                 gnome_vfs_socket_close (http_socket);
1245                 return result;
1246         }
1247
1248         bytes = 8192;
1249         buffer = g_malloc0 (bytes);
1250
1251         result = gnome_vfs_socket_read (http_socket, buffer, bytes-1, &bytes);
1252
1253         if (result != GNOME_VFS_OK) {
1254                 gnome_vfs_socket_close (http_socket);
1255                 g_free (buffer);
1256                 return result;
1257         }
1258
1259         if (!parse_status (buffer, &status_code)) {
1260                 gnome_vfs_socket_close (http_socket);
1261                 g_free (buffer);
1262                 return GNOME_VFS_ERROR_PROTOCOL_ERROR;
1263         }
1264
1265         result = http_status_to_vfs_result (status_code);
1266
1267         if (result != GNOME_VFS_OK) {
1268                 gnome_vfs_socket_close (http_socket);
1269                 g_free (buffer);
1270                 return result;
1271         }
1272
1273         /* okay - at this point we've read some stuff from the socket.. */
1274         /* FIXME: for now we'll assume thats all the headers and nothing but. */
1275
1276         g_free (buffer);
1277
1278         result = gnome_vfs_ssl_create_from_fd (&ssl, fd);
1279
1280         if (result != GNOME_VFS_OK) {
1281                 gnome_vfs_socket_close (http_socket);
1282                 return result;
1283         }
1284
1285         https_socket = gnome_vfs_ssl_to_socket (ssl);
1286
1287         *socket_return = https_socket;
1288
1289         return GNOME_VFS_OK;
1290 }
1291
1292
1293
1294 static GnomeVFSResult
1295 connect_to_uri (
1296         GnomeVFSToplevelURI *toplevel_uri, 
1297         /* OUT */ GnomeVFSSocketBuffer **p_socket_buffer,
1298         /* OUT */ gboolean * p_proxy_connect)
1299 {
1300         guint host_port;
1301         char *proxy_host;
1302         guint proxy_port;
1303         GnomeVFSResult result;
1304         GnomeVFSCancellation * cancellation;
1305         GnomeVFSInetConnection *connection;
1306         GnomeVFSSSL *ssl;
1307         GnomeVFSSocket *socket;
1308         gboolean https = FALSE;
1309
1310         cancellation = gnome_vfs_context_get_cancellation (
1311                                 gnome_vfs_context_peek_current ());
1312
1313         g_return_val_if_fail (p_socket_buffer != NULL, GNOME_VFS_ERROR_INTERNAL);
1314         g_return_val_if_fail (p_proxy_connect != NULL, GNOME_VFS_ERROR_INTERNAL);
1315         g_return_val_if_fail (toplevel_uri != NULL, GNOME_VFS_ERROR_INTERNAL);
1316
1317         if (!g_ascii_strcasecmp (gnome_vfs_uri_get_scheme (&toplevel_uri->uri), 
1318                                 "https")) {
1319                 if (!gnome_vfs_ssl_enabled ()) {
1320                         return GNOME_VFS_ERROR_NOT_SUPPORTED;
1321                 }
1322                 https = TRUE;
1323         }
1324
1325         if (toplevel_uri->host_port == 0) {
1326                 if (https) {
1327                         host_port = DEFAULT_HTTPS_PORT;
1328                 } else {
1329                         host_port = DEFAULT_HTTP_PORT;
1330                 }
1331         } else {
1332                 host_port = toplevel_uri->host_port;
1333         }
1334
1335         ANALYZE_HTTP ("==> +Making connection");
1336
1337         if (toplevel_uri->host_name == NULL) {
1338                 result = GNOME_VFS_ERROR_INVALID_URI;
1339                 goto error;
1340         }
1341
1342         if (proxy_for_uri (toplevel_uri, &proxy_host, &proxy_port)) {
1343                 if (https) {
1344                         *p_proxy_connect = FALSE;
1345
1346                         result = https_proxy (&socket, proxy_host, proxy_port,
1347                                         toplevel_uri->host_name, host_port);
1348
1349                         g_free (proxy_host);
1350                         proxy_host = NULL;
1351
1352                         if (result != GNOME_VFS_OK) {
1353                                 return result;
1354                         }
1355
1356                 } else {
1357                         *p_proxy_connect = TRUE;
1358
1359                         result = gnome_vfs_inet_connection_create (&connection,
1360                                                         proxy_host,
1361                                                         proxy_port, 
1362                                                         cancellation);
1363                         if (result != GNOME_VFS_OK) {
1364                                 return result;
1365                         }
1366                         socket = gnome_vfs_inet_connection_to_socket 
1367                                                                 (connection);
1368
1369                         g_free (proxy_host);
1370                         proxy_host = NULL;
1371                 }
1372         } else {
1373                 *p_proxy_connect = FALSE;
1374
1375                 if (https) {
1376                         result = gnome_vfs_ssl_create (&ssl, 
1377                                         toplevel_uri->host_name, host_port);
1378
1379                         if (result != GNOME_VFS_OK) {
1380                                 return result;
1381                         }
1382                         socket = gnome_vfs_ssl_to_socket (ssl);
1383                 } else {
1384                         result = gnome_vfs_inet_connection_create (&connection,
1385                                                    toplevel_uri->host_name,
1386                                                    host_port,
1387                                                    cancellation);
1388                         if (result != GNOME_VFS_OK) {
1389                                 return result;
1390                         }
1391                         socket = gnome_vfs_inet_connection_to_socket 
1392                                                                 (connection);
1393                 }
1394         }
1395
1396         *p_socket_buffer = gnome_vfs_socket_buffer_new (socket);
1397
1398         if (*p_socket_buffer == NULL) {
1399                 gnome_vfs_socket_close (socket);
1400                 return GNOME_VFS_ERROR_INTERNAL;
1401         }
1402
1403         ANALYZE_HTTP ("==> -Making connection");
1404
1405 error:
1406         return result;
1407 }
1408
1409 static GString *
1410 build_request (const char * method, GnomeVFSToplevelURI * toplevel_uri, gboolean proxy_connect)
1411 {
1412         gchar *uri_string = NULL;
1413         GString *request;
1414         GnomeVFSURI *uri;
1415         gchar *user_agent;
1416
1417         uri = (GnomeVFSURI *)toplevel_uri;
1418
1419         if (proxy_connect) {
1420                 uri_string = gnome_vfs_uri_to_string (uri,
1421                                                       GNOME_VFS_URI_HIDE_USER_NAME
1422                                                       | GNOME_VFS_URI_HIDE_PASSWORD);
1423
1424         } else {
1425                 uri_string = gnome_vfs_uri_to_string (uri,
1426                                                       GNOME_VFS_URI_HIDE_USER_NAME
1427                                                       | GNOME_VFS_URI_HIDE_PASSWORD
1428                                                       | GNOME_VFS_URI_HIDE_HOST_NAME
1429                                                       | GNOME_VFS_URI_HIDE_HOST_PORT
1430                                                       | GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD);
1431         }
1432
1433         /* Request line.  */
1434         request = g_string_new ("");
1435
1436         g_string_append_printf (request, "%s %s%s HTTP/1.0\r\n", method, uri_string,
1437                                 gnome_vfs_uri_get_path (uri)[0] == '\0' ? "/" : "" );
1438
1439         DEBUG_HTTP (("-->Making request '%s %s'", method, uri_string));
1440         
1441         g_free (uri_string);
1442         uri_string = NULL;
1443
1444         /* `Host:' header.  */
1445         if(toplevel_uri->host_port && toplevel_uri->host_port != 0) {
1446                 g_string_append_printf (request, "Host: %s:%d\r\n",
1447                                         toplevel_uri->host_name, toplevel_uri->host_port);
1448         } else {
1449                 g_string_append_printf (request, "Host: %s:80\r\n",
1450                                         toplevel_uri->host_name);
1451         }
1452
1453         /* `Accept:' header.  */
1454         g_string_append (request, "Accept: */*\r\n");
1455
1456         /* `User-Agent:' header.  */
1457         user_agent = getenv (CUSTOM_USER_AGENT_VARIABLE);
1458
1459         if(user_agent == NULL) {
1460                 user_agent = USER_AGENT_STRING;
1461         }
1462
1463         g_string_append_printf (request, "User-Agent: %s\r\n", user_agent);
1464
1465         return request;
1466 }
1467
1468 static GnomeVFSResult
1469 xmit_request (GnomeVFSSocketBuffer *socket_buffer, 
1470               GString *request, 
1471               GByteArray *data)
1472 {
1473         GnomeVFSResult result;
1474         GnomeVFSFileSize bytes_written;
1475
1476         ANALYZE_HTTP ("==> Writing request and header");
1477
1478         /* Transmit the request headers.  */
1479         result = gnome_vfs_socket_buffer_write (socket_buffer, request->str, 
1480                         request->len, &bytes_written);
1481
1482         if (result != GNOME_VFS_OK) {
1483                 goto error;
1484         }
1485
1486         /* Transmit the body */
1487         if(data && data->data) {
1488                 ANALYZE_HTTP ("==> Writing data");
1489                 
1490                 result = gnome_vfs_socket_buffer_write (socket_buffer, 
1491                                 data->data, data->len, &bytes_written);
1492         }
1493
1494         if (result != GNOME_VFS_OK) {
1495                 goto error;
1496         }
1497
1498         result = gnome_vfs_socket_buffer_flush (socket_buffer); 
1499
1500 error:
1501         return result;
1502 }
1503
1504 static GnomeVFSResult
1505 make_request (HttpFileHandle *handle,
1506               GnomeVFSURI *uri,
1507               const gchar *method,
1508               GByteArray *data,
1509               gchar *extra_headers,
1510               GnomeVFSContext *context)
1511 {
1512         GnomeVFSSocketBuffer *socket_buffer;
1513         GnomeVFSResult result;
1514         GnomeVFSToplevelURI *toplevel_uri;
1515         GString *request;
1516         gboolean proxy_connect;
1517         char *authn_header_request;
1518         char *authn_header_proxy;
1519         gboolean handle_valid = FALSE;
1520
1521         g_return_val_if_fail (handle != NULL, GNOME_VFS_ERROR_INTERNAL);
1522
1523         ANALYZE_HTTP ("==> +make_request");
1524
1525         request                 = NULL;
1526         proxy_connect           = FALSE;
1527         authn_header_request    = NULL;
1528         authn_header_proxy      = NULL;
1529         
1530         toplevel_uri = (GnomeVFSToplevelURI *) uri;
1531
1532         for (;;) {
1533                 GList *list;
1534
1535                 g_free (authn_header_request);
1536                 g_free (authn_header_proxy);
1537
1538                 socket_buffer = NULL;
1539                 result = connect_to_uri (toplevel_uri, &socket_buffer, 
1540                                 &proxy_connect);
1541                 
1542                 if (result != GNOME_VFS_OK) {
1543                         break;
1544                 }
1545                 
1546                 request = build_request (method, toplevel_uri, proxy_connect);
1547
1548                 authn_header_request = http_authn_get_header_for_uri (uri);
1549
1550                 if (authn_header_request != NULL) {
1551                         g_string_append (request, authn_header_request);
1552                 }
1553
1554                 if (proxy_connect) {
1555                         authn_header_proxy = proxy_get_authn_header_for_uri (uri);
1556
1557                         if (authn_header_proxy != NULL) {
1558                                 g_string_append (request, authn_header_proxy);
1559                         }
1560                 }
1561                 
1562                 /* `Content-Length' header.  */
1563                 if (data != NULL) {
1564                         g_string_append_printf (request, "Content-Length: %d\r\n", data->len);
1565                 }
1566                 
1567                 /* Extra headers. */
1568                 if (extra_headers != NULL) {
1569                         g_string_append (request, extra_headers);
1570                 }
1571
1572                 /* Extra headers from user */
1573                 list = NULL;
1574
1575                 if (invoke_callback_send_additional_headers (uri, &list)) {
1576                         GList *i;
1577
1578                         for (i = list; i; i = i->next) {
1579                                 g_string_append (request, i->data);
1580                                 g_free (i->data);
1581                                 i->data = NULL;
1582                         }
1583
1584                         g_list_free (list);
1585                 }
1586
1587                 /* Empty line ends header section.  */
1588                 g_string_append (request, "\r\n");
1589
1590                 result = xmit_request (socket_buffer, request, data);
1591                 g_string_free (request, TRUE);
1592                 request = NULL;
1593
1594                 if (result != GNOME_VFS_OK) {
1595                         break;
1596                 }
1597
1598                 /* Read the headers and create our internal HTTP file handle.  */
1599                 result = create_handle (uri, socket_buffer, context, handle);
1600                 handle_valid = TRUE;
1601
1602                 if (result == GNOME_VFS_OK) {
1603                         socket_buffer = NULL;
1604                         break;
1605                 }
1606                 if (handle->server_status == HTTP_STATUS_UNAUTHORIZED) {
1607                         if (! check_authn_retry_request (handle, AuthnHeader_WWW, authn_header_request)) {
1608                                 break;
1609                         }
1610                 } else if (handle->server_status == HTTP_STATUS_PROXY_AUTH_REQUIRED) {
1611                         if (! check_authn_retry_request (handle, AuthnHeader_WWW, authn_header_proxy)) {
1612                                 break;
1613                         }
1614                 } else {
1615                         break;
1616                 }
1617                 http_file_handle_destroy (handle);
1618                 handle_valid = FALSE;
1619         }
1620
1621         g_free (authn_header_request);
1622         g_free (authn_header_proxy);
1623
1624         if (result != GNOME_VFS_OK && handle_valid) {
1625                 http_file_handle_destroy (handle);
1626                 handle_valid = FALSE;
1627         }
1628
1629         if (request != NULL) {
1630                 g_string_free (request, TRUE);
1631         }
1632         
1633         if (socket_buffer != NULL) {
1634                 gnome_vfs_socket_buffer_destroy (socket_buffer, TRUE);
1635         }
1636         
1637         ANALYZE_HTTP ("==> -make_request");
1638         return result;
1639 }
1640
1641 static void
1642 http_handle_close (HttpFileHandle *handle, 
1643                    GnomeVFSContext *context)
1644 {
1645         ANALYZE_HTTP ("==> +http_handle_close");
1646         
1647         if (handle != NULL) {
1648                 if (handle->socket_buffer) {
1649                         gnome_vfs_socket_buffer_flush (handle->socket_buffer);
1650                         gnome_vfs_socket_buffer_destroy (handle->socket_buffer,
1651                                                          TRUE);
1652                         handle->socket_buffer = NULL;
1653                 }
1654
1655                 http_file_handle_destroy (handle);
1656         }
1657         
1658         ANALYZE_HTTP ("==> -http_handle_close");
1659 }
1660
1661 static GnomeVFSResult
1662 do_open (GnomeVFSMethod *method,
1663          GnomeVFSMethodHandle **method_handle,
1664          GnomeVFSURI *uri,
1665          GnomeVFSOpenMode mode,
1666          GnomeVFSContext *context)
1667 {
1668         HttpFileHandle *handle;
1669         GnomeVFSResult result = GNOME_VFS_OK;
1670         
1671         g_return_val_if_fail (uri->parent == NULL, GNOME_VFS_ERROR_INVALID_URI);
1672         g_return_val_if_fail (!(mode & GNOME_VFS_OPEN_READ && 
1673                                 mode & GNOME_VFS_OPEN_WRITE),
1674                               GNOME_VFS_ERROR_INVALID_OPEN_MODE);
1675
1676         ANALYZE_HTTP ("==> +do_open");
1677         DEBUG_HTTP (("+Open URI: '%s' mode:'%c'", gnome_vfs_uri_to_string(uri, 0), 
1678                      mode & GNOME_VFS_OPEN_READ ? 'R' : 'W'));
1679
1680         handle = g_new (HttpFileHandle, 1);
1681         if (mode & GNOME_VFS_OPEN_READ) {
1682                 result = make_request (handle, uri, "GET", NULL, NULL,
1683                                        context);
1684         } else {
1685                 http_file_handle_new(handle, NULL, uri); /* shrug */
1686         }
1687         if (result == GNOME_VFS_OK) {
1688                 *method_handle = (GnomeVFSMethodHandle *) handle;
1689         } else {
1690                 *method_handle = NULL;
1691         }
1692
1693         DEBUG_HTTP (("-Open (%d) handle:0x%08x", result, (unsigned int)handle));
1694         ANALYZE_HTTP ("==> -do_open");
1695         
1696         return result;
1697 }
1698
1699 static GnomeVFSResult
1700 do_create (GnomeVFSMethod *method,
1701            GnomeVFSMethodHandle **method_handle,
1702            GnomeVFSURI *uri,
1703            GnomeVFSOpenMode mode,
1704            gboolean exclusive,
1705            guint perm,
1706            GnomeVFSContext *context)
1707 {
1708         /* try to write a zero length file - this appears to be the 
1709          * only reliable way of testing if a put will succeed. 
1710          * Xythos can apparently tell us if we have write permission by
1711          * playing with LOCK, but mod_dav cannot. */
1712         HttpFileHandle *handle;
1713         GnomeVFSResult result;
1714         GByteArray *bytes = g_byte_array_new();
1715         
1716         ANALYZE_HTTP ("==> +do_create");
1717         DEBUG_HTTP (("+Create URI: '%s'", gnome_vfs_uri_get_path (uri)));
1718
1719         http_cache_invalidate_uri_parent (uri);
1720
1721         /* Don't ignore exclusive; it should check first whether
1722            the file exists, since the http protocol default is to 
1723            overwrite by default */
1724         /* FIXME we've stopped using HEAD -- we should use GET instead  */
1725         /* FIXME we should check the cache here */
1726         if (exclusive) {
1727                 
1728                 ANALYZE_HTTP ("==> Checking to see if file exists");
1729                 
1730                 handle = g_new (HttpFileHandle, 1);
1731                 result = make_request (handle, uri, "HEAD", NULL, NULL,
1732                                        context);
1733                 http_handle_close (handle, context);
1734                 g_free (handle);
1735                 
1736                 if (result != GNOME_VFS_OK &&
1737                     result != GNOME_VFS_ERROR_NOT_FOUND) {
1738                         return result;
1739                 }
1740                 if (result == GNOME_VFS_OK) {
1741                         return GNOME_VFS_ERROR_FILE_EXISTS;
1742                 }
1743         }
1744         
1745         ANALYZE_HTTP ("==> Creating initial file");
1746         
1747         handle = g_new (HttpFileHandle, 1);
1748         result = make_request (handle, uri, "PUT", bytes, NULL, context);
1749         http_handle_close(handle, context);
1750         g_free (handle);
1751         
1752         if (result != GNOME_VFS_OK) {
1753                 /* the PUT failed */
1754                 
1755                 /* FIXME bugzilla.gnome.org 45131
1756                  * If you PUT a file with an invalid name to Xythos, it 
1757                  * returns a 403 Forbidden, which is different from the behaviour
1758                  * in MKCOL or MOVE.  Unfortunately, it is not possible to discern whether 
1759                  * that 403 Forbidden is being returned because of invalid characters in the name
1760                  * or because of permissions problems
1761                  */  
1762
1763                 if (result == GNOME_VFS_ERROR_NOT_FOUND) {
1764                         result = resolve_409 (method, uri, context);
1765                 }
1766
1767                 return result;
1768         }
1769
1770         /* clean up */
1771         g_byte_array_free (bytes, TRUE);
1772         
1773         /* FIXME bugzilla.gnome.org 41159: do we need to do something more intelligent here? */
1774         result = do_open (method, method_handle, uri, GNOME_VFS_OPEN_WRITE, context);
1775
1776         DEBUG_HTTP (("-Create (%d) handle:0x%08x", result, (unsigned int)handle));
1777         ANALYZE_HTTP ("==> -do_create");
1778
1779         return result;
1780 }
1781
1782 static GnomeVFSResult
1783 do_close (GnomeVFSMethod *method,
1784           GnomeVFSMethodHandle *method_handle,
1785           GnomeVFSContext *context)
1786 {
1787         HttpFileHandle *old_handle;
1788         HttpFileHandle *new_handle;
1789         GnomeVFSResult result;
1790         
1791         ANALYZE_HTTP ("==> +do_close");
1792         DEBUG_HTTP (("+Close handle:0x%08x", (unsigned int)method_handle));
1793
1794         old_handle = (HttpFileHandle *) method_handle;
1795         
1796         /* if the handle was opened in write mode then:
1797          * 1) there won't be a connection open, and
1798          * 2) there will be data to_be_written...
1799          */
1800         if (old_handle->to_be_written != NULL) {
1801                 GnomeVFSURI *uri = old_handle->uri;
1802                 GByteArray *bytes = old_handle->to_be_written;
1803                 GnomeVFSMimeSniffBuffer *sniff_buffer;
1804                 char *extraheader = NULL;
1805                 const char *mime_type = NULL;
1806                 
1807                 sniff_buffer = 
1808                         gnome_vfs_mime_sniff_buffer_new_from_existing_data (bytes->data, 
1809                                                                             bytes->len);
1810
1811                 if (sniff_buffer != NULL) {
1812                         mime_type = 
1813                                 gnome_vfs_get_mime_type_for_buffer (
1814                                                 sniff_buffer);
1815                         if (mime_type != NULL) {
1816                                 extraheader = g_strdup_printf(
1817                                                 "Content-type: %s\r\n", 
1818                                                 mime_type);
1819                         }
1820                         gnome_vfs_mime_sniff_buffer_free (sniff_buffer);
1821
1822                 }
1823
1824                 http_cache_invalidate_uri (uri);
1825
1826                 ANALYZE_HTTP ("==> doing PUT");
1827                 new_handle = g_new (HttpFileHandle, 1);
1828                 result = make_request (new_handle, uri, "PUT", bytes, 
1829                                        extraheader, context);
1830                 g_free (extraheader);
1831                 http_handle_close (new_handle, context);
1832                 g_free (new_handle);
1833         } else {
1834                 result = GNOME_VFS_OK;
1835         }
1836
1837         http_handle_close (old_handle, context);
1838         g_free (old_handle);
1839         
1840         DEBUG_HTTP (("-Close (%d)", result));
1841         ANALYZE_HTTP ("==> -do_close");
1842         
1843         return result;
1844 }
1845         
1846 static GnomeVFSResult
1847 do_write (GnomeVFSMethod *method,
1848           GnomeVFSMethodHandle *method_handle,
1849           gconstpointer buffer,
1850           GnomeVFSFileSize num_bytes,
1851           GnomeVFSFileSize *bytes_written,
1852           GnomeVFSContext *context)
1853 {
1854         HttpFileHandle *handle;
1855
1856         DEBUG_HTTP (("+Write handle:0x%08x", (unsigned int)method_handle));
1857
1858         handle = (HttpFileHandle *) method_handle;
1859
1860         if (handle->offset != 0)
1861                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
1862
1863         if(handle->to_be_written == NULL) {
1864                 handle->to_be_written = g_byte_array_new();
1865         }
1866         handle->to_be_written = g_byte_array_append(handle->to_be_written, buffer, num_bytes);
1867         *bytes_written = num_bytes;
1868         
1869         DEBUG_HTTP (("-Write (0)"));
1870         
1871         return GNOME_VFS_OK;
1872 }
1873
1874
1875 static GnomeVFSResult
1876 do_read (GnomeVFSMethod *method,
1877          GnomeVFSMethodHandle *method_handle,
1878          gpointer buffer,
1879          GnomeVFSFileSize num_bytes,
1880          GnomeVFSFileSize *bytes_read,
1881          GnomeVFSContext *context)
1882 {
1883         HttpFileHandle *handle;
1884         GnomeVFSResult result;
1885
1886         ANALYZE_HTTP ("==> +do_read");
1887         DEBUG_HTTP (("+Read handle=0x%08x", (unsigned int) method_handle));
1888
1889         handle = (HttpFileHandle *) method_handle;
1890
1891         if (handle->file_info->flags & GNOME_VFS_FILE_INFO_FIELDS_SIZE) {
1892                 GnomeVFSFileSize max_bytes;
1893
1894                 max_bytes = MAX (0, handle->file_info->size - handle->offset);
1895                 num_bytes = MIN (max_bytes, num_bytes);
1896         }
1897
1898         if (!num_bytes) {
1899                 *bytes_read = 0;
1900                 return GNOME_VFS_ERROR_EOF;
1901         }
1902
1903         if (1
1904             && handle->offset >  handle->socket_buffer_offset
1905             && handle->offset <= handle->socket_buffer_offset+MAX_BUFFER_SEEK_SKIP_READ) {
1906 static char drop_buffer[0x1000];
1907 GnomeVFSFileSize bytes, bytes_read;
1908 GnomeVFSResult result;
1909
1910                 while ((bytes=MIN(sizeof(drop_buffer), handle->offset - handle->socket_buffer_offset))) {
1911                         result = gnome_vfs_socket_buffer_read (handle->socket_buffer, drop_buffer, 
1912                                         bytes, &bytes_read);
1913                         if (result != GNOME_VFS_OK)
1914                                 return result;
1915                         handle->socket_buffer_offset += bytes_read;
1916                 }
1917         }
1918
1919         if (handle->offset != handle->socket_buffer_offset) {
1920                 GnomeVFSURI *uri = handle->uri;
1921                 gchar *extra_headers;
1922                 GnomeVFSFileOffset offset_save;
1923
1924                 offset_save = handle->offset;
1925                 gnome_vfs_uri_ref(uri);
1926                 http_handle_close (handle, context);
1927                 extra_headers = g_strdup_printf("Range: bytes=%" G_GINT64_FORMAT "-\r\n",(gint64)handle->offset);
1928                 result = make_request (handle, uri, "GET", NULL, extra_headers,
1929                                        context);
1930                 g_free (extra_headers);
1931                 gnome_vfs_uri_unref(uri);
1932                 handle->offset = offset_save;
1933                 if (result != GNOME_VFS_OK) {
1934                         /* FIXME: 'method_handle' is now broken! */
1935                         memset(handle, 0, sizeof (*handle));
1936                         return result;
1937                 }
1938                 handle->socket_buffer_offset = handle->offset;
1939         }
1940
1941         result = gnome_vfs_socket_buffer_read (handle->socket_buffer, buffer, 
1942                         num_bytes, bytes_read);
1943         
1944         if (*bytes_read == 0) {
1945                 return GNOME_VFS_ERROR_EOF;
1946         }                                      
1947
1948         handle->socket_buffer_offset += *bytes_read;
1949         handle->offset += *bytes_read;
1950
1951         DEBUG_HTTP (("-Read (%d)", result));
1952         ANALYZE_HTTP ("==> -do_read");
1953
1954         return result;
1955 }
1956
1957 static GnomeVFSResult
1958 do_seek (GnomeVFSMethod *method,
1959          GnomeVFSMethodHandle *method_handle,
1960          GnomeVFSSeekPosition  whence,
1961          GnomeVFSFileOffset    offset,
1962          GnomeVFSContext *context)
1963 {
1964         HttpFileHandle *handle;
1965
1966         handle = (HttpFileHandle *) method_handle;
1967
1968         if (handle->to_be_written != NULL)
1969                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
1970
1971         switch (whence) {
1972         case GNOME_VFS_SEEK_START:
1973                 handle->offset = offset;
1974                 break;
1975         case GNOME_VFS_SEEK_CURRENT:
1976                 handle->offset += offset;
1977                 break;
1978         case GNOME_VFS_SEEK_END:
1979                 if (!(handle->file_info->flags & GNOME_VFS_FILE_INFO_FIELDS_SIZE))
1980                         return GNOME_VFS_ERROR_NOT_SUPPORTED;
1981                 handle->offset = handle->file_info->size + offset;
1982                 break;
1983         default:
1984                 g_return_val_if_reached(GNOME_VFS_ERROR_NOT_SUPPORTED);
1985         }
1986
1987         return GNOME_VFS_OK;
1988 }
1989
1990 static GnomeVFSResult
1991 do_tell (GnomeVFSMethod *method,
1992          GnomeVFSMethodHandle *method_handle,
1993          GnomeVFSFileOffset *offset_return)
1994 {
1995         HttpFileHandle *handle;
1996
1997         handle = (HttpFileHandle *) method_handle;
1998
1999         *offset_return = handle->offset;
2000
2001         return GNOME_VFS_OK;
2002 }
2003
2004 /* Directory handling - WebDAV servers only */
2005
2006 static void
2007 process_propfind_propstat (xmlNodePtr node, 
2008                            GnomeVFSFileInfo *file_info)
2009 {
2010         xmlNodePtr l;
2011         gboolean treat_as_directory;
2012
2013         treat_as_directory = FALSE;
2014
2015         while (node != NULL) {
2016                 if (strcmp ((char *)node->name, "prop") != 0) {
2017                         /* node name != "prop" - prop is all we care about */
2018                         node = node->next;
2019                         continue;
2020                 }
2021                 /* properties of the file */
2022                 l = node->xmlChildrenNode;
2023                 while (l != NULL) {
2024                         char *node_content_xml = xmlNodeGetContent(l);
2025                         if (node_content_xml) {
2026                                 if (strcmp ((char *)l->name, "getcontenttype") == 0) {
2027
2028                                         file_info->valid_fields |= 
2029                                                 GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
2030                                         
2031                                         if (!file_info->mime_type) {
2032                                                 file_info->mime_type = strip_semicolon (node_content_xml);
2033                                         }
2034                                 } else if (strcmp ((char *)l->name, "getcontentlength") == 0){
2035                                         file_info->valid_fields |= 
2036                                                 GNOME_VFS_FILE_INFO_FIELDS_SIZE;
2037                                         file_info->size = atoi(node_content_xml);
2038                                 } else if (strcmp((char *)l->name, "getlastmodified") == 0) {
2039                                         if (gnome_vfs_atotm (node_content_xml, &(file_info->mtime))) {
2040                                                 file_info->ctime = file_info->mtime;
2041                                                 file_info->valid_fields |= 
2042                                                         GNOME_VFS_FILE_INFO_FIELDS_MTIME 
2043                                                         | GNOME_VFS_FILE_INFO_FIELDS_CTIME;
2044                                         }
2045                                 } 
2046                                 /* Unfortunately, we don't have a mapping for "creationdate" */
2047
2048                                 xmlFree (node_content_xml);
2049                                 node_content_xml = NULL;
2050                         }
2051                         if (strcmp ((char *)l->name, "resourcetype") == 0) {
2052                                 file_info->valid_fields |= 
2053                                         GNOME_VFS_FILE_INFO_FIELDS_TYPE;
2054                                 file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;
2055                                 
2056                                 if (l->xmlChildrenNode && l->xmlChildrenNode->name 
2057                                     && strcmp ((char *)l->xmlChildrenNode->name, "collection") == 0) {
2058                                         file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
2059                                 }
2060                         }
2061                         l = l->next;
2062                 }
2063                 node = node->next;
2064         }
2065         
2066         /* If this is a DAV collection, do we tell nautilus to treat it
2067          * as a directory or as a web page?
2068          */
2069         if (file_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_TYPE
2070             && file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
2071                 g_free (file_info->mime_type);
2072                 if (treat_as_directory) {
2073                         file_info->mime_type = g_strdup ("x-directory/webdav-prefer-directory");
2074                         file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
2075                 } else {
2076                         file_info->mime_type = g_strdup ("x-directory/webdav");
2077                         file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
2078                 }
2079         }
2080         
2081         
2082         if ((file_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE) == 0) {
2083                 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
2084                 file_info->mime_type = g_strdup (gnome_vfs_mime_type_from_name_or_default (file_info->name, "text/plain"));
2085         }
2086
2087         if ((file_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_TYPE) == 0) {
2088                 /* Is this a reasonable assumption ? */
2089                 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE;
2090                 file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;
2091         }
2092 }
2093
2094 /* a strcmp that doesn't barf on NULLs */
2095 static gint
2096 null_handling_strcmp (const char *a, const char *b) 
2097 {
2098         if ((a == NULL) != (b == NULL)) {
2099                 return 1;
2100         }
2101         
2102         if (a == NULL && b == NULL) {
2103                 return 0;
2104         }
2105         
2106         return strcmp (a, b);
2107 }
2108
2109 #if 0
2110 static char *
2111 unescape_unreserved_chars (const char *in_string)
2112 {
2113         /* RFC 2396 section 2.2 */
2114         static const char * reserved_chars = "%;/?:@&=+$,";
2115
2116         char *ret, *write_char;
2117         const char * read_char;
2118
2119         if (in_string == NULL) {
2120                 return NULL;
2121         }
2122         
2123         ret = g_new (char, strlen (in_string) + 1);
2124
2125         for (read_char = in_string, write_char = ret ; *read_char != '\0' ; read_char++) {
2126                 if (read_char[0] == '%' 
2127                     && g_ascii_isxdigit (read_char[1]) 
2128                     && g_ascii_isxdigit (read_char[2])) {
2129                         char unescaped;
2130                         
2131                         unescaped = (g_ascii_xdigit_value (read_char[1]) << 4) | g_ascii_xdigit_value (read_char[2]);
2132                         
2133                         if (strchr (reserved_chars, unescaped)) {
2134                                 *write_char++ = *read_char++;
2135                                 *write_char++ = *read_char++;
2136                                 *write_char++ = *read_char; /*The last ++ is done in the for statement */
2137                         } else {
2138                                 *write_char++ = unescaped;
2139                                 read_char += 2; /*The last ++ is done in the for statement */ 
2140                         }
2141                 } else {
2142                         *write_char++ = *read_char;
2143                 }
2144         }
2145         *write_char++ = '\0';           
2146         
2147         return ret;
2148 }
2149 #endif /* 0 */
2150
2151 static xmlNodePtr
2152 find_child_node_named (xmlNodePtr node, 
2153                        const char *child_node_name)
2154 {
2155         xmlNodePtr child;
2156
2157         child = node->xmlChildrenNode;
2158
2159         for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
2160                 if (0 == strcmp (child->name, child_node_name)) {
2161                         return child;
2162                 }
2163         }
2164
2165         return NULL;
2166 }
2167
2168 /* Look for a <status> tag in the children of node, and returns
2169  * the corresponding (HTTP) error code
2170  */
2171 static gboolean
2172 get_status_node (xmlNodePtr node, guint *status_code)
2173 {
2174         xmlNodePtr status_node;
2175         char *status_string;
2176         gboolean ret;
2177
2178         status_node = find_child_node_named (node, "status");
2179
2180         if (status_node != NULL) {
2181                 status_string = xmlNodeGetContent (status_node);
2182                 ret = parse_status (status_string, status_code);
2183                 xmlFree (status_string);
2184         } else {
2185                 ret = FALSE;
2186         }
2187         
2188         return ret;
2189 }
2190
2191 static GnomeVFSFileInfo *
2192 process_propfind_response(xmlNodePtr n,
2193                           GnomeVFSURI *base_uri)
2194 {
2195         GnomeVFSFileInfo *file_info = defaults_file_info_new ();
2196         GnomeVFSURI *second_base = gnome_vfs_uri_append_path (base_uri, "/");
2197         guint status_code;
2198         
2199         file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE;
2200         
2201         while (n != NULL) {
2202                 if (strcmp ((char *)n->name, "href") == 0) {
2203                         char *nodecontent = xmlNodeGetContent (n);
2204                         GnomeVFSResult rv;
2205                         
2206                         rv = gnome_vfs_remove_optional_escapes (nodecontent);
2207                         
2208                         if (nodecontent != NULL && *nodecontent != '\0' && rv == GNOME_VFS_OK) {
2209                                 gint len;
2210                                 GnomeVFSURI *uri = gnome_vfs_uri_new (nodecontent);
2211                                 
2212                                 if (uri != NULL) {
2213                                         if ((0 == null_handling_strcmp (base_uri->text, uri->text)) ||
2214                                             (0 == null_handling_strcmp (second_base->text, uri->text))) {
2215                                                 file_info->name = NULL; /* this file is the . directory */
2216                                         } else {
2217                                                 if (file_info->name != NULL) {
2218                                                         /* Don't leak if a (potentially malicious)
2219                                                          * server returns several href in its answer
2220                                                          */
2221                                                         g_free (file_info->name);
2222                                                 }
2223                                                 file_info->name = gnome_vfs_uri_extract_short_name (uri);
2224                                                 if (file_info->name != NULL) {
2225                                                         len = strlen (file_info->name) -1;
2226                                                         if (file_info->name[len] == '/') {
2227                                                                 /* trim trailing `/` - it confuses stuff */
2228                                                                 file_info->name[len] = '\0';
2229                                                         }
2230                                                 } else {
2231                                                         g_warning ("Invalid filename in PROPFIND '%s'; silently skipping", nodecontent);
2232                                                 }
2233                                         }
2234                                         gnome_vfs_uri_unref (uri);
2235                                 } else {
2236                                         g_warning ("Can't make URI from href in PROPFIND '%s'; silently skipping", nodecontent);
2237                                 }
2238                         } else {
2239                                 g_warning ("got href without contents in PROPFIND response");
2240                         }
2241
2242                         xmlFree (nodecontent);
2243                 } else if (strcmp ((char *)n->name, "propstat") == 0) {
2244                         if (get_status_node (n, &status_code) && status_code == 200) {
2245                                 process_propfind_propstat (n->xmlChildrenNode, file_info);
2246                         }
2247                 }
2248                 n = n->next;
2249         }
2250
2251         gnome_vfs_uri_unref (second_base);
2252
2253         return file_info;
2254 }
2255
2256
2257
2258 static GnomeVFSResult
2259 make_propfind_request (HttpFileHandle *handle,
2260         GnomeVFSURI *uri,
2261         gint depth,
2262         GnomeVFSContext *context)
2263 {
2264         GnomeVFSResult result = GNOME_VFS_OK;
2265         GnomeVFSFileSize bytes_read, num_bytes=(64*1024);
2266         char *buffer = g_malloc(num_bytes);
2267         xmlParserCtxtPtr parserContext;
2268         xmlDocPtr doc = NULL;
2269         xmlNodePtr cur = NULL;
2270         char *extraheaders = g_strdup_printf("Depth: %d\r\n", depth);
2271         gboolean found_root_node_props;
2272         gboolean handle_valid = FALSE;
2273
2274         GByteArray *request = g_byte_array_new();
2275         char *request_str = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
2276                 "<D:propfind xmlns:D=\"DAV:\" >"
2277                 "<D:prop>"
2278                 "<D:creationdate/>"
2279                 "<D:getcontentlength/>"
2280                 "<D:getcontenttype/>"
2281                 "<D:getlastmodified/>"
2282                 "<D:resourcetype/>"
2283                 "</D:prop>"
2284                 "</D:propfind>";
2285
2286         ANALYZE_HTTP ("==> +make_propfind_request");
2287
2288         request = g_byte_array_append(request, request_str, 
2289                         strlen(request_str));
2290
2291         parserContext = xmlCreatePushParserCtxt(NULL, NULL, "", 0, "PROPFIND");
2292
2293         if (depth > 0) {
2294                 http_cache_invalidate_uri_and_children (uri);
2295         }
2296
2297         result = make_request (handle, uri, "PROPFIND", request, 
2298                                extraheaders, context);
2299         if (result == GNOME_VFS_OK)
2300                 handle_valid = TRUE;
2301         
2302         /* FIXME bugzilla.gnome.org 43834: It looks like some http
2303          * servers (eg, www.yahoo.com) treat PROPFIND as a GET and
2304          * return a 200 OK. Others may return access denied errors or
2305          * redirects or any other legal response. This case probably
2306          * needs to be made more robust.
2307          */
2308         if (result == GNOME_VFS_OK && handle->server_status != 207) { /* Multi-Status */
2309                 DEBUG_HTTP (("HTTP server returned an invalid PROPFIND response: %d", handle->server_status));
2310                 result = GNOME_VFS_ERROR_NOT_SUPPORTED;
2311         }
2312         
2313         if (result == GNOME_VFS_OK) {
2314                 do {
2315                         result = do_read (NULL, (GnomeVFSMethodHandle *) handle,
2316                                           buffer, num_bytes, &bytes_read, context);
2317                         
2318                         if (result != GNOME_VFS_OK ) {
2319                                 break;
2320                         }
2321                         xmlParseChunk (parserContext, buffer, bytes_read, 0);
2322                         buffer[bytes_read]=0;
2323                 } while (bytes_read > 0);
2324         }
2325
2326         if (result == GNOME_VFS_ERROR_EOF) {
2327                 result = GNOME_VFS_OK;
2328         }
2329         
2330         if (result != GNOME_VFS_OK) {
2331                 goto cleanup;
2332         }
2333         
2334         xmlParseChunk (parserContext, "", 0, 1);
2335
2336         doc = parserContext->myDoc;
2337         if (doc == NULL) {
2338                 result = GNOME_VFS_ERROR_GENERIC;
2339                 goto cleanup;
2340         }
2341         
2342         cur = doc->xmlRootNode;
2343         if (strcmp ((char *)cur->name, "multistatus") != 0) {
2344                 DEBUG_HTTP (("Couldn't find <multistatus>.\n"));
2345                 result = GNOME_VFS_ERROR_GENERIC;
2346                 goto cleanup;
2347         }
2348         cur = cur->xmlChildrenNode;
2349         
2350         found_root_node_props = FALSE;
2351         while (cur != NULL) {
2352                 if (strcmp ((char *)cur->name, "response") == 0) {
2353                         GnomeVFSFileInfo *file_info;
2354                         guint status;
2355                         
2356                         /* Some webdav servers (eg resin) put the HTTP status
2357                          * code for PROPFIND request in the xml answer instead
2358                          * of directly sending a 404 
2359                          */
2360                         if (get_status_node (cur, &status) && !HTTP_20X(status)) {
2361                                 result = http_status_to_vfs_result (status);
2362                                 goto cleanup;
2363                         }
2364                         
2365                         file_info = 
2366                                 process_propfind_response (cur->xmlChildrenNode, uri);
2367                         
2368                         if (file_info->name != NULL) { 
2369                                 handle->files = g_list_append (handle->files, file_info);
2370                         } else {
2371                                 /* This response refers to the root node */
2372                                 /* Abandon the old information that came from create_handle*/
2373                                 
2374                                 file_info->name = handle->file_info->name;
2375                                 handle->file_info->name = NULL;
2376                                 gnome_vfs_file_info_unref (handle->file_info);
2377                                 handle->file_info = file_info;
2378                                 found_root_node_props = TRUE;
2379                         }
2380                         
2381                 } else {
2382                         DEBUG_HTTP(("expecting <response> got <%s>\n", cur->name));
2383                 }
2384                 cur = cur->next;
2385         }
2386         
2387         if (!found_root_node_props) {
2388                 DEBUG_HTTP (("Failed to find root request node properties during propfind"));
2389                 result = GNOME_VFS_ERROR_GENERIC;
2390                 goto cleanup;
2391         }
2392
2393         /*
2394          * RFC 2518
2395          * Section 8.1, final line
2396          * "The results of this method [PROPFIND] SHOULD NOT be cached"
2397          * Well, at least its not "MUST NOT"
2398          */
2399         
2400         if (depth == 0) {
2401                 http_cache_add_uri (uri, handle->file_info, TRUE);
2402         } else {
2403                 http_cache_add_uri_and_children (uri, handle->file_info, handle->files);
2404         }
2405
2406 cleanup:
2407         g_free(buffer);
2408         g_free(extraheaders);
2409         xmlFreeParserCtxt(parserContext);
2410         
2411         if (result != GNOME_VFS_OK && handle_valid) {
2412                 http_handle_close (handle, context);
2413         }
2414         
2415         ANALYZE_HTTP ("==> -make_propfind_request");
2416         
2417         return result;
2418 }
2419
2420 static GnomeVFSResult
2421 do_open_directory(GnomeVFSMethod *method,
2422                   GnomeVFSMethodHandle **method_handle,
2423                   GnomeVFSURI *uri,
2424                   GnomeVFSFileInfoOptions options,
2425                   GnomeVFSContext *context) 
2426 {
2427         /* TODO move to using the gnome_vfs_file_info_list family of functions */
2428         GnomeVFSResult result;
2429         HttpFileHandle *handle = NULL;
2430         GnomeVFSFileInfo * file_info_cached;
2431         GList *child_file_info_cached_list = NULL;
2432
2433         ANALYZE_HTTP ("==> +do_open_directory");
2434         DEBUG_HTTP (("+Open_Directory options: %d URI: '%s'", options, gnome_vfs_uri_to_string (uri, 0)));
2435
2436         /* Check the cache--is this even a directory?  
2437          * (Nautilus, in particular, seems to like to make this call on non directories
2438          */
2439
2440         file_info_cached = http_cache_check_uri (uri);
2441
2442         if (file_info_cached) {
2443                 if (GNOME_VFS_FILE_TYPE_DIRECTORY != file_info_cached->type) {
2444                         ANALYZE_HTTP ("==> Cache Hit (Negative)");      
2445                         gnome_vfs_file_info_unref (file_info_cached);
2446                         result = GNOME_VFS_ERROR_NOT_A_DIRECTORY;
2447                         goto error;
2448                 }
2449                 gnome_vfs_file_info_unref (file_info_cached);
2450                 file_info_cached = NULL;
2451         }
2452
2453         
2454         /* The check for directory contents is more stringent */
2455         file_info_cached = http_cache_check_directory_uri (uri, &child_file_info_cached_list);
2456
2457         if (file_info_cached) {
2458                 handle = g_new (HttpFileHandle, 1);
2459                 http_file_handle_new (handle, NULL, uri);
2460                 gnome_vfs_file_info_unref (handle->file_info);
2461                 handle->file_info = file_info_cached;
2462                 handle->files = child_file_info_cached_list;
2463                 result = GNOME_VFS_OK;
2464         } else {
2465                 handle = g_new (HttpFileHandle, 1);
2466                 result = make_propfind_request(handle, uri, 1, context);
2467                 /* mfleming -- is this necessary?  Most DAV server's I've seen don't have the horrible
2468                  * lack-of-trailing-/-is-a-301 problem for PROPFIND's
2469                  */
2470                 if (result == GNOME_VFS_ERROR_NOT_FOUND) { /* 404 not found */
2471                         if (uri->text != NULL && *uri->text != '\0'
2472                            && uri->text[strlen (uri->text) - 1] != '/') {
2473                                 GnomeVFSURI *tmpuri = gnome_vfs_uri_append_path (uri, "/");
2474                                 result = do_open_directory (method, (GnomeVFSMethodHandle **)&handle, tmpuri, options, context);
2475                                 gnome_vfs_uri_unref (tmpuri);
2476
2477                         }
2478                 }
2479
2480                 if (result == GNOME_VFS_OK
2481                     && handle->file_info->type != GNOME_VFS_FILE_TYPE_DIRECTORY) {
2482                         result = GNOME_VFS_ERROR_NOT_A_DIRECTORY;
2483                         http_handle_close (handle, context);
2484                         g_free (handle);
2485                         handle = NULL;
2486                 }
2487         }
2488         
2489         *method_handle = (GnomeVFSMethodHandle *)handle;
2490
2491 error:
2492         DEBUG_HTTP (("-Open_Directory (%d) handle:0x%08x", result, (unsigned int)handle));
2493         ANALYZE_HTTP ("==> -do_open_directory");
2494         
2495         return result;
2496 }
2497
2498 static GnomeVFSResult
2499 do_close_directory (GnomeVFSMethod *method,
2500                     GnomeVFSMethodHandle *method_handle,
2501                     GnomeVFSContext *context) 
2502 {
2503         HttpFileHandle *handle;
2504         
2505         DEBUG_HTTP (("+Close_Directory"));
2506         
2507         handle = (HttpFileHandle *) method_handle;
2508         
2509         http_handle_close(handle, context);
2510         g_free (handle);
2511
2512         DEBUG_HTTP (("-Close_Directory (0) handle:0x%08x", (unsigned int) method_handle));
2513
2514         return GNOME_VFS_OK;
2515 }
2516        
2517 static GnomeVFSResult
2518 do_read_directory (GnomeVFSMethod *method,
2519        GnomeVFSMethodHandle *method_handle,
2520        GnomeVFSFileInfo *file_info,
2521        GnomeVFSContext *context)
2522 {
2523         HttpFileHandle *handle;
2524         GnomeVFSResult result;
2525
2526         DEBUG_HTTP (("+Read_Directory handle:0x%08x", (unsigned int) method_handle));
2527
2528         handle = (HttpFileHandle *) method_handle;
2529         
2530         if (handle->files && g_list_length (handle->files)) {
2531                 GnomeVFSFileInfo *original_info = g_list_nth_data (handle->files, 0);
2532                 gboolean found_entry = FALSE;
2533                 
2534                 /* mfleming -- Why is this check here?  Does anyone set original_info->name to NULL? */
2535                 if (original_info->name != NULL && original_info->name[0]) {
2536                         gnome_vfs_file_info_copy (file_info, original_info); 
2537                         found_entry = TRUE;
2538                 }
2539                 
2540                 /* remove our GnomeVFSFileInfo from the list */
2541                 handle->files = g_list_remove (handle->files, original_info);
2542                 gnome_vfs_file_info_unref (original_info);
2543         
2544                 /* mfleming -- Is this necessary? */
2545                 if (found_entry) {
2546                         result = GNOME_VFS_OK;
2547                 } else {
2548                         result = do_read_directory (method, method_handle, file_info, context);
2549                 }
2550         } else {
2551                 result = GNOME_VFS_ERROR_EOF;
2552         }
2553
2554         DEBUG_HTTP (("-Read_Directory (%d)", result));
2555         return result;
2556 }
2557  
2558
2559 static GnomeVFSResult
2560 do_get_file_info (GnomeVFSMethod *method,
2561                   GnomeVFSURI *uri,
2562                   GnomeVFSFileInfo *file_info,
2563                   GnomeVFSFileInfoOptions options,
2564                   GnomeVFSContext *context)
2565 {
2566         HttpFileHandle *handle;
2567         GnomeVFSResult result;
2568         GnomeVFSFileInfo * file_info_cached;
2569
2570         ANALYZE_HTTP ("==> +do_get_file_info");
2571         DEBUG_HTTP (("+Get_File_Info options: %d URI: '%s'", options, gnome_vfs_uri_to_string( uri, 0)));
2572
2573         file_info_cached = http_cache_check_uri (uri);
2574
2575         if (file_info_cached != NULL) {
2576                 gnome_vfs_file_info_copy (file_info, file_info_cached);
2577                 gnome_vfs_file_info_unref (file_info_cached);
2578                 ANALYZE_HTTP ("==> Cache Hit"); 
2579                 result = GNOME_VFS_OK;
2580         } else {
2581                 /*
2582                  * Start off by making a PROPFIND request.  Fall back to a HEAD if it fails
2583                  */
2584                 
2585                 handle = g_new (HttpFileHandle, 1);
2586                 result = make_propfind_request (handle, uri, 0, context);
2587                 
2588                 /* Note that theoretically we could not bother with this request if we get a 404 back,
2589                  * but since some servers seem to return wierd things on PROPFIND (mostly 200 OK's...)
2590                  * I'm not going to count on the PROPFIND response....
2591                  */ 
2592                 if (result == GNOME_VFS_OK) {
2593                         gnome_vfs_file_info_copy (file_info, handle->file_info);
2594                         http_handle_close (handle, context);
2595                         g_free (handle);
2596                         handle = NULL;
2597                 } else {
2598                         g_free (handle);
2599                         handle = NULL;
2600                         g_assert (handle == NULL); /* Make sure we're not leaking some old one */
2601                         
2602                         /* Lame buggy servers (eg: www.mozilla.org,
2603                          * www.corel.com)) return an HTTP error for a
2604                          * HEAD where a GET would succeed. In these
2605                          * cases lets try to do a GET.
2606                          */
2607                         if (result != GNOME_VFS_OK) {
2608                                 g_assert (handle == NULL); /* Make sure we're not leaking some old one */
2609
2610                                 ANALYZE_HTTP ("==> do_get_file_info: do GET ");
2611
2612                                 handle = g_new (HttpFileHandle, 1);
2613                                 result = make_request (handle, uri, "GET", NULL, NULL, context);
2614                                 if (result == GNOME_VFS_OK) {
2615                                         gnome_vfs_file_info_copy (file_info, handle->file_info);
2616                                         http_cache_add_uri (uri, handle->file_info, FALSE);
2617                                         http_handle_close (handle, context);
2618                                 }
2619                                 g_free (handle);
2620                                 handle = NULL;
2621
2622                                 /* If we get a redirect, we should be
2623                                  * basing the MIME type on the type of
2624                                  * the page we'll be redirected
2625                                  * too. Maybe we even want to take the
2626                                  * "follow_links" setting into account.
2627                                  */
2628                                 /* FIXME: For now we treat all
2629                                  * redirects as if they lead to a
2630                                  * text/html. That works pretty well,
2631                                  * but it's not correct.
2632                                  */
2633                                 if (handle != NULL && HTTP_REDIRECTED (handle->server_status)) {
2634                                         g_free (file_info->mime_type);
2635                                         file_info->mime_type = g_strdup ("text/html");
2636                                 }
2637                         }
2638                         
2639                         if (result == GNOME_VFS_ERROR_NOT_FOUND) { /* 404 not found */
2640                                 /* FIXME bugzilla.gnome.org 43835: mfleming: Is this code really appropriate?
2641                                  * In any case, it doesn't seem to be appropriate for a DAV-enabled
2642                                  * server, since they don't seem to send 301's when you PROPFIND collections
2643                                  * without a trailing '/'.
2644                                  */
2645                                 if (uri->text != NULL && *uri->text != '\0' 
2646                                     && uri->text[strlen(uri->text)-1] != '/') {
2647                                         GnomeVFSURI *tmpuri = gnome_vfs_uri_append_path (uri, "/");
2648                                         
2649                                         result = do_get_file_info (method, tmpuri, file_info, options, context);
2650                                         gnome_vfs_uri_unref (tmpuri);
2651                                 }
2652                         }
2653                 }
2654         }
2655         
2656         DEBUG_HTTP (("-Get_File_Info (%d)", result));
2657         ANALYZE_HTTP ("==> -do_get_file_info");
2658         
2659         return result;
2660 }
2661
2662 static GnomeVFSResult
2663 do_get_file_info_from_handle (GnomeVFSMethod *method,
2664                               GnomeVFSMethodHandle *method_handle,
2665                               GnomeVFSFileInfo *file_info,
2666                               GnomeVFSFileInfoOptions options,
2667                               GnomeVFSContext *context)
2668 {
2669         HttpFileHandle *handle;
2670         
2671         DEBUG_HTTP (("+Get_File_Info_From_Handle"));
2672         
2673         handle = (HttpFileHandle *) method_handle;
2674         
2675         gnome_vfs_file_info_copy (file_info, handle->file_info);
2676         
2677         DEBUG_HTTP (("-Get_File_Info_From_Handle"));
2678         
2679         return GNOME_VFS_OK;
2680 }
2681
2682 static gboolean
2683 do_is_local (GnomeVFSMethod *method,
2684              const GnomeVFSURI *uri)
2685 {
2686         DEBUG_HTTP (("+Is_Local"));
2687         return FALSE;
2688 }
2689
2690 static GnomeVFSResult 
2691 do_make_directory (GnomeVFSMethod *method, 
2692                    GnomeVFSURI *uri,
2693                    guint perm, 
2694                    GnomeVFSContext *context) 
2695 {
2696         /* MKCOL /path HTTP/1.0 */
2697
2698         HttpFileHandle *handle;
2699         GnomeVFSResult result;
2700
2701         DEBUG_HTTP (("+Make_Directory URI: '%s'", gnome_vfs_uri_to_string (uri, 0)));
2702         ANALYZE_HTTP ("==> +do_make_directory");
2703
2704         /*
2705          * MKCOL returns a 405 if you try to MKCOL on something that
2706          * already exists.  Of course, we don't know whether that means that 
2707          * the server doesn't support DAV or the collection already exists.
2708          * So we do a PROPFIND first to find out
2709          */
2710         /* FIXME check cache here */
2711         handle = g_new (HttpFileHandle, 1);
2712         result = make_propfind_request(handle, uri, 0, context);
2713
2714         if (result == GNOME_VFS_OK) {
2715                 result = GNOME_VFS_ERROR_FILE_EXISTS;
2716         } else {
2717                 /* Make sure we're not leaking an old one */
2718                 g_assert (handle == NULL);
2719                 
2720                 if (result == GNOME_VFS_ERROR_NOT_FOUND) {
2721                         http_cache_invalidate_uri_parent (uri);
2722                         handle = g_new (HttpFileHandle, 1);
2723                         result = make_request (handle, uri, "MKCOL", NULL, NULL, context);
2724                 }
2725         }
2726         http_handle_close (handle, context);
2727         g_free (handle);
2728         
2729         if (result == GNOME_VFS_ERROR_NOT_FOUND) {
2730                 result = resolve_409 (method, uri, context);
2731         }
2732
2733         ANALYZE_HTTP ("==> -do_make_directory");
2734         DEBUG_HTTP (("-Make_Directory (%d)", result));
2735
2736         return result;
2737 }
2738
2739 static GnomeVFSResult 
2740 do_remove_directory(GnomeVFSMethod *method, 
2741                     GnomeVFSURI *uri, 
2742                     GnomeVFSContext *context) 
2743 {
2744         /* DELETE /path HTTP/1.0 */
2745         HttpFileHandle *handle;
2746         GnomeVFSResult result;
2747
2748         ANALYZE_HTTP ("==> +do_remove_directory");
2749         DEBUG_HTTP (("+Remove_Directory URI: '%s'", gnome_vfs_uri_to_string (uri, 0)));
2750
2751         http_cache_invalidate_uri_parent (uri);
2752
2753         /* FIXME this should return GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY if the
2754          * directory is not empty
2755          */
2756         handle = g_new (HttpFileHandle, 1);
2757         result = make_request (handle, uri, "DELETE", NULL, NULL,
2758                                context);
2759         http_handle_close (handle, context);
2760         g_free (handle);
2761         
2762         DEBUG_HTTP (("-Remove_Directory (%d)", result));
2763         ANALYZE_HTTP ("==> -do_remove_directory");
2764         
2765         return result;
2766 }
2767
2768 static gboolean 
2769 is_same_fs (const GnomeVFSURI *a, 
2770             const GnomeVFSURI *b)
2771 {
2772         return null_handling_strcmp (gnome_vfs_uri_get_scheme (a), gnome_vfs_uri_get_scheme (b)) == 0
2773                 && null_handling_strcmp (gnome_vfs_uri_get_host_name (a), gnome_vfs_uri_get_host_name (b)) == 0
2774                 && null_handling_strcmp (gnome_vfs_uri_get_user_name (a), gnome_vfs_uri_get_user_name (b)) == 0
2775                 && null_handling_strcmp (gnome_vfs_uri_get_password (a), gnome_vfs_uri_get_password (b)) == 0
2776                 && (gnome_vfs_uri_get_host_port (a) == gnome_vfs_uri_get_host_port (b));
2777 }
2778
2779 static GnomeVFSResult
2780 do_move (GnomeVFSMethod *method,
2781          GnomeVFSURI *old_uri,
2782          GnomeVFSURI *new_uri,
2783          gboolean force_replace,
2784          GnomeVFSContext *context)
2785 {
2786
2787         /*
2788          * MOVE /path1 HTTP/1.0
2789          * Destination: /path2
2790          * Overwrite: (T|F)
2791          */
2792
2793         HttpFileHandle *handle;
2794         GnomeVFSResult result;
2795
2796         char *destpath, *destheader;
2797
2798         ANALYZE_HTTP ("==> +do_move");
2799         DEBUG_HTTP (("+Move URI: '%s' Dest: '%s'", 
2800                 gnome_vfs_uri_to_string (old_uri, 0), 
2801                 gnome_vfs_uri_to_string (new_uri, 0)));
2802
2803         if (!is_same_fs (old_uri, new_uri)) {
2804                 return GNOME_VFS_ERROR_NOT_SAME_FILE_SYSTEM;
2805         }       
2806         
2807         destpath = gnome_vfs_uri_to_string (new_uri, GNOME_VFS_URI_HIDE_USER_NAME|GNOME_VFS_URI_HIDE_PASSWORD);
2808         destheader = g_strdup_printf ("Destination: %s\r\nOverwrite: %c\r\n", destpath, force_replace ? 'T' : 'F' );
2809
2810         handle = g_new (HttpFileHandle, 1);
2811         result = make_request (handle, old_uri, "MOVE", NULL, destheader, context);
2812         http_handle_close (handle, context);
2813         g_free (handle);
2814         handle = NULL;
2815
2816         if (result == GNOME_VFS_ERROR_NOT_FOUND) {
2817                 result = resolve_409 (method, new_uri, context);
2818         }
2819
2820         http_cache_invalidate_uri_parent (old_uri);
2821         http_cache_invalidate_uri_parent (new_uri);
2822
2823         DEBUG_HTTP (("-Move (%d)", result));
2824         ANALYZE_HTTP ("==> -do_move");
2825
2826         return result;
2827 }
2828
2829
2830 static GnomeVFSResult 
2831 do_unlink(GnomeVFSMethod *method,
2832         GnomeVFSURI *uri,
2833           GnomeVFSContext *context)
2834 {
2835         GnomeVFSResult result;
2836
2837         /* FIXME need to make sure this fails on directories */
2838         ANALYZE_HTTP ("==> +do_unlink");
2839         DEBUG_HTTP (("+Unlink URI: '%s'", gnome_vfs_uri_to_string (uri, 0)));
2840         result = do_remove_directory (method, uri, context);
2841         DEBUG_HTTP (("-Unlink (%d)", result));
2842         ANALYZE_HTTP ("==> -do_unlink");
2843         
2844         return result;
2845 }
2846
2847 static GnomeVFSResult 
2848 do_check_same_fs (GnomeVFSMethod *method,
2849                   GnomeVFSURI *a,
2850                   GnomeVFSURI *b,
2851                   gboolean *same_fs_return,
2852                   GnomeVFSContext *context)
2853 {
2854         *same_fs_return = is_same_fs (a, b);
2855         
2856         return GNOME_VFS_OK;
2857 }
2858
2859 static GnomeVFSResult
2860 do_set_file_info (GnomeVFSMethod *method,
2861                   GnomeVFSURI *uri,
2862                   const GnomeVFSFileInfo *info,
2863                   GnomeVFSSetFileInfoMask mask,
2864                   GnomeVFSContext *context)
2865 {
2866         GnomeVFSURI *parent_uri, *new_uri;
2867         GnomeVFSResult result;
2868         
2869         /* FIXME: For now, we only support changing the name. */
2870         if ((mask & ~(GNOME_VFS_SET_FILE_INFO_NAME)) != 0) {
2871                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2872         }
2873         
2874         /* FIXME bugzillagnome.org 40645: Make sure this returns an
2875          * error for incoming names with "/" characters in them,
2876          * instead of moving the file.
2877          */
2878         
2879         /* Share code with do_move. */
2880         parent_uri = gnome_vfs_uri_get_parent (uri);
2881         if (parent_uri == NULL) {
2882                 return GNOME_VFS_ERROR_NOT_FOUND;
2883         }
2884         new_uri = gnome_vfs_uri_append_file_name (parent_uri, info->name);
2885         gnome_vfs_uri_unref (parent_uri);
2886         result = do_move (method, uri, new_uri, FALSE, context);
2887         gnome_vfs_uri_unref (new_uri);
2888         return result;
2889 }
2890
2891 static GnomeVFSMethod method = {
2892         sizeof (GnomeVFSMethod),
2893         do_open,
2894         do_create,
2895         do_close,
2896         do_read,
2897         do_write,
2898         do_seek,
2899         do_tell,
2900         NULL, /* truncate_handle */
2901         do_open_directory,
2902         do_close_directory,
2903         do_read_directory,
2904         do_get_file_info,
2905         do_get_file_info_from_handle,
2906         do_is_local,
2907         do_make_directory,
2908         do_remove_directory,
2909         do_move,
2910         do_unlink,
2911         do_check_same_fs,
2912         do_set_file_info,
2913         NULL, /* truncate */
2914         NULL, /* find_directory */
2915         NULL  /* create_symbolic_link */
2916 };
2917
2918 GnomeVFSMethod *
2919 vfs_module_init (const char *method_name, 
2920                  const char *args)
2921 {
2922         GError *gconf_error = NULL;
2923         gboolean use_proxy;
2924         gboolean use_proxy_auth;
2925
2926         LIBXML_TEST_VERSION
2927                 
2928         gl_client = gconf_client_get_default ();
2929
2930         gl_mutex = g_mutex_new ();
2931         
2932         gconf_client_add_dir (gl_client, PATH_GCONF_GNOME_VFS, GCONF_CLIENT_PRELOAD_ONELEVEL, &gconf_error);
2933         if (gconf_error) {
2934                 DEBUG_HTTP (("GConf error during client_add_dir '%s'", gconf_error->message));
2935                 g_error_free (gconf_error);
2936                 gconf_error = NULL;
2937         }
2938
2939         gconf_client_notify_add (gl_client, PATH_GCONF_GNOME_VFS, notify_gconf_value_changed, NULL, NULL, &gconf_error);
2940         if (gconf_error) {
2941                 DEBUG_HTTP (("GConf error during notify_error '%s'", gconf_error->message));
2942                 g_error_free (gconf_error);
2943                 gconf_error = NULL;
2944         }
2945
2946         /* Load the http proxy setting */       
2947         use_proxy = gconf_client_get_bool (gl_client, KEY_GCONF_USE_HTTP_PROXY, &gconf_error);
2948
2949         if (gconf_error != NULL) {
2950                 DEBUG_HTTP (("GConf error during client_get_bool '%s'", gconf_error->message));
2951                 g_error_free (gconf_error);
2952                 gconf_error = NULL;
2953         } else {
2954                 construct_gl_http_proxy (use_proxy);
2955         }
2956
2957         use_proxy_auth = gconf_client_get_bool (gl_client, KEY_GCONF_HTTP_USE_AUTH, &gconf_error);
2958
2959         if (gconf_error != NULL) {
2960                 DEBUG_HTTP (("GConf error during client_get_bool '%s'", gconf_error->message));
2961                 g_error_free (gconf_error);
2962                 gconf_error = NULL;
2963         } else {
2964                 set_proxy_auth (use_proxy_auth);
2965         }
2966
2967         http_authn_init ();
2968         http_cache_init ();
2969
2970         return &method;
2971 }
2972
2973 void
2974 vfs_module_shutdown (GnomeVFSMethod *method)
2975 {
2976         g_object_unref (G_OBJECT (gl_client));
2977
2978         http_authn_shutdown ();
2979         
2980         http_cache_shutdown();
2981
2982         g_mutex_free (gl_mutex);
2983
2984         gl_client = NULL;
2985 }
2986
2987 /* A "409 Conflict" currently maps to GNOME_VFS_ERROR_NOT_FOUND because it can be returned
2988  * when the parent collection/directory does not exist.  Unfortunately, Xythos also returns
2989  * this code when the destination filename of a PUT, MKCOL, or MOVE contains illegal characters, 
2990  * eg "my*file:name".
2991  * 
2992  * The only way to resolve this is to ask...
2993  */
2994
2995 static GnomeVFSResult
2996 resolve_409 (GnomeVFSMethod *method, GnomeVFSURI *uri, GnomeVFSContext *context)
2997 {
2998         GnomeVFSFileInfo *file_info;
2999         GnomeVFSURI *parent_dest_uri;
3000         GnomeVFSResult result;
3001
3002
3003         ANALYZE_HTTP ("==> +resolving 409");
3004
3005         file_info = gnome_vfs_file_info_new ();
3006         parent_dest_uri = gnome_vfs_uri_get_parent (uri);
3007
3008         if (parent_dest_uri != NULL) {
3009                 result = do_get_file_info (method,
3010                                            parent_dest_uri,
3011                                            file_info,
3012                                            GNOME_VFS_FILE_INFO_DEFAULT,
3013                                            context);
3014
3015                 gnome_vfs_file_info_unref (file_info);
3016                 file_info = NULL;
3017                 
3018                 gnome_vfs_uri_unref (parent_dest_uri);
3019                 parent_dest_uri = NULL;
3020         } else {
3021                 result = GNOME_VFS_ERROR_NOT_FOUND;
3022         }
3023         
3024         if (result == GNOME_VFS_OK) {
3025                 /* The destination filename contains characters that are not allowed
3026                  * by the server.  This is a bummer mapping, but EINVAL is what
3027                  * the Linux FAT filesystems return on bad filenames, so at least
3028                  * its not without precedent...
3029                  */ 
3030                 result = GNOME_VFS_ERROR_BAD_PARAMETERS;
3031         } else {
3032                 /* The destination's parent path does not exist */
3033                 result = GNOME_VFS_ERROR_NOT_FOUND;
3034         }
3035
3036         ANALYZE_HTTP ("==> -resolving 409");
3037
3038         return result;
3039 }
3040
3041 static gboolean
3042 invoke_callback_headers_received (HttpFileHandle *handle)
3043 {
3044         GnomeVFSModuleCallbackReceivedHeadersIn in_args;
3045         GnomeVFSModuleCallbackReceivedHeadersOut out_args;
3046         gboolean ret = FALSE;
3047
3048         memset (&in_args, 0, sizeof (in_args));
3049         memset (&out_args, 0, sizeof (out_args));
3050
3051         in_args.uri = handle->uri;
3052         in_args.headers = handle->response_headers;
3053
3054         ret = gnome_vfs_module_callback_invoke (GNOME_VFS_MODULE_CALLBACK_HTTP_RECEIVED_HEADERS,
3055                                                 &in_args, sizeof (in_args),
3056                                                 &out_args, sizeof (out_args));
3057
3058         return ret;
3059 }
3060
3061 static gboolean
3062 invoke_callback_send_additional_headers (GnomeVFSURI  *uri,
3063                                          GList       **headers)
3064 {
3065         GnomeVFSModuleCallbackAdditionalHeadersIn in_args;
3066         GnomeVFSModuleCallbackAdditionalHeadersOut out_args;
3067         gboolean ret = FALSE;
3068
3069         memset (&in_args, 0, sizeof (in_args));
3070         memset (&out_args, 0, sizeof (out_args));
3071
3072         in_args.uri = uri;
3073
3074         ret = gnome_vfs_module_callback_invoke (GNOME_VFS_MODULE_CALLBACK_HTTP_SEND_ADDITIONAL_HEADERS,
3075                                                 &in_args, sizeof (in_args),
3076                                                 &out_args, sizeof (out_args));
3077
3078         if (ret) {
3079                 *headers = out_args.headers;
3080                 return TRUE;
3081         }
3082
3083         if (out_args.headers) {
3084                 g_list_foreach (out_args.headers, (GFunc)g_free, NULL);
3085                 g_list_free (out_args.headers);
3086         }
3087
3088         *headers = NULL;
3089
3090         return FALSE;
3091 }
3092
3093 static gboolean
3094 invoke_callback_basic_authn (HttpFileHandle *handle, 
3095                              enum AuthnHeaderType authn_which,
3096                              gboolean previous_attempt_failed)
3097 {
3098         GnomeVFSModuleCallbackAuthenticationIn in_args;
3099         GnomeVFSModuleCallbackAuthenticationOut out_args;
3100         gboolean ret;
3101
3102         ret = FALSE;
3103         
3104         memset (&in_args, 0, sizeof (in_args));
3105         memset (&out_args, 0, sizeof (out_args));
3106
3107         in_args.previous_attempt_failed = previous_attempt_failed;
3108                 
3109         in_args.uri = gnome_vfs_uri_to_string (handle->uri, GNOME_VFS_URI_HIDE_NONE);
3110
3111         ret = http_authn_parse_response_header_basic (authn_which, handle->response_headers, &in_args.realm);
3112                 
3113         if (!ret) {
3114                 goto error;
3115         }
3116
3117         DEBUG_HTTP (("Invoking %s authentication callback for uri %s",
3118                 authn_which == AuthnHeader_WWW ? "basic" : "proxy", in_args.uri));
3119
3120         in_args.auth_type = AuthTypeBasic;
3121
3122         ret = gnome_vfs_module_callback_invoke (authn_which == AuthnHeader_WWW 
3123                                                 ? GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION
3124                                                 : GNOME_VFS_MODULE_CALLBACK_HTTP_PROXY_AUTHENTICATION, 
3125                                                 &in_args, sizeof (in_args), 
3126                                                 &out_args, sizeof (out_args)); 
3127
3128         if (!ret) {
3129                 DEBUG_HTTP (("No callback registered"));
3130                 goto error;
3131         }
3132
3133         ret = (out_args.username != NULL);
3134
3135         if (!ret) {
3136                 DEBUG_HTTP (("No username provided by callback"));
3137                 goto error;
3138         }
3139
3140         DEBUG_HTTP (("Back from authentication callback, adding credentials"));
3141
3142         if (authn_which == AuthnHeader_WWW) {
3143                 http_authn_session_add_credentials (handle->uri, out_args.username, out_args.password);
3144         } else /* if (authn_which == AuthnHeader_Proxy) */ {
3145                 proxy_set_authn (out_args.username, out_args.password);
3146         }
3147 error:
3148         g_free (in_args.uri);
3149         g_free (in_args.realm);
3150         g_free (out_args.username);
3151         g_free (out_args.password);
3152
3153         return ret;
3154 }
3155
3156 static int
3157 strcmp_allow_nulls (const char *s1, const char *s2)
3158 {
3159         return strcmp (s1 == NULL ? "" : s1, s2 == NULL ? "" : s2);
3160 }
3161
3162
3163 /* Returns TRUE if the given URL has changed authentication credentials
3164  * from the last request (eg, another thread updated the authn information) 
3165  * or if the application provided new credentials via a callback
3166  *
3167  * prev_authn_header is NULL if the previous request contained no authn information.
3168  */
3169
3170 gboolean
3171 check_authn_retry_request (HttpFileHandle * http_handle,
3172                            enum AuthnHeaderType authn_which,
3173                            const char *prev_authn_header)
3174 {
3175         gboolean ret;
3176         char *current_authn_header;
3177
3178         current_authn_header = NULL;
3179         
3180         g_mutex_lock (gl_mutex);
3181
3182         if (authn_which == AuthnHeader_WWW) {
3183                 current_authn_header = http_authn_get_header_for_uri (http_handle->uri);
3184         } else if (authn_which == AuthnHeader_Proxy) {
3185                 current_authn_header = proxy_get_authn_header_for_uri_nolock (http_handle->uri);
3186         } else {
3187                 g_assert_not_reached ();
3188         }
3189
3190         ret = FALSE;
3191         if (0 == strcmp_allow_nulls (current_authn_header, prev_authn_header)) {
3192                 ret = invoke_callback_basic_authn (http_handle, authn_which, prev_authn_header == NULL);
3193         } else {
3194                 ret = TRUE;
3195         }
3196
3197         g_mutex_unlock (gl_mutex);
3198
3199         g_free (current_authn_header);
3200
3201         return ret;
3202
3203
3204
3205 utime_t
3206 http_util_get_utime (void)
3207 {
3208     struct timeval tmp;
3209     gettimeofday (&tmp, NULL);
3210     return (utime_t)tmp.tv_usec + ((gint64)tmp.tv_sec) * 1000000LL;
3211 }
3212
3213
3214 /* BASE64 code ported from neon (http://www.webdav.org/neon) */
3215 static const gchar b64_alphabet[65] = {
3216         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3217         "abcdefghijklmnopqrstuvwxyz"
3218         "0123456789+/=" };
3219
3220 gchar *
3221 http_util_base64 (const gchar *text)
3222 {
3223         /* The tricky thing about this is doing the padding at the end,
3224          * doing the bit manipulation requires a bit of concentration only */
3225         gchar *buffer, *point;
3226         gint inlen, outlen;
3227
3228         /* Use 'buffer' to store the output. Work out how big it should be...
3229          * This must be a multiple of 4 bytes 
3230          */
3231
3232         inlen = strlen (text);
3233         outlen = (inlen*4)/3;
3234         if ((inlen % 3) > 0) { /* got to pad */
3235                 outlen += 4 - (inlen % 3);
3236         }
3237
3238         buffer = g_malloc (outlen + 1); /* +1 for the \0 */
3239
3240         /* now do the main stage of conversion, 3 bytes at a time,
3241          * leave the trailing bytes (if there are any) for later
3242          */
3243
3244         for (point=buffer; inlen>=3; inlen-=3, text+=3) {
3245                 *(point++) = b64_alphabet[ (*text)>>2 ];
3246                 *(point++) = b64_alphabet[ ((*text)<<4 & 0x30) | (*(text+1))>>4 ];
3247                 *(point++) = b64_alphabet[ ((*(text+1))<<2 & 0x3c) | (*(text+2))>>6 ];
3248                 *(point++) = b64_alphabet[ (*(text+2)) & 0x3f ];
3249         }
3250
3251         /* Now deal with the trailing bytes */
3252         if (inlen) {
3253                 /* We always have one trailing byte */
3254                 *(point++) = b64_alphabet[ (*text)>>2 ];
3255                 *(point++) = b64_alphabet[ ( ((*text)<<4 & 0x30) |
3256                                                                          (inlen==2?(*(text+1))>>4:0) ) ];
3257                 *(point++) = (inlen==1?'=':b64_alphabet[ (*(text+1))<<2 & 0x3c ] );
3258                 *(point++) = '=';
3259         }
3260
3261         /* Null-terminate */
3262         *point = '\0';
3263
3264         return buffer;
3265 }
3266
3267 static gboolean at_least_one_test_failed = FALSE;
3268
3269 static void
3270 test_failed (const char *format, ...)
3271 {
3272         va_list arguments;
3273         char *message;
3274
3275         va_start (arguments, format);
3276         message = g_strdup_vprintf (format, arguments);
3277         va_end (arguments);
3278
3279         g_message ("test failed: %s", message);
3280         at_least_one_test_failed = TRUE;
3281 }
3282
3283 #define VERIFY_BOOLEAN_RESULT(function, expected) \
3284         G_STMT_START {                                                                                  \
3285                 gboolean result = function;                                                             \
3286                 if (! ((result && expected) || (!result && !expected))) {                               \
3287                         test_failed ("%s: returned '%d' expected '%d'", #function, (int)result, (int)expected); \
3288                 }                                                                                       \
3289         } G_STMT_END
3290
3291
3292 static gboolean
3293 http_self_test (void)
3294 {
3295         g_message ("self-test: http\n");
3296
3297         VERIFY_BOOLEAN_RESULT (proxy_should_for_hostname ("localhost"), FALSE);
3298         VERIFY_BOOLEAN_RESULT (proxy_should_for_hostname ("LocalHost"), FALSE);
3299         VERIFY_BOOLEAN_RESULT (proxy_should_for_hostname ("127.0.0.1"), FALSE);
3300         VERIFY_BOOLEAN_RESULT (proxy_should_for_hostname ("127.127.0.1"), FALSE);
3301         VERIFY_BOOLEAN_RESULT (proxy_should_for_hostname ("www.yahoo.com"), TRUE);
3302
3303         return !at_least_one_test_failed;
3304 }
3305
3306 gboolean vfs_module_self_test (void);
3307
3308 gboolean
3309 vfs_module_self_test (void)
3310 {
3311         gboolean ret;
3312
3313         ret = TRUE;
3314
3315         ret = http_authn_self_test () && ret;
3316
3317         ret = http_self_test () && ret;
3318
3319         return ret;
3320 }
3321