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
5 Copyright (C) 1999 Free Software Foundation
6 Copyright (C) 2000-2001 Eazel, Inc
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.
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.
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.
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
32 - Handle persistent connections. */
35 #include "http-method.h"
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>
60 #include <stdlib.h> /* for atoi */
62 #include <sys/socket.h>
67 #ifndef HAVE_G_STR_HAS_SUFFIX
68 gboolean g_str_has_suffix(const gchar *str, const gchar *suffix);
71 #ifdef DEBUG_HTTP_ENABLE
73 http_debug_printf (char *fmt, ...)
82 out = g_strdup_vprintf (fmt, args);
84 fprintf (stderr, "HTTP: [0x%08x] [%p] %s\n",
85 (unsigned int) http_util_get_utime (),
86 g_thread_self (), out);
91 #endif /* DEBUG_HTTP_ENABLE */
93 /* What do we qualify ourselves as? */
94 /* FIXME bugzilla.gnome.org 41160: "gnome-vfs/1.0.0" may not be good. */
95 #define USER_AGENT_STRING "gnome-vfs/" VERSION
97 /* Custom User-Agent environment variable */
98 #define CUSTOM_USER_AGENT_VARIABLE "GNOME_VFS_HTTP_USER_AGENT"
100 /* Standard HTTP[S] port. */
101 #define DEFAULT_HTTP_PORT 80
102 #define DEFAULT_HTTPS_PORT 443
104 /* Standard HTTP proxy port */
105 #define DEFAULT_HTTP_PROXY_PORT 8080
107 /* Maximum amount of data to read if seek()ed forward. Otherwise make a new connection. */
108 #define MAX_BUFFER_SEEK_SKIP_READ 0x10000
110 /* GConf paths and keys */
111 #define PATH_GCONF_GNOME_VFS "/system/http_proxy"
112 #define ITEM_GCONF_HTTP_PROXY_PORT "port"
113 #define ITEM_GCONF_HTTP_PROXY_HOST "host"
114 #define KEY_GCONF_HTTP_PROXY_PORT (PATH_GCONF_GNOME_VFS "/" ITEM_GCONF_HTTP_PROXY_PORT)
115 #define KEY_GCONF_HTTP_PROXY_HOST (PATH_GCONF_GNOME_VFS "/" ITEM_GCONF_HTTP_PROXY_HOST)
117 #define ITEM_GCONF_USE_HTTP_PROXY "use_http_proxy"
118 #define KEY_GCONF_USE_HTTP_PROXY (PATH_GCONF_GNOME_VFS "/" ITEM_GCONF_USE_HTTP_PROXY)
120 #define KEY_GCONF_HTTP_AUTH_USER (PATH_GCONF_GNOME_VFS "/" "authentication_user")
121 #define KEY_GCONF_HTTP_AUTH_PW (PATH_GCONF_GNOME_VFS "/" "authentication_password")
122 #define KEY_GCONF_HTTP_USE_AUTH (PATH_GCONF_GNOME_VFS "/" "use_authentication")
124 #define KEY_GCONF_HTTP_PROXY_IGNORE_HOSTS (PATH_GCONF_GNOME_VFS "/" "ignore_hosts")
127 /* Some status code validation macros. */
128 #define HTTP_20X(x) (((x) >= 200) && ((x) < 300))
129 #define HTTP_PARTIAL(x) ((x) == HTTP_STATUS_PARTIAL_CONTENTS)
130 #define HTTP_REDIRECTED(x) (((x) == HTTP_STATUS_MOVED_PERMANENTLY) \
131 || ((x) == HTTP_STATUS_MOVED_TEMPORARILY))
133 /* HTTP/1.1 status codes from RFC2068, provided for reference. */
134 /* Successful 2xx. */
135 #define HTTP_STATUS_OK 200
136 #define HTTP_STATUS_CREATED 201
137 #define HTTP_STATUS_ACCEPTED 202
138 #define HTTP_STATUS_NON_AUTHORITATIVE 203
139 #define HTTP_STATUS_NO_CONTENT 204
140 #define HTTP_STATUS_RESET_CONTENT 205
141 #define HTTP_STATUS_PARTIAL_CONTENTS 206
143 /* Redirection 3xx. */
144 #define HTTP_STATUS_MULTIPLE_CHOICES 300
145 #define HTTP_STATUS_MOVED_PERMANENTLY 301
146 #define HTTP_STATUS_MOVED_TEMPORARILY 302
147 #define HTTP_STATUS_SEE_OTHER 303
148 #define HTTP_STATUS_NOT_MODIFIED 304
149 #define HTTP_STATUS_USE_PROXY 305
151 /* Client error 4xx. */
152 #define HTTP_STATUS_BAD_REQUEST 400
153 #define HTTP_STATUS_UNAUTHORIZED 401
154 #define HTTP_STATUS_PAYMENT_REQUIRED 402
155 #define HTTP_STATUS_FORBIDDEN 403
156 #define HTTP_STATUS_NOT_FOUND 404
157 #define HTTP_STATUS_METHOD_NOT_ALLOWED 405
158 #define HTTP_STATUS_NOT_ACCEPTABLE 406
159 #define HTTP_STATUS_PROXY_AUTH_REQUIRED 407
160 #define HTTP_STATUS_REQUEST_TIMEOUT 408
161 #define HTTP_STATUS_CONFLICT 409
162 #define HTTP_STATUS_GONE 410
163 #define HTTP_STATUS_LENGTH_REQUIRED 411
164 #define HTTP_STATUS_PRECONDITION_FAILED 412
165 #define HTTP_STATUS_REQENTITY_TOO_LARGE 413
166 #define HTTP_STATUS_REQURI_TOO_LARGE 414
167 #define HTTP_STATUS_UNSUPPORTED_MEDIA 415
168 #define HTTP_STATUS_LOCKED 423
170 /* Server errors 5xx. */
171 #define HTTP_STATUS_INTERNAL 500
172 #define HTTP_STATUS_NOT_IMPLEMENTED 501
173 #define HTTP_STATUS_BAD_GATEWAY 502
174 #define HTTP_STATUS_UNAVAILABLE 503
175 #define HTTP_STATUS_GATEWAY_TIMEOUT 504
176 #define HTTP_STATUS_UNSUPPORTED_VERSION 505
177 #define HTTP_STATUS_INSUFFICIENT_STORAGE 507
183 /* Global variables used by the HTTP proxy config */
184 static GConfClient * gl_client = NULL;
185 static GMutex *gl_mutex = NULL; /* This mutex protects preference values
186 * and ensures serialization of authentication
189 static gchar *gl_http_proxy = NULL;
190 static gchar *gl_http_proxy_auth = NULL;
191 static GSList *gl_ignore_hosts = NULL; /* Elements are strings. */
192 static GSList *gl_ignore_addrs = NULL; /* Elements are ProxyHostAddrs */
194 /* Store IP addresses that may represent network or host addresses and may be
206 struct in6_addr addr6;
207 struct in6_addr mask6;
212 GnomeVFSSocketBuffer *socket_buffer;
215 /* The list of headers returned with this response, newlines removed */
216 GList *response_headers;
218 /* File info for this file */
219 GnomeVFSFileInfo *file_info;
221 /* File offset of current pointer of 'socket_buffer'. */
222 GnomeVFSFileOffset socket_buffer_offset;
224 /* Offset; Current file position. */
225 GnomeVFSFileOffset offset;
227 /* Bytes to be written... */
228 GByteArray *to_be_written;
230 /* List of GnomeVFSFileInfo from a directory listing */
233 /* The last HTTP status code returned */
237 static GnomeVFSResult resolve_409 (GnomeVFSMethod *method,
239 GnomeVFSContext *context);
240 static void proxy_set_authn (const char *username,
241 const char *password);
242 static void proxy_unset_authn (void);
243 static gboolean invoke_callback_send_additional_headers (GnomeVFSURI *uri,
245 static gboolean invoke_callback_headers_received (HttpFileHandle *handle);
246 static gboolean invoke_callback_basic_authn (HttpFileHandle *handle,
247 enum AuthnHeaderType authn_which,
248 gboolean previous_attempt_failed);
249 static gboolean check_authn_retry_request (HttpFileHandle * http_handle,
250 enum AuthnHeaderType authn_which,
251 const char *prev_authn_header);
252 static void parse_ignore_host (gpointer data,
255 static void ipv6_network_addr (const struct in6_addr *addr,
256 const struct in6_addr *mask,
257 struct in6_addr *res);
259 /*Check whether the node is IPv6 enabled.*/
265 s = socket (AF_INET6, SOCK_STREAM, 0);
275 static GnomeVFSFileInfo *
276 defaults_file_info_new (void)
278 GnomeVFSFileInfo *ret;
280 /* Fill up the file info structure with default values */
281 /* Default to REGULAR unless we find out later via a PROPFIND that it's a collection */
283 ret = gnome_vfs_file_info_new();
285 ret->type = GNOME_VFS_FILE_TYPE_REGULAR;
286 ret->flags = GNOME_VFS_FILE_FLAGS_NONE;
289 GNOME_VFS_FILE_INFO_FIELDS_TYPE
290 | GNOME_VFS_FILE_INFO_FIELDS_FLAGS;
296 /* Do not allocate the 'handle' memory. */
298 http_file_handle_init (HttpFileHandle *handle,
299 GnomeVFSSocketBuffer *socket_buffer,
302 memset (handle, 0, sizeof (*handle));
304 handle->socket_buffer = socket_buffer;
305 handle->uri_string = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE );
307 gnome_vfs_uri_ref(handle->uri);
309 handle->file_info = defaults_file_info_new();
310 handle->file_info->name = gnome_vfs_uri_extract_short_name (uri);
313 static HttpFileHandle *
314 http_file_handle_alloc (void)
316 return g_new0 (HttpFileHandle, 1);
319 static HttpFileHandle *
320 http_file_handle_new (GnomeVFSSocketBuffer *socket_buffer,
323 HttpFileHandle *result;
325 result = http_file_handle_alloc ();
326 http_file_handle_init (result, socket_buffer, uri);
331 /* Does not free the 'handle' memory. */
333 http_file_handle_clear (HttpFileHandle *handle)
335 if (handle == NULL || handle->uri == NULL) {
339 gnome_vfs_uri_unref(handle->uri);
340 gnome_vfs_file_info_unref (handle->file_info);
341 g_free (handle->uri_string);
342 if (handle->to_be_written) {
343 g_byte_array_free(handle->to_be_written, TRUE);
346 g_list_foreach (handle->response_headers, (GFunc) g_free, NULL);
347 g_list_free (handle->response_headers);
349 g_list_foreach(handle->files, (GFunc)gnome_vfs_file_info_unref, NULL);
350 g_list_free(handle->files);
352 /* Structure is now fully cleared: */
353 memset (handle, 0, sizeof (*handle));
357 http_file_handle_destroy (HttpFileHandle *handle)
359 if (handle == NULL) {
363 http_file_handle_clear (handle);
368 /* The following comes from GNU Wget with minor changes by myself.
369 Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc. */
370 /* Parse the HTTP status line, which is of format:
372 HTTP-Version SP Status-Code SP Reason-Phrase
374 The function returns the status-code, or -1 if the status line is
375 malformed. The pointer to reason-phrase is returned in RP. */
377 parse_status (const char *cline,
378 guint *status_return)
380 /* (the variables must not be named `major' and `minor', because
381 that breaks compilation with SunOS4 cc.) */
384 const guchar *p, *line;
386 line = (const guchar *)cline;
388 /* The standard format of HTTP-Version is: `HTTP/X.Y', where X is
389 major version, and Y is minor version. */
390 if (strncmp (line, "HTTP/", 5) == 0) {
393 /* Calculate major HTTP version. */
395 for (mjr = 0; g_ascii_isdigit (*line); line++)
396 mjr = 10 * mjr + (*line - '0');
397 if (*line != '.' || p == line)
401 /* Calculate minor HTTP version. */
403 for (mnr = 0; g_ascii_isdigit (*line); line++)
404 mnr = 10 * mnr + (*line - '0');
405 if (*line != ' ' || p == line)
407 /* Wget will accept only 1.0 and higher HTTP-versions. The value of
408 minor version can be safely ignored. */
412 } else if (strncmp (line, "ICY ", 4) == 0) {
413 /* FIXME: workaround for broken ShoutCast and IceCast status replies.
414 * They send things like "ICY 200 OK" instead of "HTTP/1.0 200 OK".
415 * Is there a better way to handle this?
424 /* Calculate status code. */
425 if (!(g_ascii_isdigit (*line) && g_ascii_isdigit (line[1]) && g_ascii_isdigit (line[2])))
427 statcode = 100 * (*line - '0') + 10 * (line[1] - '0') + (line[2] - '0');
429 *status_return = statcode;
433 static GnomeVFSResult
434 http_status_to_vfs_result (guint status)
436 if (HTTP_20X (status))
439 /* FIXME bugzilla.gnome.org 41163 */
440 /* mfleming--I've improved the situation slightly, but more
441 * test cases need to be written to ensure that HTTP (esp DAV) does compatibile
442 * things with the normal file method
446 case HTTP_STATUS_PRECONDITION_FAILED:
447 /* This mapping is certainly true for MOVE with Overwrite: F, otherwise not so true */
448 return GNOME_VFS_ERROR_FILE_EXISTS;
449 case HTTP_STATUS_UNAUTHORIZED:
450 case HTTP_STATUS_PROXY_AUTH_REQUIRED:
451 case HTTP_STATUS_FORBIDDEN:
452 /* Note that FORBIDDEN can also be returned on a MOVE in a case which
453 * should be VFS_ERROR_BAD_PARAMETERS
455 return GNOME_VFS_ERROR_ACCESS_DENIED;
456 case HTTP_STATUS_NOT_FOUND:
457 return GNOME_VFS_ERROR_NOT_FOUND;
458 case HTTP_STATUS_METHOD_NOT_ALLOWED:
459 /* Note that METHOD_NOT_ALLOWED is also returned in a PROPFIND in a case which
460 * should be FILE_EXISTS. This is handled in do_make_directory
462 case HTTP_STATUS_BAD_REQUEST:
463 case HTTP_STATUS_NOT_IMPLEMENTED:
464 case HTTP_STATUS_UNSUPPORTED_VERSION:
465 return GNOME_VFS_ERROR_NOT_SUPPORTED;
466 case HTTP_STATUS_CONFLICT:
467 /* _CONFLICT's usually happen when collection paths don't exist */
468 return GNOME_VFS_ERROR_NOT_FOUND;
469 case HTTP_STATUS_LOCKED:
470 /* Maybe we need a separate GNOME_VFS_ERROR_LOCKED? */
471 return GNOME_VFS_ERROR_DIRECTORY_BUSY;
472 case HTTP_STATUS_INSUFFICIENT_STORAGE:
473 return GNOME_VFS_ERROR_NO_SPACE;
475 return GNOME_VFS_ERROR_GENERIC;
479 /* Header parsing routines. */
482 header_value_to_number (const char *header_value,
490 for (result = 0; g_ascii_isdigit (*p); p++)
491 result = 10 * result + (*p - '0');
501 set_content_length (HttpFileHandle *handle,
507 result = header_value_to_number (value, &size);
511 DEBUG_HTTP (("Expected size is %lu.", size));
512 handle->file_info->size = size;
513 handle->file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_SIZE;
518 strip_semicolon (const char *value)
522 p = strchr (value, ';');
525 return g_strndup (value, p - value);
528 return g_strdup (value);
533 set_content_type (HttpFileHandle *handle,
536 g_free (handle->file_info->mime_type);
538 handle->file_info->mime_type = strip_semicolon (value);
539 handle->file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
545 set_last_modified (HttpFileHandle *handle,
550 if (! gnome_vfs_atotm (value, &time))
553 handle->file_info->mtime = time;
554 handle->file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MTIME;
559 set_access_time (HttpFileHandle *handle,
564 if (! gnome_vfs_atotm (value, &time))
567 handle->file_info->atime = time;
568 handle->file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_ATIME;
574 gboolean (* set_func) (HttpFileHandle *handle, const char *value);
576 typedef struct _Header Header;
578 static Header headers[] = {
579 { "Content-Length", set_content_length },
580 { "Content-Type", set_content_type },
581 { "Last-Modified", set_last_modified },
582 { "Date", set_access_time },
587 check_header (const char *header,
592 for (p = header, q = name; *p != '\0' && *q != '\0'; p++, q++) {
593 if (g_ascii_tolower (*p) != g_ascii_tolower (*q))
597 if (*q != '\0' || *p != ':')
601 while (*p == ' ' || *p == '\t')
608 parse_header (HttpFileHandle *handle,
613 for (i = 0; headers[i].name != NULL; i++) {
616 value = check_header (header, headers[i].name);
618 return (* headers[i].set_func) (handle, value);
621 /* Simply ignore headers we don't know. */
625 /* Header/status reading. */
627 static GnomeVFSResult
628 get_header (GnomeVFSSocketBuffer *socket_buffer,
631 GnomeVFSResult result;
632 GnomeVFSFileSize bytes_read;
635 ANALYZE_HTTP ("==> +get_header");
637 g_string_truncate (s, 0);
643 /* ANALYZE_HTTP ("==> +get_header read"); */
644 result = gnome_vfs_socket_buffer_read (socket_buffer, &c, 1,
646 /* ANALYZE_HTTP ("==> -get_header read"); */
648 if (result != GNOME_VFS_OK) {
651 if (bytes_read == 0) {
652 return GNOME_VFS_ERROR_EOF;
656 /* Handle continuation lines. */
657 if (count != 0 && (count != 1 || s->str[0] != '\r')) {
660 result = gnome_vfs_socket_buffer_peekc (
661 socket_buffer, &next);
662 if (result != GNOME_VFS_OK) {
666 if (next == '\t' || next == ' ') {
668 && s->str[count - 1] == '\r')
669 s->str[count - 1] = '\0';
674 if (count > 0 && s->str[count - 1] == '\r')
675 s->str[count - 1] = '\0';
678 g_string_append_c (s, c);
685 ANALYZE_HTTP ("==> -get_header");
690 /* rename this function? */
691 static GnomeVFSResult
692 init_handle (GnomeVFSURI *uri,
693 GnomeVFSSocketBuffer *socket_buffer,
694 GnomeVFSContext *context,
695 /* OUT */ HttpFileHandle *handle)
697 GString *header_string;
698 GnomeVFSResult result;
701 g_return_val_if_fail (handle != NULL, GNOME_VFS_ERROR_INTERNAL);
703 http_file_handle_init (handle, socket_buffer, uri);
705 header_string = g_string_new (NULL);
707 ANALYZE_HTTP ("==> +init_handle");
709 /* This is the status report string, which is the first header. */
710 result = get_header (socket_buffer, header_string);
711 if (result != GNOME_VFS_OK) {
715 if (!parse_status (header_string->str, &server_status)) {
716 /* An unparsable status line is fatal */
717 result = GNOME_VFS_ERROR_GENERIC;
721 handle->server_status = server_status;
723 ANALYZE_HTTP ("==> +init_handle: fetching headers");
725 /* Header fetching loop. */
727 result = get_header (socket_buffer, header_string);
728 if (result != GNOME_VFS_OK) {
732 /* Empty header ends header section. */
733 if (header_string->str[0] == '\0') {
737 handle->response_headers = g_list_prepend (handle->response_headers,
738 g_strdup (header_string->str));
740 /* We don't really care if we successfully parse the
741 * header or not. It might be nice to tell someone we
742 * found a header we can't parse, but it's not clear
743 * who would be interested or how we tell them. In the
744 * past we would return NOT_FOUND if any header could
745 * not be parsed, but that seems wrong.
747 parse_header (handle, header_string->str);
750 invoke_callback_headers_received (handle);
752 ANALYZE_HTTP ("==> -init_handle: fetching headers");
754 if (result != GNOME_VFS_OK) {
758 if (! HTTP_20X (server_status) && !HTTP_REDIRECTED(server_status)) {
759 result = http_status_to_vfs_result (server_status);
763 result = GNOME_VFS_OK;
765 g_string_free (header_string, TRUE);
767 ANALYZE_HTTP ("==> -init_handle");
772 * Here's how the gconf gnome-vfs HTTP proxy variables
773 * are intended to be used
775 * /system/http_proxy/use_http_proxy
777 * If set to TRUE, the client should use an HTTP proxy to connect to all
778 * servers (except those specified in the ignore_hosts key -- see below).
779 * The proxy is specified in other gconf variables below.
781 * /system/http_proxy/host
783 * The hostname of the HTTP proxy this client should use. If
784 * use-http-proxy is TRUE, this should be set. If it is not set, the
785 * application should behave as if use-http-proxy is was set to FALSE.
787 * /system/http_proxy/port
789 * The port number on the HTTP proxy host that the client should connect to
790 * If use_http_proxy and host are set but this is not set, the application
791 * should use a default port value of 8080
793 * /system/http_proxy/authentication-user
795 * Username to pass to an authenticating HTTP proxy.
797 * /system/http_proxy/authentication_password
799 * Password to pass to an authenticating HTTP proxy.
801 * /system/http_proxy/use-authentication
803 * TRUE if the client should pass http-proxy-authorization-user and
804 * http-proxy-authorization-password an HTTP proxy
806 * /system/http_proxy/ignore_hosts
807 * Type: list of strings
808 * A list of hosts (hostnames, wildcard domains, IP addresses, and CIDR
809 * network addresses) that should be accessed directly.
813 construct_gl_http_proxy (gboolean use_proxy)
815 g_free (gl_http_proxy);
816 gl_http_proxy = NULL;
818 g_slist_foreach (gl_ignore_hosts, (GFunc) g_free, NULL);
819 g_slist_free (gl_ignore_hosts);
820 gl_ignore_hosts = NULL;
821 g_slist_foreach (gl_ignore_addrs, (GFunc) g_free, NULL);
822 g_slist_free (gl_ignore_addrs);
823 gl_ignore_addrs = NULL;
830 proxy_host = gconf_client_get_string (gl_client, KEY_GCONF_HTTP_PROXY_HOST, NULL);
831 proxy_port = gconf_client_get_int (gl_client, KEY_GCONF_HTTP_PROXY_PORT, NULL);
834 if (0 != proxy_port && 0xffff >= (unsigned) proxy_port) {
835 gl_http_proxy = g_strdup_printf ("%s:%u", proxy_host, (unsigned)proxy_port);
837 gl_http_proxy = g_strdup_printf ("%s:%u", proxy_host, (unsigned)DEFAULT_HTTP_PROXY_PORT);
839 DEBUG_HTTP (("New HTTP proxy: '%s'", gl_http_proxy));
841 DEBUG_HTTP (("HTTP proxy unset"));
847 ignore = gconf_client_get_list (gl_client, KEY_GCONF_HTTP_PROXY_IGNORE_HOSTS, GCONF_VALUE_STRING, NULL);
848 g_slist_foreach (ignore, (GFunc) parse_ignore_host, NULL);
849 g_slist_foreach (ignore, (GFunc) g_free, NULL);
850 g_slist_free (ignore);
856 parse_ignore_host (gpointer data, gpointer user_data)
858 gchar *hostname, *input, *netmask;
859 gboolean ip_addr = FALSE, has_error = FALSE;
860 struct in_addr host, mask;
862 struct in6_addr host6, mask6;
867 input = (gchar*) data;
868 elt = g_new0 (ProxyHostAddr, 1);
869 if ((netmask = strchr (input, '/')) != NULL) {
870 hostname = g_strndup (input, netmask - input);
874 hostname = g_ascii_strdown (input, -1);
876 if (inet_pton (AF_INET, hostname, &host) > 0) {
878 elt->type = PROXY_IPv4;
879 elt->addr.s_addr = host.s_addr;
882 gint width = strtol (netmask, &endptr, 10);
884 if (*endptr != '\0' || width < 0 || width > 32) {
887 elt->mask.s_addr = htonl(~0 << width);
888 elt->addr.s_addr &= mask.s_addr;
891 elt->mask.s_addr = 0xffffffff;
895 else if (have_ipv6 () && inet_pton (AF_INET6, hostname, &host6) > 0) {
897 elt->type = PROXY_IPv6;
898 for (i = 0; i < 16; ++i) {
899 elt->addr6.s6_addr[i] = host6.s6_addr[i];
903 gint width = strtol (netmask, &endptr, 10);
905 if (*endptr != '\0' || width < 0 || width > 128) {
908 for (i = 0; i < 16; ++i) {
909 elt->mask6.s6_addr[i] = 0;
911 for (i=0; i < width/8; i++) {
912 elt->mask6.s6_addr[i] = 0xff;
914 elt->mask6.s6_addr[i] = (0xff << (8 - width % 8)) & 0xff;
915 ipv6_network_addr (&elt->addr6, &mask6, &elt->addr6);
918 for (i = 0; i < 16; ++i) {
919 elt->mask6.s6_addr[i] = 0xff;
927 gchar *dst = g_new0 (gchar, INET_ADDRSTRLEN);
929 gl_ignore_addrs = g_slist_append (gl_ignore_addrs, elt);
930 DEBUG_HTTP (("Host %s/%s does not go through proxy.",
932 inet_ntop(AF_INET, &elt->mask, dst, INET_ADDRSTRLEN)));
937 /* It is a hostname. */
938 gl_ignore_hosts = g_slist_append (gl_ignore_hosts, hostname);
939 DEBUG_HTTP (("Host %s does not go through proxy.", hostname));
944 set_proxy_auth (gboolean use_proxy_auth)
949 auth_user = gconf_client_get_string (gl_client, KEY_GCONF_HTTP_AUTH_USER, NULL);
950 auth_pw = gconf_client_get_string (gl_client, KEY_GCONF_HTTP_AUTH_PW, NULL);
952 if (use_proxy_auth) {
953 proxy_set_authn (auth_user, auth_pw);
954 DEBUG_HTTP (("New HTTP proxy auth user: '%s'", auth_user));
956 proxy_unset_authn ();
957 DEBUG_HTTP (("HTTP proxy auth unset"));
965 * sig_gconf_value_changed
966 * GGconf notify function for when HTTP proxy GConf key has changed.
969 notify_gconf_value_changed (GConfClient *client,
976 key = gconf_entry_get_key (entry);
978 if (strcmp (key, KEY_GCONF_USE_HTTP_PROXY) == 0
979 || strcmp (key, KEY_GCONF_HTTP_PROXY_IGNORE_HOSTS) == 0
980 || strcmp (key, KEY_GCONF_HTTP_PROXY_HOST) == 0
981 || strcmp (key, KEY_GCONF_HTTP_PROXY_PORT) == 0) {
982 gboolean use_proxy_value;
984 g_mutex_lock (gl_mutex);
986 /* Check and see if we are using the proxy */
987 use_proxy_value = gconf_client_get_bool (gl_client, KEY_GCONF_USE_HTTP_PROXY, NULL);
988 construct_gl_http_proxy (use_proxy_value);
990 g_mutex_unlock (gl_mutex);
991 } else if (strcmp (key, KEY_GCONF_HTTP_AUTH_USER) == 0
992 || strcmp (key, KEY_GCONF_HTTP_AUTH_PW) == 0
993 || strcmp (key, KEY_GCONF_HTTP_USE_AUTH) == 0) {
994 gboolean use_proxy_auth;
996 g_mutex_lock (gl_mutex);
998 use_proxy_auth = gconf_client_get_bool (gl_client, KEY_GCONF_HTTP_USE_AUTH, NULL);
999 set_proxy_auth (use_proxy_auth);
1001 g_mutex_unlock (gl_mutex);
1006 * host_port_from_string
1007 * splits a <host>:<port> formatted string into its separate components
1010 host_port_from_string (const char *http_proxy,
1011 char **p_proxy_host,
1012 guint *p_proxy_port)
1016 port_part = strchr (http_proxy, ':');
1018 if (port_part && '\0' != ++port_part && p_proxy_port) {
1019 *p_proxy_port = (guint) strtoul (port_part, NULL, 10);
1020 } else if (p_proxy_port) {
1021 *p_proxy_port = DEFAULT_HTTP_PROXY_PORT;
1025 if ( port_part != http_proxy ) {
1026 *p_proxy_host = g_strndup (http_proxy, port_part - http_proxy - 1);
1035 /* FIXME: should be done using AC_REPLACE_FUNCS */
1036 #ifndef HAVE_INET_PTON
1038 inet_pton(int af, const char *hostname, void *pton)
1041 if (!inet_aton(hostname, &in))
1043 memcpy(pton, &in, sizeof(in));
1050 ipv6_network_addr (const struct in6_addr *addr, const struct in6_addr *mask, struct in6_addr *res)
1054 for (i = 0; i < 16; ++i) {
1055 res->s6_addr[i] = addr->s6_addr[i] & mask->s6_addr[i];
1061 proxy_should_for_hostname (const char *hostname)
1064 struct in6_addr in6, net6;
1068 ProxyHostAddr *addr;
1072 if (inet_pton (AF_INET, hostname, &in) > 0) {
1073 for (elt = gl_ignore_addrs; elt; elt = g_slist_next (elt)) {
1074 addr = (ProxyHostAddr*) (elt->data);
1075 if (addr->type == PROXY_IPv4
1076 && (in.s_addr & addr->mask.s_addr) == addr->addr.s_addr) {
1077 DEBUG_HTTP (("Host %s using direct connection.", hostname));
1083 else if (have_ipv6 () && inet_pton (AF_INET6, hostname, &in6)) {
1084 for (elt = gl_ignore_addrs; elt; elt = g_slist_next (elt)) {
1085 addr = (ProxyHostAddr*) (elt->data);
1086 ipv6_network_addr (&in6, &addr->mask6, &net6);
1087 if (addr->type == PROXY_IPv6
1088 && IN6_ARE_ADDR_EQUAL (&net6, &addr->addr6)) {
1089 DEBUG_HTTP (("Host %s using direct connection.", hostname));
1092 /* Handle IPv6-wrapped IPv4 addresses. */
1093 else if (addr->type == PROXY_IPv4
1094 && IN6_IS_ADDR_V4MAPPED (&net6)) {
1097 v4addr = net6.s6_addr[12] << 24 | net6.s6_addr[13] << 16 | net6.s6_addr[14] << 8 | net6.s6_addr[15];
1098 if ((v4addr & addr->mask.s_addr) != addr->addr.s_addr) {
1099 DEBUG_HTTP (("Host %s using direct connection.", hostname));
1106 /* All hostnames (foo.bar.com) -- independent of IPv4 or IPv6 */
1108 /* If there are IPv6 addresses in the ignore_hosts list but we do not
1109 * have IPv6 available at runtime, then those addresses will also fall
1110 * through to here (and harmlessly fail to match). */
1112 gchar *hn = g_ascii_strdown (hostname, -1);
1114 for (elt = gl_ignore_hosts; elt; elt = g_slist_next (elt)) {
1115 if (*(gchar*) (elt->data) == '*' ) {
1116 if (g_str_has_suffix (hn,
1117 (gchar*) (elt->data) + 1)) {
1118 DEBUG_HTTP (("Host %s using direct connection.", hn));
1123 else if (strcmp (hn, elt->data) == 0) {
1124 DEBUG_HTTP (("Host %s using direct connection.", hn));
1135 proxy_get_authn_header_for_uri_nolock (GnomeVFSURI * uri)
1141 /* FIXME this needs to be atomic */
1142 if (gl_http_proxy_auth != NULL) {
1143 ret = g_strdup_printf ("Proxy-Authorization: Basic %s\r\n", gl_http_proxy_auth);
1150 proxy_get_authn_header_for_uri (GnomeVFSURI * uri)
1154 g_mutex_lock (gl_mutex);
1156 ret = proxy_get_authn_header_for_uri_nolock (uri);
1158 g_mutex_unlock (gl_mutex);
1165 * Retrives an appropriate HTTP proxy for a given toplevel uri
1166 * Currently, only a single HTTP proxy is implemented (there's no way for
1167 * specifying non-proxy domain names's). Returns FALSE if the connect should
1168 * take place directly
1172 GnomeVFSToplevelURI * toplevel_uri,
1173 gchar **p_proxy_host, /* Callee must free */
1174 guint *p_proxy_port) /* Callee must free */
1178 ret = proxy_should_for_hostname (toplevel_uri->host_name);
1180 g_mutex_lock (gl_mutex);
1182 if (ret && gl_http_proxy != NULL) {
1183 ret = host_port_from_string (gl_http_proxy, p_proxy_host, p_proxy_port);
1185 p_proxy_host = NULL;
1186 p_proxy_port = NULL;
1190 g_mutex_unlock (gl_mutex);
1196 proxy_set_authn (const char *username, const char *password)
1200 g_free (gl_http_proxy_auth);
1201 gl_http_proxy_auth = NULL;
1203 credentials = g_strdup_printf ("%s:%s",
1204 username == NULL ? "" : username,
1205 password == NULL ? "" : password);
1207 gl_http_proxy_auth = http_util_base64 (credentials);
1209 g_free (credentials);
1213 proxy_unset_authn (void)
1215 g_free (gl_http_proxy_auth);
1216 gl_http_proxy_auth = NULL;
1220 static GnomeVFSResult
1221 https_proxy (GnomeVFSSocket **socket_return,
1227 /* use CONNECT to do https proxying. It goes something like this:
1228 * >CONNECT server:port HTTP/1.0
1230 * <HTTP/1.0 200 Connection-established
1231 * <Proxy-agent: Apache/1.3.19 (Unix) Debian/GNU
1233 * and then we've got an open connection.
1235 * So we sent "CONNECT server:port HTTP/1.0\r\n\r\n"
1236 * Check the HTTP status.
1237 * Wait for "\r\n\r\n"
1238 * Start doing the SSL dance.
1241 GnomeVFSResult result;
1242 GnomeVFSInetConnection *http_connection;
1243 GnomeVFSSocket *http_socket;
1244 GnomeVFSSocket *https_socket;
1247 GnomeVFSFileSize bytes;
1251 result = gnome_vfs_inet_connection_create (&http_connection,
1252 proxy_host, proxy_port, NULL);
1254 if (result != GNOME_VFS_OK) {
1258 fd = gnome_vfs_inet_connection_get_fd (http_connection);
1260 http_socket = gnome_vfs_inet_connection_to_socket (http_connection);
1262 buffer = g_strdup_printf ("CONNECT %s:%d HTTP/1.0\r\n\r\n",
1263 server_host, server_port);
1264 result = gnome_vfs_socket_write (http_socket, buffer, strlen(buffer),
1268 if (result != GNOME_VFS_OK) {
1269 gnome_vfs_socket_close (http_socket);
1273 buffer = proxy_get_authn_header_for_uri (NULL); /* FIXME need uri */
1274 if (buffer != NULL) {
1275 result = gnome_vfs_socket_write (http_socket, buffer,
1276 strlen(buffer), &bytes);
1280 if (result != GNOME_VFS_OK) {
1281 gnome_vfs_socket_close (http_socket);
1286 buffer = g_malloc0 (bytes);
1288 result = gnome_vfs_socket_read (http_socket, buffer, bytes-1, &bytes);
1290 if (result != GNOME_VFS_OK) {
1291 gnome_vfs_socket_close (http_socket);
1296 if (!parse_status (buffer, &status_code)) {
1297 gnome_vfs_socket_close (http_socket);
1299 return GNOME_VFS_ERROR_PROTOCOL_ERROR;
1302 result = http_status_to_vfs_result (status_code);
1304 if (result != GNOME_VFS_OK) {
1305 gnome_vfs_socket_close (http_socket);
1310 /* okay - at this point we've read some stuff from the socket.. */
1311 /* FIXME: for now we'll assume thats all the headers and nothing but. */
1315 result = gnome_vfs_ssl_create_from_fd (&ssl, fd);
1317 if (result != GNOME_VFS_OK) {
1318 gnome_vfs_socket_close (http_socket);
1322 https_socket = gnome_vfs_ssl_to_socket (ssl);
1324 *socket_return = https_socket;
1326 return GNOME_VFS_OK;
1331 static GnomeVFSResult
1333 GnomeVFSToplevelURI *toplevel_uri,
1334 /* OUT */ GnomeVFSSocketBuffer **p_socket_buffer,
1335 /* OUT */ gboolean * p_proxy_connect)
1340 GnomeVFSResult result;
1341 GnomeVFSCancellation * cancellation;
1342 GnomeVFSInetConnection *connection;
1344 GnomeVFSSocket *socket;
1345 gboolean https = FALSE;
1347 cancellation = gnome_vfs_context_get_cancellation (
1348 gnome_vfs_context_peek_current ());
1350 g_return_val_if_fail (p_socket_buffer != NULL, GNOME_VFS_ERROR_INTERNAL);
1351 g_return_val_if_fail (p_proxy_connect != NULL, GNOME_VFS_ERROR_INTERNAL);
1352 g_return_val_if_fail (toplevel_uri != NULL, GNOME_VFS_ERROR_INTERNAL);
1354 if (!g_ascii_strcasecmp (gnome_vfs_uri_get_scheme (&toplevel_uri->uri),
1356 if (!gnome_vfs_ssl_enabled ()) {
1357 return GNOME_VFS_ERROR_NOT_SUPPORTED;
1362 if (toplevel_uri->host_port == 0) {
1364 host_port = DEFAULT_HTTPS_PORT;
1366 host_port = DEFAULT_HTTP_PORT;
1369 host_port = toplevel_uri->host_port;
1372 ANALYZE_HTTP ("==> +Making connection");
1374 if (toplevel_uri->host_name == NULL) {
1375 result = GNOME_VFS_ERROR_INVALID_URI;
1379 if (proxy_for_uri (toplevel_uri, &proxy_host, &proxy_port)) {
1381 *p_proxy_connect = FALSE;
1383 result = https_proxy (&socket, proxy_host, proxy_port,
1384 toplevel_uri->host_name, host_port);
1386 g_free (proxy_host);
1389 if (result != GNOME_VFS_OK) {
1394 *p_proxy_connect = TRUE;
1396 result = gnome_vfs_inet_connection_create (&connection,
1400 if (result != GNOME_VFS_OK) {
1403 socket = gnome_vfs_inet_connection_to_socket
1406 g_free (proxy_host);
1410 *p_proxy_connect = FALSE;
1413 result = gnome_vfs_ssl_create (&ssl,
1414 toplevel_uri->host_name, host_port);
1416 if (result != GNOME_VFS_OK) {
1419 socket = gnome_vfs_ssl_to_socket (ssl);
1421 result = gnome_vfs_inet_connection_create (&connection,
1422 toplevel_uri->host_name,
1425 if (result != GNOME_VFS_OK) {
1428 socket = gnome_vfs_inet_connection_to_socket
1433 *p_socket_buffer = gnome_vfs_socket_buffer_new (socket);
1435 if (*p_socket_buffer == NULL) {
1436 gnome_vfs_socket_close (socket);
1437 return GNOME_VFS_ERROR_INTERNAL;
1440 ANALYZE_HTTP ("==> -Making connection");
1447 build_request (const char * method, GnomeVFSToplevelURI * toplevel_uri, gboolean proxy_connect)
1449 gchar *uri_string = NULL;
1454 uri = (GnomeVFSURI *)toplevel_uri;
1456 if (proxy_connect) {
1457 uri_string = gnome_vfs_uri_to_string (uri,
1458 GNOME_VFS_URI_HIDE_USER_NAME
1459 | GNOME_VFS_URI_HIDE_PASSWORD);
1462 uri_string = gnome_vfs_uri_to_string (uri,
1463 GNOME_VFS_URI_HIDE_USER_NAME
1464 | GNOME_VFS_URI_HIDE_PASSWORD
1465 | GNOME_VFS_URI_HIDE_HOST_NAME
1466 | GNOME_VFS_URI_HIDE_HOST_PORT
1467 | GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD);
1471 request = g_string_new ("");
1473 g_string_append_printf (request, "%s %s%s HTTP/1.0\r\n", method, uri_string,
1474 gnome_vfs_uri_get_path (uri)[0] == '\0' ? "/" : "" );
1476 DEBUG_HTTP (("-->Making request '%s %s'", method, uri_string));
1478 g_free (uri_string);
1481 /* `Host:' header. */
1482 if(toplevel_uri->host_port && toplevel_uri->host_port != 0) {
1483 g_string_append_printf (request, "Host: %s:%d\r\n",
1484 toplevel_uri->host_name, toplevel_uri->host_port);
1486 g_string_append_printf (request, "Host: %s:80\r\n",
1487 toplevel_uri->host_name);
1490 /* `Accept:' header. */
1491 g_string_append (request, "Accept: */*\r\n");
1493 /* `User-Agent:' header. */
1494 user_agent = getenv (CUSTOM_USER_AGENT_VARIABLE);
1496 if(user_agent == NULL) {
1497 user_agent = USER_AGENT_STRING;
1500 g_string_append_printf (request, "User-Agent: %s\r\n", user_agent);
1505 static GnomeVFSResult
1506 xmit_request (GnomeVFSSocketBuffer *socket_buffer,
1510 GnomeVFSResult result;
1511 GnomeVFSFileSize bytes_written;
1513 ANALYZE_HTTP ("==> Writing request and header");
1515 /* Transmit the request headers. */
1516 result = gnome_vfs_socket_buffer_write (socket_buffer, request->str,
1517 request->len, &bytes_written);
1519 if (result != GNOME_VFS_OK) {
1523 /* Transmit the body */
1524 if(data && data->data) {
1525 ANALYZE_HTTP ("==> Writing data");
1527 result = gnome_vfs_socket_buffer_write (socket_buffer,
1528 data->data, data->len, &bytes_written);
1531 if (result != GNOME_VFS_OK) {
1535 result = gnome_vfs_socket_buffer_flush (socket_buffer);
1541 /* Do not allocate the 'handle' memory. */
1542 static GnomeVFSResult
1543 init_request (HttpFileHandle *handle,
1545 const gchar *method,
1547 gchar *extra_headers,
1548 GnomeVFSContext *context)
1550 GnomeVFSSocketBuffer *socket_buffer;
1551 GnomeVFSResult result;
1552 GnomeVFSToplevelURI *toplevel_uri;
1554 gboolean proxy_connect;
1555 char *authn_header_request;
1556 char *authn_header_proxy;
1558 g_return_val_if_fail (handle != NULL, GNOME_VFS_ERROR_INTERNAL);
1560 ANALYZE_HTTP ("==> +init_request");
1563 proxy_connect = FALSE;
1564 authn_header_request = NULL;
1565 authn_header_proxy = NULL;
1567 toplevel_uri = (GnomeVFSToplevelURI *) uri;
1572 g_free (authn_header_request);
1573 g_free (authn_header_proxy);
1575 socket_buffer = NULL;
1576 result = connect_to_uri (toplevel_uri, &socket_buffer,
1579 if (result != GNOME_VFS_OK) {
1583 request = build_request (method, toplevel_uri, proxy_connect);
1585 authn_header_request = http_authn_get_header_for_uri (uri);
1587 if (authn_header_request != NULL) {
1588 g_string_append (request, authn_header_request);
1591 if (proxy_connect) {
1592 authn_header_proxy = proxy_get_authn_header_for_uri (uri);
1594 if (authn_header_proxy != NULL) {
1595 g_string_append (request, authn_header_proxy);
1599 /* `Content-Length' header. */
1601 g_string_append_printf (request, "Content-Length: %d\r\n", data->len);
1604 /* Extra headers. */
1605 if (extra_headers != NULL) {
1606 g_string_append (request, extra_headers);
1609 /* Extra headers from user */
1612 if (invoke_callback_send_additional_headers (uri, &list)) {
1615 for (i = list; i; i = i->next) {
1616 g_string_append (request, i->data);
1624 /* Empty line ends header section. */
1625 g_string_append (request, "\r\n");
1627 result = xmit_request (socket_buffer, request, data);
1628 g_string_free (request, TRUE);
1631 if (result != GNOME_VFS_OK) {
1635 /* Read the headers and create our internal HTTP file handle. */
1636 result = init_handle (uri, socket_buffer, context, handle);
1638 if (result == GNOME_VFS_OK) {
1639 socket_buffer = NULL;
1642 if (handle->server_status == HTTP_STATUS_UNAUTHORIZED) {
1643 if (! check_authn_retry_request (handle, AuthnHeader_WWW, authn_header_request)) {
1646 } else if (handle->server_status == HTTP_STATUS_PROXY_AUTH_REQUIRED) {
1647 if (! check_authn_retry_request (handle, AuthnHeader_WWW, authn_header_proxy)) {
1653 http_file_handle_clear (handle);
1656 g_free (authn_header_request);
1657 g_free (authn_header_proxy);
1659 if (result != GNOME_VFS_OK) {
1660 http_file_handle_clear (handle);
1663 if (request != NULL) {
1664 g_string_free (request, TRUE);
1667 if (socket_buffer != NULL) {
1668 gnome_vfs_socket_buffer_destroy (socket_buffer, TRUE);
1671 ANALYZE_HTTP ("==> -init_request");
1675 static GnomeVFSResult
1676 make_request (HttpFileHandle **handle_return,
1678 const gchar *method,
1680 gchar *extra_headers,
1681 GnomeVFSContext *context)
1683 GnomeVFSResult result;
1685 g_return_val_if_fail (handle_return != NULL, GNOME_VFS_ERROR_INTERNAL);
1687 *handle_return = http_file_handle_alloc ();
1688 result = init_request (*handle_return, uri, method, data, extra_headers, context);
1689 if (result != GNOME_VFS_OK) {
1690 http_file_handle_destroy (*handle_return);
1691 *handle_return = NULL;
1698 http_handle_clear (HttpFileHandle *handle,
1699 GnomeVFSContext *context)
1701 ANALYZE_HTTP ("==> +http_handle_clear");
1703 if (handle != NULL) {
1704 if (handle->socket_buffer) {
1705 gnome_vfs_socket_buffer_flush (handle->socket_buffer);
1706 gnome_vfs_socket_buffer_destroy (handle->socket_buffer,
1708 handle->socket_buffer = NULL;
1711 http_file_handle_clear (handle);
1714 ANALYZE_HTTP ("==> -http_handle_clear");
1718 http_handle_close (HttpFileHandle *handle,
1719 GnomeVFSContext *context)
1721 http_handle_clear (handle, context);
1723 http_file_handle_destroy (handle);
1726 static GnomeVFSResult
1727 do_open (GnomeVFSMethod *method,
1728 GnomeVFSMethodHandle **method_handle,
1730 GnomeVFSOpenMode mode,
1731 GnomeVFSContext *context)
1733 HttpFileHandle *handle;
1734 GnomeVFSResult result = GNOME_VFS_OK;
1736 g_return_val_if_fail (uri->parent == NULL, GNOME_VFS_ERROR_INVALID_URI);
1737 g_return_val_if_fail (!(mode & GNOME_VFS_OPEN_READ &&
1738 mode & GNOME_VFS_OPEN_WRITE),
1739 GNOME_VFS_ERROR_INVALID_OPEN_MODE);
1741 ANALYZE_HTTP ("==> +do_open");
1742 DEBUG_HTTP (("+Open URI: '%s' mode:'%c'", gnome_vfs_uri_to_string(uri, 0),
1743 mode & GNOME_VFS_OPEN_READ ? 'R' : 'W'));
1745 if (mode & GNOME_VFS_OPEN_READ) {
1746 result = make_request (&handle, uri, "GET", NULL, NULL,
1749 handle = http_file_handle_new(NULL, uri); /* shrug */
1751 if (result == GNOME_VFS_OK) {
1752 *method_handle = (GnomeVFSMethodHandle *) handle;
1754 *method_handle = NULL;
1757 DEBUG_HTTP (("-Open (%d) handle:0x%08x", result, (unsigned int)handle));
1758 ANALYZE_HTTP ("==> -do_open");
1763 static GnomeVFSResult
1764 do_create (GnomeVFSMethod *method,
1765 GnomeVFSMethodHandle **method_handle,
1767 GnomeVFSOpenMode mode,
1770 GnomeVFSContext *context)
1772 /* try to write a zero length file - this appears to be the
1773 * only reliable way of testing if a put will succeed.
1774 * Xythos can apparently tell us if we have write permission by
1775 * playing with LOCK, but mod_dav cannot. */
1776 HttpFileHandle *handle;
1777 GnomeVFSResult result;
1778 GByteArray *bytes = g_byte_array_new();
1780 ANALYZE_HTTP ("==> +do_create");
1781 DEBUG_HTTP (("+Create URI: '%s'", gnome_vfs_uri_get_path (uri)));
1783 http_cache_invalidate_uri_parent (uri);
1785 /* Don't ignore exclusive; it should check first whether
1786 the file exists, since the http protocol default is to
1787 overwrite by default */
1788 /* FIXME we've stopped using HEAD -- we should use GET instead */
1789 /* FIXME we should check the cache here */
1792 ANALYZE_HTTP ("==> Checking to see if file exists");
1794 result = make_request (&handle, uri, "HEAD", NULL, NULL,
1796 http_handle_close (handle, context);
1798 if (result != GNOME_VFS_OK &&
1799 result != GNOME_VFS_ERROR_NOT_FOUND) {
1802 if (result == GNOME_VFS_OK) {
1803 return GNOME_VFS_ERROR_FILE_EXISTS;
1807 ANALYZE_HTTP ("==> Creating initial file");
1809 result = make_request (&handle, uri, "PUT", bytes, NULL, context);
1810 http_handle_close(handle, context);
1812 if (result != GNOME_VFS_OK) {
1813 /* the PUT failed */
1815 /* FIXME bugzilla.gnome.org 45131
1816 * If you PUT a file with an invalid name to Xythos, it
1817 * returns a 403 Forbidden, which is different from the behaviour
1818 * in MKCOL or MOVE. Unfortunately, it is not possible to discern whether
1819 * that 403 Forbidden is being returned because of invalid characters in the name
1820 * or because of permissions problems
1823 if (result == GNOME_VFS_ERROR_NOT_FOUND) {
1824 result = resolve_409 (method, uri, context);
1831 g_byte_array_free (bytes, TRUE);
1833 /* FIXME bugzilla.gnome.org 41159: do we need to do something more intelligent here? */
1834 result = do_open (method, method_handle, uri, GNOME_VFS_OPEN_WRITE, context);
1836 DEBUG_HTTP (("-Create (%d) handle:0x%08x", result, (unsigned int)handle));
1837 ANALYZE_HTTP ("==> -do_create");
1842 static GnomeVFSResult
1843 do_close (GnomeVFSMethod *method,
1844 GnomeVFSMethodHandle *method_handle,
1845 GnomeVFSContext *context)
1847 HttpFileHandle *old_handle;
1848 HttpFileHandle *new_handle;
1849 GnomeVFSResult result;
1851 ANALYZE_HTTP ("==> +do_close");
1852 DEBUG_HTTP (("+Close handle:0x%08x", (unsigned int)method_handle));
1854 old_handle = (HttpFileHandle *) method_handle;
1856 /* if the handle was opened in write mode then:
1857 * 1) there won't be a connection open, and
1858 * 2) there will be data to_be_written...
1860 if (old_handle->to_be_written != NULL) {
1861 GnomeVFSURI *uri = old_handle->uri;
1862 GByteArray *bytes = old_handle->to_be_written;
1863 GnomeVFSMimeSniffBuffer *sniff_buffer;
1864 char *extraheader = NULL;
1865 const char *mime_type = NULL;
1868 gnome_vfs_mime_sniff_buffer_new_from_existing_data (bytes->data,
1871 if (sniff_buffer != NULL) {
1873 gnome_vfs_get_mime_type_for_buffer (
1875 if (mime_type != NULL) {
1876 extraheader = g_strdup_printf(
1877 "Content-type: %s\r\n",
1880 gnome_vfs_mime_sniff_buffer_free (sniff_buffer);
1884 http_cache_invalidate_uri (uri);
1886 ANALYZE_HTTP ("==> doing PUT");
1887 result = make_request (&new_handle, uri, "PUT", bytes,
1888 extraheader, context);
1889 g_free (extraheader);
1890 http_handle_close (new_handle, context);
1892 result = GNOME_VFS_OK;
1895 http_handle_close (old_handle, context);
1897 DEBUG_HTTP (("-Close (%d)", result));
1898 ANALYZE_HTTP ("==> -do_close");
1903 static GnomeVFSResult
1904 do_write (GnomeVFSMethod *method,
1905 GnomeVFSMethodHandle *method_handle,
1906 gconstpointer buffer,
1907 GnomeVFSFileSize num_bytes,
1908 GnomeVFSFileSize *bytes_written,
1909 GnomeVFSContext *context)
1911 HttpFileHandle *handle;
1913 DEBUG_HTTP (("+Write handle:0x%08x", (unsigned int)method_handle));
1915 handle = (HttpFileHandle *) method_handle;
1917 if (handle->offset != 0)
1918 return GNOME_VFS_ERROR_NOT_SUPPORTED;
1920 if(handle->to_be_written == NULL) {
1921 handle->to_be_written = g_byte_array_new();
1923 handle->to_be_written = g_byte_array_append(handle->to_be_written, buffer, num_bytes);
1924 *bytes_written = num_bytes;
1926 DEBUG_HTTP (("-Write (0)"));
1928 return GNOME_VFS_OK;
1932 static GnomeVFSResult
1933 do_read (GnomeVFSMethod *method,
1934 GnomeVFSMethodHandle *method_handle,
1936 GnomeVFSFileSize num_bytes,
1937 GnomeVFSFileSize *bytes_read,
1938 GnomeVFSContext *context)
1940 HttpFileHandle *handle;
1941 GnomeVFSResult result;
1943 ANALYZE_HTTP ("==> +do_read");
1944 DEBUG_HTTP (("+Read handle=0x%08x", (unsigned int) method_handle));
1946 handle = (HttpFileHandle *) method_handle;
1948 if (handle->file_info->flags & GNOME_VFS_FILE_INFO_FIELDS_SIZE) {
1949 GnomeVFSFileSize max_bytes;
1951 max_bytes = MAX (0, handle->file_info->size - handle->offset);
1952 num_bytes = MIN (max_bytes, num_bytes);
1957 return GNOME_VFS_ERROR_EOF;
1961 && handle->offset > handle->socket_buffer_offset
1962 && handle->offset <= handle->socket_buffer_offset+MAX_BUFFER_SEEK_SKIP_READ) {
1963 static char drop_buffer[0x1000];
1964 GnomeVFSFileSize bytes, bytes_read;
1965 GnomeVFSResult result;
1967 while ((bytes=MIN(sizeof(drop_buffer), handle->offset - handle->socket_buffer_offset))) {
1968 result = gnome_vfs_socket_buffer_read (handle->socket_buffer, drop_buffer,
1969 bytes, &bytes_read);
1970 if (result != GNOME_VFS_OK)
1972 handle->socket_buffer_offset += bytes_read;
1976 if (handle->offset != handle->socket_buffer_offset) {
1977 GnomeVFSURI *uri = handle->uri;
1978 gchar *extra_headers;
1979 GnomeVFSFileOffset offset_save;
1981 offset_save = handle->offset;
1982 gnome_vfs_uri_ref(uri);
1983 http_handle_clear (handle, context);
1984 /* 'handle->offset' is already destroyed here: */
1985 extra_headers = g_strdup_printf("Range: bytes=%" G_GINT64_FORMAT "-\r\n",(gint64)offset_save);
1986 result = init_request (handle, uri, "GET", NULL, extra_headers,
1988 g_free (extra_headers);
1989 gnome_vfs_uri_unref(uri);
1990 handle->offset = offset_save;
1991 if (result != GNOME_VFS_OK) {
1992 /* FIXME: 'method_handle' is now broken! */
1993 memset(handle, 0, sizeof (*handle));
1996 handle->socket_buffer_offset = handle->offset;
1999 result = gnome_vfs_socket_buffer_read (handle->socket_buffer, buffer,
2000 num_bytes, bytes_read);
2002 if (*bytes_read == 0) {
2003 return GNOME_VFS_ERROR_EOF;
2006 handle->socket_buffer_offset += *bytes_read;
2007 handle->offset += *bytes_read;
2009 DEBUG_HTTP (("-Read (%d)", result));
2010 ANALYZE_HTTP ("==> -do_read");
2015 static GnomeVFSResult
2016 do_seek (GnomeVFSMethod *method,
2017 GnomeVFSMethodHandle *method_handle,
2018 GnomeVFSSeekPosition whence,
2019 GnomeVFSFileOffset offset,
2020 GnomeVFSContext *context)
2022 HttpFileHandle *handle;
2024 handle = (HttpFileHandle *) method_handle;
2026 if (handle->to_be_written != NULL)
2027 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2030 case GNOME_VFS_SEEK_START:
2031 handle->offset = offset;
2033 case GNOME_VFS_SEEK_CURRENT:
2034 handle->offset += offset;
2036 case GNOME_VFS_SEEK_END:
2037 if (!(handle->file_info->flags & GNOME_VFS_FILE_INFO_FIELDS_SIZE))
2038 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2039 handle->offset = handle->file_info->size + offset;
2042 g_return_val_if_reached(GNOME_VFS_ERROR_NOT_SUPPORTED);
2045 return GNOME_VFS_OK;
2048 static GnomeVFSResult
2049 do_tell (GnomeVFSMethod *method,
2050 GnomeVFSMethodHandle *method_handle,
2051 GnomeVFSFileOffset *offset_return)
2053 HttpFileHandle *handle;
2055 handle = (HttpFileHandle *) method_handle;
2057 *offset_return = handle->offset;
2059 return GNOME_VFS_OK;
2062 /* Directory handling - WebDAV servers only */
2065 process_propfind_propstat (xmlNodePtr node,
2066 GnomeVFSFileInfo *file_info)
2069 gboolean treat_as_directory;
2071 treat_as_directory = FALSE;
2073 while (node != NULL) {
2074 if (strcmp ((char *)node->name, "prop") != 0) {
2075 /* node name != "prop" - prop is all we care about */
2079 /* properties of the file */
2080 l = node->xmlChildrenNode;
2082 char *node_content_xml = xmlNodeGetContent(l);
2083 if (node_content_xml) {
2084 if (strcmp ((char *)l->name, "getcontenttype") == 0) {
2086 file_info->valid_fields |=
2087 GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
2089 if (!file_info->mime_type) {
2090 file_info->mime_type = strip_semicolon (node_content_xml);
2092 } else if (strcmp ((char *)l->name, "getcontentlength") == 0){
2093 file_info->valid_fields |=
2094 GNOME_VFS_FILE_INFO_FIELDS_SIZE;
2095 file_info->size = atoi(node_content_xml);
2096 } else if (strcmp((char *)l->name, "getlastmodified") == 0) {
2097 if (gnome_vfs_atotm (node_content_xml, &(file_info->mtime))) {
2098 file_info->ctime = file_info->mtime;
2099 file_info->valid_fields |=
2100 GNOME_VFS_FILE_INFO_FIELDS_MTIME
2101 | GNOME_VFS_FILE_INFO_FIELDS_CTIME;
2104 /* Unfortunately, we don't have a mapping for "creationdate" */
2106 xmlFree (node_content_xml);
2107 node_content_xml = NULL;
2109 if (strcmp ((char *)l->name, "resourcetype") == 0) {
2110 file_info->valid_fields |=
2111 GNOME_VFS_FILE_INFO_FIELDS_TYPE;
2112 file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;
2114 if (l->xmlChildrenNode && l->xmlChildrenNode->name
2115 && strcmp ((char *)l->xmlChildrenNode->name, "collection") == 0) {
2116 file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
2124 /* If this is a DAV collection, do we tell nautilus to treat it
2125 * as a directory or as a web page?
2127 if (file_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_TYPE
2128 && file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
2129 g_free (file_info->mime_type);
2130 if (treat_as_directory) {
2131 file_info->mime_type = g_strdup ("x-directory/webdav-prefer-directory");
2132 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
2134 file_info->mime_type = g_strdup ("x-directory/webdav");
2135 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
2140 if ((file_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE) == 0) {
2141 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
2142 file_info->mime_type = g_strdup (gnome_vfs_mime_type_from_name_or_default (file_info->name, "text/plain"));
2145 if ((file_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_TYPE) == 0) {
2146 /* Is this a reasonable assumption ? */
2147 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE;
2148 file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;
2152 /* a strcmp that doesn't barf on NULLs */
2154 null_handling_strcmp (const char *a, const char *b)
2156 if ((a == NULL) != (b == NULL)) {
2160 if (a == NULL && b == NULL) {
2164 return strcmp (a, b);
2169 unescape_unreserved_chars (const char *in_string)
2171 /* RFC 2396 section 2.2 */
2172 static const char * reserved_chars = "%;/?:@&=+$,";
2174 char *ret, *write_char;
2175 const char * read_char;
2177 if (in_string == NULL) {
2181 ret = g_new (char, strlen (in_string) + 1);
2183 for (read_char = in_string, write_char = ret ; *read_char != '\0' ; read_char++) {
2184 if (read_char[0] == '%'
2185 && g_ascii_isxdigit (read_char[1])
2186 && g_ascii_isxdigit (read_char[2])) {
2189 unescaped = (g_ascii_xdigit_value (read_char[1]) << 4) | g_ascii_xdigit_value (read_char[2]);
2191 if (strchr (reserved_chars, unescaped)) {
2192 *write_char++ = *read_char++;
2193 *write_char++ = *read_char++;
2194 *write_char++ = *read_char; /*The last ++ is done in the for statement */
2196 *write_char++ = unescaped;
2197 read_char += 2; /*The last ++ is done in the for statement */
2200 *write_char++ = *read_char;
2203 *write_char++ = '\0';
2210 find_child_node_named (xmlNodePtr node,
2211 const char *child_node_name)
2215 child = node->xmlChildrenNode;
2217 for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
2218 if (0 == strcmp (child->name, child_node_name)) {
2226 /* Look for a <status> tag in the children of node, and returns
2227 * the corresponding (HTTP) error code
2230 get_status_node (xmlNodePtr node, guint *status_code)
2232 xmlNodePtr status_node;
2233 char *status_string;
2236 status_node = find_child_node_named (node, "status");
2238 if (status_node != NULL) {
2239 status_string = xmlNodeGetContent (status_node);
2240 ret = parse_status (status_string, status_code);
2241 xmlFree (status_string);
2249 static GnomeVFSFileInfo *
2250 process_propfind_response(xmlNodePtr n,
2251 GnomeVFSURI *base_uri)
2253 GnomeVFSFileInfo *file_info = defaults_file_info_new ();
2254 GnomeVFSURI *second_base = gnome_vfs_uri_append_path (base_uri, "/");
2257 file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE;
2260 if (strcmp ((char *)n->name, "href") == 0) {
2261 char *nodecontent = xmlNodeGetContent (n);
2264 rv = gnome_vfs_remove_optional_escapes (nodecontent);
2266 if (nodecontent != NULL && *nodecontent != '\0' && rv == GNOME_VFS_OK) {
2268 GnomeVFSURI *uri = gnome_vfs_uri_new (nodecontent);
2271 if ((0 == null_handling_strcmp (base_uri->text, uri->text)) ||
2272 (0 == null_handling_strcmp (second_base->text, uri->text))) {
2273 file_info->name = NULL; /* this file is the . directory */
2275 if (file_info->name != NULL) {
2276 /* Don't leak if a (potentially malicious)
2277 * server returns several href in its answer
2279 g_free (file_info->name);
2281 file_info->name = gnome_vfs_uri_extract_short_name (uri);
2282 if (file_info->name != NULL) {
2283 len = strlen (file_info->name) -1;
2284 if (file_info->name[len] == '/') {
2285 /* trim trailing `/` - it confuses stuff */
2286 file_info->name[len] = '\0';
2289 g_warning ("Invalid filename in PROPFIND '%s'; silently skipping", nodecontent);
2292 gnome_vfs_uri_unref (uri);
2294 g_warning ("Can't make URI from href in PROPFIND '%s'; silently skipping", nodecontent);
2297 g_warning ("got href without contents in PROPFIND response");
2300 xmlFree (nodecontent);
2301 } else if (strcmp ((char *)n->name, "propstat") == 0) {
2302 if (get_status_node (n, &status_code) && status_code == 200) {
2303 process_propfind_propstat (n->xmlChildrenNode, file_info);
2309 gnome_vfs_uri_unref (second_base);
2316 static GnomeVFSResult
2317 make_propfind_request (HttpFileHandle **handle_return,
2320 GnomeVFSContext *context)
2322 GnomeVFSResult result = GNOME_VFS_OK;
2323 GnomeVFSFileSize bytes_read, num_bytes=(64*1024);
2324 char *buffer = g_malloc(num_bytes);
2325 xmlParserCtxtPtr parserContext;
2326 xmlDocPtr doc = NULL;
2327 xmlNodePtr cur = NULL;
2328 char *extraheaders = g_strdup_printf("Depth: %d\r\n", depth);
2329 gboolean found_root_node_props;
2331 GByteArray *request = g_byte_array_new();
2332 char *request_str = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
2333 "<D:propfind xmlns:D=\"DAV:\" >"
2336 "<D:getcontentlength/>"
2337 "<D:getcontenttype/>"
2338 "<D:getlastmodified/>"
2343 ANALYZE_HTTP ("==> +make_propfind_request");
2345 request = g_byte_array_append(request, request_str,
2346 strlen(request_str));
2348 parserContext = xmlCreatePushParserCtxt(NULL, NULL, "", 0, "PROPFIND");
2351 http_cache_invalidate_uri_and_children (uri);
2354 result = make_request (handle_return, uri, "PROPFIND", request,
2355 extraheaders, context);
2357 /* FIXME bugzilla.gnome.org 43834: It looks like some http
2358 * servers (eg, www.yahoo.com) treat PROPFIND as a GET and
2359 * return a 200 OK. Others may return access denied errors or
2360 * redirects or any other legal response. This case probably
2361 * needs to be made more robust.
2363 if (result == GNOME_VFS_OK && (*handle_return)->server_status != 207) { /* Multi-Status */
2364 DEBUG_HTTP (("HTTP server returned an invalid PROPFIND response: %d", (*handle_return)->server_status));
2365 result = GNOME_VFS_ERROR_NOT_SUPPORTED;
2367 /* Some servers (download.microsoft.com) will just close
2368 * the connection (EOF) without returning any HTTP status.
2370 if (result == GNOME_VFS_ERROR_EOF) {
2371 DEBUG_HTTP (("HTTP server returned an empty PROPFIND response"));
2372 result = GNOME_VFS_ERROR_NOT_SUPPORTED;
2375 if (result == GNOME_VFS_OK) {
2377 result = do_read (NULL, (GnomeVFSMethodHandle *) *handle_return,
2378 buffer, num_bytes, &bytes_read, context);
2380 if (result != GNOME_VFS_OK ) {
2383 xmlParseChunk (parserContext, buffer, bytes_read, 0);
2384 buffer[bytes_read]=0;
2385 } while (bytes_read > 0);
2388 if (result == GNOME_VFS_ERROR_EOF) {
2389 result = GNOME_VFS_OK;
2392 if (result != GNOME_VFS_OK) {
2396 xmlParseChunk (parserContext, "", 0, 1);
2398 doc = parserContext->myDoc;
2400 result = GNOME_VFS_ERROR_GENERIC;
2404 cur = doc->xmlRootNode;
2405 if (strcmp ((char *)cur->name, "multistatus") != 0) {
2406 DEBUG_HTTP (("Couldn't find <multistatus>.\n"));
2407 result = GNOME_VFS_ERROR_GENERIC;
2410 cur = cur->xmlChildrenNode;
2412 found_root_node_props = FALSE;
2413 while (cur != NULL) {
2414 if (strcmp ((char *)cur->name, "response") == 0) {
2415 GnomeVFSFileInfo *file_info;
2418 /* Some webdav servers (eg resin) put the HTTP status
2419 * code for PROPFIND request in the xml answer instead
2420 * of directly sending a 404
2422 if (get_status_node (cur, &status) && !HTTP_20X(status)) {
2423 result = http_status_to_vfs_result (status);
2428 process_propfind_response (cur->xmlChildrenNode, uri);
2430 if (file_info->name != NULL) {
2431 (*handle_return)->files = g_list_append ((*handle_return)->files, file_info);
2433 /* This response refers to the root node */
2434 /* Abandon the old information that came from init_handle */
2436 file_info->name = (*handle_return)->file_info->name;
2437 (*handle_return)->file_info->name = NULL;
2438 gnome_vfs_file_info_unref ((*handle_return)->file_info);
2439 (*handle_return)->file_info = file_info;
2440 found_root_node_props = TRUE;
2444 DEBUG_HTTP(("expecting <response> got <%s>\n", cur->name));
2449 if (!found_root_node_props) {
2450 DEBUG_HTTP (("Failed to find root request node properties during propfind"));
2451 result = GNOME_VFS_ERROR_GENERIC;
2457 * Section 8.1, final line
2458 * "The results of this method [PROPFIND] SHOULD NOT be cached"
2459 * Well, at least its not "MUST NOT"
2463 http_cache_add_uri (uri, (*handle_return)->file_info, TRUE);
2465 http_cache_add_uri_and_children (uri, (*handle_return)->file_info, (*handle_return)->files);
2470 g_free(extraheaders);
2471 xmlFreeParserCtxt(parserContext);
2473 if (result != GNOME_VFS_OK) {
2474 http_handle_close (*handle_return, context);
2475 *handle_return = NULL;
2478 ANALYZE_HTTP ("==> -make_propfind_request");
2483 static GnomeVFSResult
2484 do_open_directory(GnomeVFSMethod *method,
2485 GnomeVFSMethodHandle **method_handle,
2487 GnomeVFSFileInfoOptions options,
2488 GnomeVFSContext *context)
2490 /* TODO move to using the gnome_vfs_file_info_list family of functions */
2491 GnomeVFSResult result;
2492 HttpFileHandle *handle = NULL;
2493 GnomeVFSFileInfo * file_info_cached;
2494 GList *child_file_info_cached_list = NULL;
2496 ANALYZE_HTTP ("==> +do_open_directory");
2497 DEBUG_HTTP (("+Open_Directory options: %d URI: '%s'", options, gnome_vfs_uri_to_string (uri, 0)));
2499 /* Check the cache--is this even a directory?
2500 * (Nautilus, in particular, seems to like to make this call on non directories
2503 file_info_cached = http_cache_check_uri (uri);
2505 if (file_info_cached) {
2506 if (GNOME_VFS_FILE_TYPE_DIRECTORY != file_info_cached->type) {
2507 ANALYZE_HTTP ("==> Cache Hit (Negative)");
2508 gnome_vfs_file_info_unref (file_info_cached);
2509 result = GNOME_VFS_ERROR_NOT_A_DIRECTORY;
2512 gnome_vfs_file_info_unref (file_info_cached);
2513 file_info_cached = NULL;
2517 /* The check for directory contents is more stringent */
2518 file_info_cached = http_cache_check_directory_uri (uri, &child_file_info_cached_list);
2520 if (file_info_cached) {
2521 handle = http_file_handle_new (NULL, uri);
2522 gnome_vfs_file_info_unref (handle->file_info);
2523 handle->file_info = file_info_cached;
2524 handle->files = child_file_info_cached_list;
2525 result = GNOME_VFS_OK;
2527 result = make_propfind_request(&handle, uri, 1, context);
2528 /* mfleming -- is this necessary? Most DAV server's I've seen don't have the horrible
2529 * lack-of-trailing-/-is-a-301 problem for PROPFIND's
2531 if (result == GNOME_VFS_ERROR_NOT_FOUND) { /* 404 not found */
2532 if (uri->text != NULL && *uri->text != '\0'
2533 && uri->text[strlen (uri->text) - 1] != '/') {
2534 GnomeVFSURI *tmpuri = gnome_vfs_uri_append_path (uri, "/");
2535 result = do_open_directory (method, (GnomeVFSMethodHandle **)&handle, tmpuri, options, context);
2536 gnome_vfs_uri_unref (tmpuri);
2541 if (result == GNOME_VFS_OK
2542 && handle->file_info->type != GNOME_VFS_FILE_TYPE_DIRECTORY) {
2543 result = GNOME_VFS_ERROR_NOT_A_DIRECTORY;
2544 http_handle_close (handle, context);
2549 *method_handle = (GnomeVFSMethodHandle *)handle;
2552 DEBUG_HTTP (("-Open_Directory (%d) handle:0x%08x", result, (unsigned int)handle));
2553 ANALYZE_HTTP ("==> -do_open_directory");
2558 static GnomeVFSResult
2559 do_close_directory (GnomeVFSMethod *method,
2560 GnomeVFSMethodHandle *method_handle,
2561 GnomeVFSContext *context)
2563 HttpFileHandle *handle;
2565 DEBUG_HTTP (("+Close_Directory"));
2567 handle = (HttpFileHandle *) method_handle;
2569 http_handle_close(handle, context);
2571 DEBUG_HTTP (("-Close_Directory (0) handle:0x%08x", (unsigned int) method_handle));
2573 return GNOME_VFS_OK;
2576 static GnomeVFSResult
2577 do_read_directory (GnomeVFSMethod *method,
2578 GnomeVFSMethodHandle *method_handle,
2579 GnomeVFSFileInfo *file_info,
2580 GnomeVFSContext *context)
2582 HttpFileHandle *handle;
2583 GnomeVFSResult result;
2585 DEBUG_HTTP (("+Read_Directory handle:0x%08x", (unsigned int) method_handle));
2587 handle = (HttpFileHandle *) method_handle;
2589 if (handle->files && g_list_length (handle->files)) {
2590 GnomeVFSFileInfo *original_info = g_list_nth_data (handle->files, 0);
2591 gboolean found_entry = FALSE;
2593 /* mfleming -- Why is this check here? Does anyone set original_info->name to NULL? */
2594 if (original_info->name != NULL && original_info->name[0]) {
2595 gnome_vfs_file_info_copy (file_info, original_info);
2599 /* remove our GnomeVFSFileInfo from the list */
2600 handle->files = g_list_remove (handle->files, original_info);
2601 gnome_vfs_file_info_unref (original_info);
2603 /* mfleming -- Is this necessary? */
2605 result = GNOME_VFS_OK;
2607 result = do_read_directory (method, method_handle, file_info, context);
2610 result = GNOME_VFS_ERROR_EOF;
2613 DEBUG_HTTP (("-Read_Directory (%d)", result));
2618 static GnomeVFSResult
2619 do_get_file_info (GnomeVFSMethod *method,
2621 GnomeVFSFileInfo *file_info,
2622 GnomeVFSFileInfoOptions options,
2623 GnomeVFSContext *context)
2625 HttpFileHandle *handle;
2626 GnomeVFSResult result;
2627 GnomeVFSFileInfo * file_info_cached;
2629 ANALYZE_HTTP ("==> +do_get_file_info");
2630 DEBUG_HTTP (("+Get_File_Info options: %d URI: '%s'", options, gnome_vfs_uri_to_string( uri, 0)));
2632 file_info_cached = http_cache_check_uri (uri);
2634 if (file_info_cached != NULL) {
2635 gnome_vfs_file_info_copy (file_info, file_info_cached);
2636 gnome_vfs_file_info_unref (file_info_cached);
2637 ANALYZE_HTTP ("==> Cache Hit");
2638 result = GNOME_VFS_OK;
2641 * Start off by making a PROPFIND request. Fall back to a HEAD if it fails
2644 result = make_propfind_request (&handle, uri, 0, context);
2646 /* Note that theoretically we could not bother with this request if we get a 404 back,
2647 * but since some servers seem to return wierd things on PROPFIND (mostly 200 OK's...)
2648 * I'm not going to count on the PROPFIND response....
2650 if (result == GNOME_VFS_OK) {
2651 gnome_vfs_file_info_copy (file_info, handle->file_info);
2652 http_handle_close (handle, context);
2655 g_assert (handle == NULL); /* Make sure we're not leaking some old one */
2657 /* Lame buggy servers (eg: www.mozilla.org,
2658 * www.corel.com)) return an HTTP error for a
2659 * HEAD where a GET would succeed. In these
2660 * cases lets try to do a GET.
2662 if (result != GNOME_VFS_OK) {
2663 g_assert (handle == NULL); /* Make sure we're not leaking some old one */
2665 ANALYZE_HTTP ("==> do_get_file_info: do GET ");
2667 result = make_request (&handle, uri, "GET", NULL, NULL, context);
2668 if (result == GNOME_VFS_OK) {
2669 gnome_vfs_file_info_copy (file_info, handle->file_info);
2670 http_cache_add_uri (uri, handle->file_info, FALSE);
2671 http_handle_close (handle, context);
2675 /* If we get a redirect, we should be
2676 * basing the MIME type on the type of
2677 * the page we'll be redirected
2678 * too. Maybe we even want to take the
2679 * "follow_links" setting into account.
2681 /* FIXME: For now we treat all
2682 * redirects as if they lead to a
2683 * text/html. That works pretty well,
2684 * but it's not correct.
2686 if (handle != NULL && HTTP_REDIRECTED (handle->server_status)) {
2687 g_free (file_info->mime_type);
2688 file_info->mime_type = g_strdup ("text/html");
2692 if (result == GNOME_VFS_ERROR_NOT_FOUND) { /* 404 not found */
2693 /* FIXME bugzilla.gnome.org 43835: mfleming: Is this code really appropriate?
2694 * In any case, it doesn't seem to be appropriate for a DAV-enabled
2695 * server, since they don't seem to send 301's when you PROPFIND collections
2696 * without a trailing '/'.
2698 if (uri->text != NULL && *uri->text != '\0'
2699 && uri->text[strlen(uri->text)-1] != '/') {
2700 GnomeVFSURI *tmpuri = gnome_vfs_uri_append_path (uri, "/");
2702 result = do_get_file_info (method, tmpuri, file_info, options, context);
2703 gnome_vfs_uri_unref (tmpuri);
2709 DEBUG_HTTP (("-Get_File_Info (%d)", result));
2710 ANALYZE_HTTP ("==> -do_get_file_info");
2715 static GnomeVFSResult
2716 do_get_file_info_from_handle (GnomeVFSMethod *method,
2717 GnomeVFSMethodHandle *method_handle,
2718 GnomeVFSFileInfo *file_info,
2719 GnomeVFSFileInfoOptions options,
2720 GnomeVFSContext *context)
2722 HttpFileHandle *handle;
2724 DEBUG_HTTP (("+Get_File_Info_From_Handle"));
2726 handle = (HttpFileHandle *) method_handle;
2728 gnome_vfs_file_info_copy (file_info, handle->file_info);
2730 DEBUG_HTTP (("-Get_File_Info_From_Handle"));
2732 return GNOME_VFS_OK;
2736 do_is_local (GnomeVFSMethod *method,
2737 const GnomeVFSURI *uri)
2739 DEBUG_HTTP (("+Is_Local"));
2743 static GnomeVFSResult
2744 do_make_directory (GnomeVFSMethod *method,
2747 GnomeVFSContext *context)
2749 /* MKCOL /path HTTP/1.0 */
2751 HttpFileHandle *handle;
2752 GnomeVFSResult result;
2754 DEBUG_HTTP (("+Make_Directory URI: '%s'", gnome_vfs_uri_to_string (uri, 0)));
2755 ANALYZE_HTTP ("==> +do_make_directory");
2758 * MKCOL returns a 405 if you try to MKCOL on something that
2759 * already exists. Of course, we don't know whether that means that
2760 * the server doesn't support DAV or the collection already exists.
2761 * So we do a PROPFIND first to find out
2763 /* FIXME check cache here */
2764 result = make_propfind_request(&handle, uri, 0, context);
2766 if (result == GNOME_VFS_OK) {
2767 result = GNOME_VFS_ERROR_FILE_EXISTS;
2769 /* Make sure we're not leaking an old one */
2770 g_assert (handle == NULL);
2772 if (result == GNOME_VFS_ERROR_NOT_FOUND) {
2773 http_cache_invalidate_uri_parent (uri);
2774 result = make_request (&handle, uri, "MKCOL", NULL, NULL, context);
2777 http_handle_close (handle, context);
2779 if (result == GNOME_VFS_ERROR_NOT_FOUND) {
2780 result = resolve_409 (method, uri, context);
2783 ANALYZE_HTTP ("==> -do_make_directory");
2784 DEBUG_HTTP (("-Make_Directory (%d)", result));
2789 static GnomeVFSResult
2790 do_remove_directory(GnomeVFSMethod *method,
2792 GnomeVFSContext *context)
2794 /* DELETE /path HTTP/1.0 */
2795 HttpFileHandle *handle;
2796 GnomeVFSResult result;
2798 ANALYZE_HTTP ("==> +do_remove_directory");
2799 DEBUG_HTTP (("+Remove_Directory URI: '%s'", gnome_vfs_uri_to_string (uri, 0)));
2801 http_cache_invalidate_uri_parent (uri);
2803 /* FIXME this should return GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY if the
2804 * directory is not empty
2806 result = make_request (&handle, uri, "DELETE", NULL, NULL,
2808 http_handle_close (handle, context);
2810 DEBUG_HTTP (("-Remove_Directory (%d)", result));
2811 ANALYZE_HTTP ("==> -do_remove_directory");
2817 is_same_fs (const GnomeVFSURI *a,
2818 const GnomeVFSURI *b)
2820 return null_handling_strcmp (gnome_vfs_uri_get_scheme (a), gnome_vfs_uri_get_scheme (b)) == 0
2821 && null_handling_strcmp (gnome_vfs_uri_get_host_name (a), gnome_vfs_uri_get_host_name (b)) == 0
2822 && null_handling_strcmp (gnome_vfs_uri_get_user_name (a), gnome_vfs_uri_get_user_name (b)) == 0
2823 && null_handling_strcmp (gnome_vfs_uri_get_password (a), gnome_vfs_uri_get_password (b)) == 0
2824 && (gnome_vfs_uri_get_host_port (a) == gnome_vfs_uri_get_host_port (b));
2827 static GnomeVFSResult
2828 do_move (GnomeVFSMethod *method,
2829 GnomeVFSURI *old_uri,
2830 GnomeVFSURI *new_uri,
2831 gboolean force_replace,
2832 GnomeVFSContext *context)
2836 * MOVE /path1 HTTP/1.0
2837 * Destination: /path2
2841 HttpFileHandle *handle;
2842 GnomeVFSResult result;
2844 char *destpath, *destheader;
2846 ANALYZE_HTTP ("==> +do_move");
2847 DEBUG_HTTP (("+Move URI: '%s' Dest: '%s'",
2848 gnome_vfs_uri_to_string (old_uri, 0),
2849 gnome_vfs_uri_to_string (new_uri, 0)));
2851 if (!is_same_fs (old_uri, new_uri)) {
2852 return GNOME_VFS_ERROR_NOT_SAME_FILE_SYSTEM;
2855 destpath = gnome_vfs_uri_to_string (new_uri, GNOME_VFS_URI_HIDE_USER_NAME|GNOME_VFS_URI_HIDE_PASSWORD);
2856 destheader = g_strdup_printf ("Destination: %s\r\nOverwrite: %c\r\n", destpath, force_replace ? 'T' : 'F' );
2858 result = make_request (&handle, old_uri, "MOVE", NULL, destheader, context);
2859 http_handle_close (handle, context);
2862 if (result == GNOME_VFS_ERROR_NOT_FOUND) {
2863 result = resolve_409 (method, new_uri, context);
2866 http_cache_invalidate_uri_parent (old_uri);
2867 http_cache_invalidate_uri_parent (new_uri);
2869 DEBUG_HTTP (("-Move (%d)", result));
2870 ANALYZE_HTTP ("==> -do_move");
2876 static GnomeVFSResult
2877 do_unlink(GnomeVFSMethod *method,
2879 GnomeVFSContext *context)
2881 GnomeVFSResult result;
2883 /* FIXME need to make sure this fails on directories */
2884 ANALYZE_HTTP ("==> +do_unlink");
2885 DEBUG_HTTP (("+Unlink URI: '%s'", gnome_vfs_uri_to_string (uri, 0)));
2886 result = do_remove_directory (method, uri, context);
2887 DEBUG_HTTP (("-Unlink (%d)", result));
2888 ANALYZE_HTTP ("==> -do_unlink");
2893 static GnomeVFSResult
2894 do_check_same_fs (GnomeVFSMethod *method,
2897 gboolean *same_fs_return,
2898 GnomeVFSContext *context)
2900 *same_fs_return = is_same_fs (a, b);
2902 return GNOME_VFS_OK;
2905 static GnomeVFSResult
2906 do_set_file_info (GnomeVFSMethod *method,
2908 const GnomeVFSFileInfo *info,
2909 GnomeVFSSetFileInfoMask mask,
2910 GnomeVFSContext *context)
2912 GnomeVFSURI *parent_uri, *new_uri;
2913 GnomeVFSResult result;
2915 /* FIXME: For now, we only support changing the name. */
2916 if ((mask & ~(GNOME_VFS_SET_FILE_INFO_NAME)) != 0) {
2917 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2920 /* FIXME bugzillagnome.org 40645: Make sure this returns an
2921 * error for incoming names with "/" characters in them,
2922 * instead of moving the file.
2925 /* Share code with do_move. */
2926 parent_uri = gnome_vfs_uri_get_parent (uri);
2927 if (parent_uri == NULL) {
2928 return GNOME_VFS_ERROR_NOT_FOUND;
2930 new_uri = gnome_vfs_uri_append_file_name (parent_uri, info->name);
2931 gnome_vfs_uri_unref (parent_uri);
2932 result = do_move (method, uri, new_uri, FALSE, context);
2933 gnome_vfs_uri_unref (new_uri);
2937 static GnomeVFSMethod method = {
2938 sizeof (GnomeVFSMethod),
2946 NULL, /* truncate_handle */
2951 do_get_file_info_from_handle,
2954 do_remove_directory,
2959 NULL, /* truncate */
2960 NULL, /* find_directory */
2961 NULL /* create_symbolic_link */
2965 vfs_module_init (const char *method_name,
2968 GError *gconf_error = NULL;
2970 gboolean use_proxy_auth;
2974 gl_client = gconf_client_get_default ();
2976 gl_mutex = g_mutex_new ();
2978 gconf_client_add_dir (gl_client, PATH_GCONF_GNOME_VFS, GCONF_CLIENT_PRELOAD_ONELEVEL, &gconf_error);
2980 DEBUG_HTTP (("GConf error during client_add_dir '%s'", gconf_error->message));
2981 g_error_free (gconf_error);
2985 gconf_client_notify_add (gl_client, PATH_GCONF_GNOME_VFS, notify_gconf_value_changed, NULL, NULL, &gconf_error);
2987 DEBUG_HTTP (("GConf error during notify_error '%s'", gconf_error->message));
2988 g_error_free (gconf_error);
2992 /* Load the http proxy setting */
2993 use_proxy = gconf_client_get_bool (gl_client, KEY_GCONF_USE_HTTP_PROXY, &gconf_error);
2995 if (gconf_error != NULL) {
2996 DEBUG_HTTP (("GConf error during client_get_bool '%s'", gconf_error->message));
2997 g_error_free (gconf_error);
3000 construct_gl_http_proxy (use_proxy);
3003 use_proxy_auth = gconf_client_get_bool (gl_client, KEY_GCONF_HTTP_USE_AUTH, &gconf_error);
3005 if (gconf_error != NULL) {
3006 DEBUG_HTTP (("GConf error during client_get_bool '%s'", gconf_error->message));
3007 g_error_free (gconf_error);
3010 set_proxy_auth (use_proxy_auth);
3020 vfs_module_shutdown (GnomeVFSMethod *method)
3022 g_object_unref (G_OBJECT (gl_client));
3024 http_authn_shutdown ();
3026 http_cache_shutdown();
3028 g_mutex_free (gl_mutex);
3033 /* A "409 Conflict" currently maps to GNOME_VFS_ERROR_NOT_FOUND because it can be returned
3034 * when the parent collection/directory does not exist. Unfortunately, Xythos also returns
3035 * this code when the destination filename of a PUT, MKCOL, or MOVE contains illegal characters,
3036 * eg "my*file:name".
3038 * The only way to resolve this is to ask...
3041 static GnomeVFSResult
3042 resolve_409 (GnomeVFSMethod *method, GnomeVFSURI *uri, GnomeVFSContext *context)
3044 GnomeVFSFileInfo *file_info;
3045 GnomeVFSURI *parent_dest_uri;
3046 GnomeVFSResult result;
3049 ANALYZE_HTTP ("==> +resolving 409");
3051 file_info = gnome_vfs_file_info_new ();
3052 parent_dest_uri = gnome_vfs_uri_get_parent (uri);
3054 if (parent_dest_uri != NULL) {
3055 result = do_get_file_info (method,
3058 GNOME_VFS_FILE_INFO_DEFAULT,
3061 gnome_vfs_file_info_unref (file_info);
3064 gnome_vfs_uri_unref (parent_dest_uri);
3065 parent_dest_uri = NULL;
3067 result = GNOME_VFS_ERROR_NOT_FOUND;
3070 if (result == GNOME_VFS_OK) {
3071 /* The destination filename contains characters that are not allowed
3072 * by the server. This is a bummer mapping, but EINVAL is what
3073 * the Linux FAT filesystems return on bad filenames, so at least
3074 * its not without precedent...
3076 result = GNOME_VFS_ERROR_BAD_PARAMETERS;
3078 /* The destination's parent path does not exist */
3079 result = GNOME_VFS_ERROR_NOT_FOUND;
3082 ANALYZE_HTTP ("==> -resolving 409");
3088 invoke_callback_headers_received (HttpFileHandle *handle)
3090 GnomeVFSModuleCallbackReceivedHeadersIn in_args;
3091 GnomeVFSModuleCallbackReceivedHeadersOut out_args;
3092 gboolean ret = FALSE;
3094 memset (&in_args, 0, sizeof (in_args));
3095 memset (&out_args, 0, sizeof (out_args));
3097 in_args.uri = handle->uri;
3098 in_args.headers = handle->response_headers;
3100 ret = gnome_vfs_module_callback_invoke (GNOME_VFS_MODULE_CALLBACK_HTTP_RECEIVED_HEADERS,
3101 &in_args, sizeof (in_args),
3102 &out_args, sizeof (out_args));
3108 invoke_callback_send_additional_headers (GnomeVFSURI *uri,
3111 GnomeVFSModuleCallbackAdditionalHeadersIn in_args;
3112 GnomeVFSModuleCallbackAdditionalHeadersOut out_args;
3113 gboolean ret = FALSE;
3115 memset (&in_args, 0, sizeof (in_args));
3116 memset (&out_args, 0, sizeof (out_args));
3120 ret = gnome_vfs_module_callback_invoke (GNOME_VFS_MODULE_CALLBACK_HTTP_SEND_ADDITIONAL_HEADERS,
3121 &in_args, sizeof (in_args),
3122 &out_args, sizeof (out_args));
3125 *headers = out_args.headers;
3129 if (out_args.headers) {
3130 g_list_foreach (out_args.headers, (GFunc)g_free, NULL);
3131 g_list_free (out_args.headers);
3140 invoke_callback_basic_authn (HttpFileHandle *handle,
3141 enum AuthnHeaderType authn_which,
3142 gboolean previous_attempt_failed)
3144 GnomeVFSModuleCallbackAuthenticationIn in_args;
3145 GnomeVFSModuleCallbackAuthenticationOut out_args;
3150 memset (&in_args, 0, sizeof (in_args));
3151 memset (&out_args, 0, sizeof (out_args));
3153 in_args.previous_attempt_failed = previous_attempt_failed;
3155 in_args.uri = gnome_vfs_uri_to_string (handle->uri, GNOME_VFS_URI_HIDE_NONE);
3157 ret = http_authn_parse_response_header_basic (authn_which, handle->response_headers, &in_args.realm);
3163 DEBUG_HTTP (("Invoking %s authentication callback for uri %s",
3164 authn_which == AuthnHeader_WWW ? "basic" : "proxy", in_args.uri));
3166 in_args.auth_type = AuthTypeBasic;
3168 ret = gnome_vfs_module_callback_invoke (authn_which == AuthnHeader_WWW
3169 ? GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION
3170 : GNOME_VFS_MODULE_CALLBACK_HTTP_PROXY_AUTHENTICATION,
3171 &in_args, sizeof (in_args),
3172 &out_args, sizeof (out_args));
3175 DEBUG_HTTP (("No callback registered"));
3179 ret = (out_args.username != NULL);
3182 DEBUG_HTTP (("No username provided by callback"));
3186 DEBUG_HTTP (("Back from authentication callback, adding credentials"));
3188 if (authn_which == AuthnHeader_WWW) {
3189 http_authn_session_add_credentials (handle->uri, out_args.username, out_args.password);
3190 } else /* if (authn_which == AuthnHeader_Proxy) */ {
3191 proxy_set_authn (out_args.username, out_args.password);
3194 g_free (in_args.uri);
3195 g_free (in_args.realm);
3196 g_free (out_args.username);
3197 g_free (out_args.password);
3203 strcmp_allow_nulls (const char *s1, const char *s2)
3205 return strcmp (s1 == NULL ? "" : s1, s2 == NULL ? "" : s2);
3209 /* Returns TRUE if the given URL has changed authentication credentials
3210 * from the last request (eg, another thread updated the authn information)
3211 * or if the application provided new credentials via a callback
3213 * prev_authn_header is NULL if the previous request contained no authn information.
3217 check_authn_retry_request (HttpFileHandle * http_handle,
3218 enum AuthnHeaderType authn_which,
3219 const char *prev_authn_header)
3222 char *current_authn_header;
3224 current_authn_header = NULL;
3226 g_mutex_lock (gl_mutex);
3228 if (authn_which == AuthnHeader_WWW) {
3229 current_authn_header = http_authn_get_header_for_uri (http_handle->uri);
3230 } else if (authn_which == AuthnHeader_Proxy) {
3231 current_authn_header = proxy_get_authn_header_for_uri_nolock (http_handle->uri);
3233 g_assert_not_reached ();
3237 if (0 == strcmp_allow_nulls (current_authn_header, prev_authn_header)) {
3238 ret = invoke_callback_basic_authn (http_handle, authn_which, prev_authn_header == NULL);
3243 g_mutex_unlock (gl_mutex);
3245 g_free (current_authn_header);
3252 http_util_get_utime (void)
3255 gettimeofday (&tmp, NULL);
3256 return (utime_t)tmp.tv_usec + ((gint64)tmp.tv_sec) * 1000000LL;
3260 /* BASE64 code ported from neon (http://www.webdav.org/neon) */
3261 static const gchar b64_alphabet[65] = {
3262 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3263 "abcdefghijklmnopqrstuvwxyz"
3267 http_util_base64 (const gchar *text)
3269 /* The tricky thing about this is doing the padding at the end,
3270 * doing the bit manipulation requires a bit of concentration only */
3271 gchar *buffer, *point;
3274 /* Use 'buffer' to store the output. Work out how big it should be...
3275 * This must be a multiple of 4 bytes
3278 inlen = strlen (text);
3279 outlen = (inlen*4)/3;
3280 if ((inlen % 3) > 0) { /* got to pad */
3281 outlen += 4 - (inlen % 3);
3284 buffer = g_malloc (outlen + 1); /* +1 for the \0 */
3286 /* now do the main stage of conversion, 3 bytes at a time,
3287 * leave the trailing bytes (if there are any) for later
3290 for (point=buffer; inlen>=3; inlen-=3, text+=3) {
3291 *(point++) = b64_alphabet[ (*text)>>2 ];
3292 *(point++) = b64_alphabet[ ((*text)<<4 & 0x30) | (*(text+1))>>4 ];
3293 *(point++) = b64_alphabet[ ((*(text+1))<<2 & 0x3c) | (*(text+2))>>6 ];
3294 *(point++) = b64_alphabet[ (*(text+2)) & 0x3f ];
3297 /* Now deal with the trailing bytes */
3299 /* We always have one trailing byte */
3300 *(point++) = b64_alphabet[ (*text)>>2 ];
3301 *(point++) = b64_alphabet[ ( ((*text)<<4 & 0x30) |
3302 (inlen==2?(*(text+1))>>4:0) ) ];
3303 *(point++) = (inlen==1?'=':b64_alphabet[ (*(text+1))<<2 & 0x3c ] );
3307 /* Null-terminate */
3313 static gboolean at_least_one_test_failed = FALSE;
3316 test_failed (const char *format, ...)
3321 va_start (arguments, format);
3322 message = g_strdup_vprintf (format, arguments);
3325 g_message ("test failed: %s", message);
3326 at_least_one_test_failed = TRUE;
3329 #define VERIFY_BOOLEAN_RESULT(function, expected) \
3331 gboolean result = function; \
3332 if (! ((result && expected) || (!result && !expected))) { \
3333 test_failed ("%s: returned '%d' expected '%d'", #function, (int)result, (int)expected); \
3339 http_self_test (void)
3341 g_message ("self-test: http\n");
3343 VERIFY_BOOLEAN_RESULT (proxy_should_for_hostname ("localhost"), FALSE);
3344 VERIFY_BOOLEAN_RESULT (proxy_should_for_hostname ("LocalHost"), FALSE);
3345 VERIFY_BOOLEAN_RESULT (proxy_should_for_hostname ("127.0.0.1"), FALSE);
3346 VERIFY_BOOLEAN_RESULT (proxy_should_for_hostname ("127.127.0.1"), FALSE);
3347 VERIFY_BOOLEAN_RESULT (proxy_should_for_hostname ("www.yahoo.com"), TRUE);
3349 return !at_least_one_test_failed;
3352 gboolean vfs_module_self_test (void);
3355 vfs_module_self_test (void)
3361 ret = http_authn_self_test () && ret;
3363 ret = http_self_test () && ret;