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