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