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