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