Pull over some new test programs from the soup-refactoring branch,
authorDan Winship <danw@src.gnome.org>
Tue, 12 Aug 2003 16:00:31 +0000 (16:00 +0000)
committerDan Winship <danw@src.gnome.org>
Tue, 12 Aug 2003 16:00:31 +0000 (16:00 +0000)
along with the SoupUri changes they depend on.

* tests/simple-httpd.c: A really simple HTTP server, to test the
server code.

* tests/simple-proxy.c: An even simpler HTTP proxy

* tests/get.c: Add "-r" flag to recursively get files (thereby
testing multiple-connections-at-once code). Also good for setting
up a tree to use with simple-httpd.

* tests/timeserver.c (main): Fix a bug. (s/ipv6/ipv4/ in the
normal case)

* tests/uri-parsing.c: Regression test for the new soup-uri.c

* libsoup/soup-uri.c: Rewrite/update to conform to RFC 2396, and
pull in some optimizations from camel-url. Also, make SoupProtocol
a GQuark so we can still compare them with ==, but we can also
recognize any protocol.
(soup_uri_new_with_base): New, to merge base and relative URIs
(soup_uri_to_string): Update this. Change the "show_password" flag
(which we always passed FALSE for) to "just_path", for places that
want the path+query without the protocol, host, etc.

* libsoup/soup-queue.c (soup_get_request_header): Just use
soup_uri_to_string to generate the request URI.

* libsoup/soup-auth.c (compute_response, digest_auth_func): Use
"soup_uri_to_path (uri, TRUE)" rather than trying to reassemble
the URI by hand badly.
* libsoup/soup-server-auth.c (parse_digest): Likewise

* libsoup/soup-socks.c (soup_connect_socks_proxy): Change a
switch() to an series of if()s since SOUP_PROTOCOL_* aren't
constants any more.

* libsoup/soup-context.c (soup_context_uri_hash,
soup_context_uri_equal): s/querystring/query/

15 files changed:
ChangeLog
libsoup/soup-auth.c
libsoup/soup-context.c
libsoup/soup-queue.c
libsoup/soup-server-auth.c
libsoup/soup-socks.c
libsoup/soup-uri.c
libsoup/soup-uri.h
tests/.cvsignore
tests/Makefile.am
tests/get.c
tests/simple-httpd.c [new file with mode: 0644]
tests/simple-proxy.c [new file with mode: 0644]
tests/timeserver.c
tests/uri-parsing.c [new file with mode: 0644]

index f8165bd..2a7385d 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,48 @@
 2003-08-12  Dan Winship  <danw@ximian.com>
 
+       Pull over some new test programs from the soup-refactoring branch,
+       along with the SoupUri changes they depend on.
+
+       * tests/simple-httpd.c: A really simple HTTP server, to test the
+       server code.
+
+       * tests/simple-proxy.c: An even simpler HTTP proxy
+
+       * tests/get.c: Add "-r" flag to recursively get files (thereby
+       testing multiple-connections-at-once code). Also good for setting
+       up a tree to use with simple-httpd.
+
+       * tests/timeserver.c (main): Fix a bug. (s/ipv6/ipv4/ in the
+       normal case)
+
+       * tests/uri-parsing.c: Regression test for the new soup-uri.c
+
+       * libsoup/soup-uri.c: Rewrite/update to conform to RFC 2396, and
+       pull in some optimizations from camel-url. Also, make SoupProtocol
+       a GQuark so we can still compare them with ==, but we can also
+       recognize any protocol.
+       (soup_uri_new_with_base): New, to merge base and relative URIs
+       (soup_uri_to_string): Update this. Change the "show_password" flag
+       (which we always passed FALSE for) to "just_path", for places that
+       want the path+query without the protocol, host, etc.
+
+       * libsoup/soup-queue.c (soup_get_request_header): Just use
+       soup_uri_to_string to generate the request URI.
+
+       * libsoup/soup-auth.c (compute_response, digest_auth_func): Use
+       "soup_uri_to_path (uri, TRUE)" rather than trying to reassemble
+       the URI by hand badly.
+       * libsoup/soup-server-auth.c (parse_digest): Likewise
+
+       * libsoup/soup-socks.c (soup_connect_socks_proxy): Change a
+       switch() to an series of if()s since SOUP_PROTOCOL_* aren't
+       constants any more.
+
+       * libsoup/soup-context.c (soup_context_uri_hash,
+       soup_context_uri_equal): s/querystring/query/
+
+2003-08-12  Dan Winship  <danw@ximian.com>
+
        * configure.in: Bump API version to 2.2 and package version to
        2.1.0. Remove NSS and OpenSSL checks and proxy-related config. Use
        libgnutls-config to find GNUTLS.
index e9615eb..9f52d97 100644 (file)
@@ -186,10 +186,7 @@ compute_response (SoupAuthDigest *digest, SoupMessage *msg)
        char *url;
 
        uri = soup_context_get_uri (msg->context);
-       if (uri->querystring)
-               url = g_strdup_printf ("%s?%s", uri->path, uri->querystring);
-       else
-               url = g_strdup (uri->path);
+       url = soup_uri_to_string (uri, TRUE);
 
        /* compute A2 */
        md5_init (&ctx);
@@ -268,10 +265,7 @@ digest_auth_func (SoupAuth *auth, SoupMessage *message)
                g_assert_not_reached ();
 
        uri = soup_context_get_uri (message->context);
-       if (uri->querystring)
-               url = g_strdup_printf ("%s?%s", uri->path, uri->querystring);
-       else
-               url = g_strdup (uri->path);
+       url = soup_uri_to_string (uri, TRUE);
 
        nc = g_strdup_printf ("%.8x", digest->nc);
 
index 4c4cd30..c9418f0 100644 (file)
@@ -80,8 +80,8 @@ soup_context_uri_hash (gconstpointer key)
        ret = uri->protocol;
        if (uri->path)
                ret += g_str_hash (uri->path);
-       if (uri->querystring)
-               ret += g_str_hash (uri->querystring);
+       if (uri->query)
+               ret += g_str_hash (uri->query);
        if (uri->user)
                ret += g_str_hash (uri->user);
        if (uri->passwd)
@@ -122,7 +122,7 @@ soup_context_uri_equal (gconstpointer v1, gconstpointer v2)
                return FALSE;
        if (!parts_equal (one->passwd, two->passwd))
                return FALSE;
-       if (!parts_equal (one->querystring, two->querystring))
+       if (!parts_equal (one->query, two->query))
                return FALSE;
 
        return TRUE;
index 7e5060c..9c5c869 100644 (file)
@@ -389,20 +389,15 @@ soup_get_request_header (SoupMessage *req)
        proxy = soup_get_proxy ();
        suri = soup_context_get_uri (req->context);
 
-       if (!g_strcasecmp (req->method, "CONNECT")) 
-               /*
-                * CONNECT URI is hostname:port for tunnel destination
-                */
+       if (!g_strcasecmp (req->method, "CONNECT")) {
+               /* CONNECT URI is hostname:port for tunnel destination */
                uri = g_strdup_printf ("%s:%d", suri->host, suri->port);
-       else if (proxy)
-               /*
-                * Proxy expects full URI to destination
+       } else {
+               /* Proxy expects full URI to destination. Otherwise
+                * just the path.
                 */
-               uri = soup_uri_to_string (suri, FALSE);
-       else if (suri->querystring)
-               uri = g_strconcat (suri->path, "?", suri->querystring, NULL);
-       else
-               uri = g_strdup (suri->path);
+               uri = soup_uri_to_string (suri, !proxy);
+       }
 
        g_string_sprintfa (header,
                           req->priv->http_version == SOUP_HTTP_1_1 ? 
index d5432e2..c381b10 100644 (file)
@@ -262,16 +262,9 @@ parse_digest (SoupServerAuthContext *auth_ctx,
                        }
                        soup_uri_free (dig_uri);
                } else {        
-                       gchar *req_path;
-
-                       if (req_uri->querystring)
-                               req_path = 
-                                       g_strdup_printf ("%s?%s", 
-                                                        req_uri->path, 
-                                                        req_uri->querystring);
-                       else
-                               req_path = g_strdup (req_uri->path);
+                       char *req_path;
 
+                       req_path = soup_uri_to_string (req_uri, TRUE);
                        if (strcmp (uri, req_path) != 0) {
                                g_free (req_path);
                                goto DIGEST_AUTH_FAIL;
index 579f428..84ce9ef 100644 (file)
@@ -306,15 +306,12 @@ soup_connect_socks_proxy (SoupConnection        *conn,
        sd->cb = cb;
        sd->user_data = user_data;
        
-       switch (proxy_uri->protocol) {
-       case SOUP_PROTOCOL_SOCKS4:
+       if (proxy_uri->protocol == SOUP_PROTOCOL_SOCKS4) {
                soup_address_new (dest_uri->host, 
                                  soup_lookup_dest_addr_cb,
                                  sd);
                sd->phase = SOCKS_4_DEST_ADDR_LOOKUP;
-               break;
-
-       case SOUP_PROTOCOL_SOCKS5:
+       } else if (proxy_uri->protocol == SOUP_PROTOCOL_SOCKS5) {
                channel = soup_connection_get_iochannel (conn);
                g_io_add_watch (channel, 
                                G_IO_OUT, 
@@ -331,11 +328,8 @@ soup_connect_socks_proxy (SoupConnection        *conn,
                g_io_channel_unref (channel);
 
                sd->phase = SOCKS_5_SEND_INIT;
-               break;
-
-       default:
+       } else
                goto CONNECT_SUCCESS;
-       }
 
        return;
        
index d522349..8ec9491 100644 (file)
 /* soup-uri.c : utility functions to parse URLs */
 
 /*
- * Authors :
- *  Dan Winship <danw@ximian.com>
- *  Alex Graveley <alex@ximian.com>
- *
- * Copyright 1999-2002 Ximian, Inc.
- */
-
-
-
-/*
- * Here we deal with URLs following the general scheme:
- *   protocol://user;AUTH=mech:password@host:port/name
- * where name is a path-like string (ie dir1/dir2/....) See RFC 1738
- * for the complete description of Uniform Resource Locators. The
- * ";AUTH=mech" addition comes from RFC 2384, "POP URL Scheme".
- */
-
-/* XXX TODO:
- * recover the words between #'s or ?'s after the path
- * % escapes
+ * Copyright 1999-2003 Ximian, Inc.
  */
 
+#include <ctype.h>
 #include <string.h>
 #include <stdlib.h>
 
 #include "soup-uri.h"
-#include "soup-misc.h"
-
-typedef struct {
-       SoupProtocol  proto;
-       gchar        *str;
-       gint          port;
-} SoupKnownProtocols;
-
-SoupKnownProtocols known_protocols [] = {
-       { SOUP_PROTOCOL_HTTP,   "http://",   80 },
-       { SOUP_PROTOCOL_HTTPS,  "https://",  443 },
-       { SOUP_PROTOCOL_SMTP,   "mailto:",   25 },
-       { SOUP_PROTOCOL_SOCKS4, "socks4://", -1 },
-       { SOUP_PROTOCOL_SOCKS5, "socks5://", -1 },
-       { SOUP_PROTOCOL_FILE,   "file://",   -1 },
-       { 0 }
-};
 
-static SoupProtocol
-soup_uri_get_protocol (const gchar *proto, int *len)
+static inline SoupProtocol
+soup_uri_get_protocol (const char *proto, int len)
 {
-       SoupKnownProtocols *known = known_protocols;
+       char proto_buf[128];
 
-       while (known->proto) {
-               if (!g_strncasecmp (proto, known->str, strlen (known->str))) {
-                       *len = strlen (known->str);
-                       return known->proto;
-               }
-               known++;
-       }
+       g_return_val_if_fail (len < sizeof (proto_buf), 0);
 
-       *len = 0;
-       return 0;
+       memcpy (proto_buf, proto, len);
+       proto_buf[len] = '\0';
+       return g_quark_from_string (proto_buf);
 }
 
-static gchar *
-soup_uri_protocol_to_string (SoupProtocol proto)
+static inline const char *
+soup_protocol_name (SoupProtocol proto)
 {
-       SoupKnownProtocols *known = known_protocols;
-
-       while (known->proto) {
-               if (known->proto == proto) return known->str;
-               known++;
-       }
-
-       return "";
+       return g_quark_to_string (proto);
 }
 
-static gint
-soup_uri_get_default_port (SoupProtocol proto)
+static inline guint
+soup_protocol_default_port (SoupProtocol proto)
 {
-       SoupKnownProtocols *known = known_protocols;
-
-       while (known->proto) {
-               if (known->proto == proto) return known->port;
-               known++;
-       }
-
-       return -1;
+       if (proto == SOUP_PROTOCOL_HTTP)
+               return 80;
+       else if (proto == SOUP_PROTOCOL_HTTPS)
+               return 443;
+       else
+               return 0;
 }
 
-/*
- * Ripped off from libxml
- */
-static void
-normalize_path (gchar *path)
-{
-       char *cur, *out;
+static void append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars);
 
-       /* 
-        * Skip all initial "/" chars.  We want to get to the beginning of the
-        * first non-empty segment.
-        */
-       cur = path;
-       while (cur[0] == '/')
-               ++cur;
-       if (cur[0] == '\0')
-               return;
+/**
+ * soup_uri_new_with_base:
+ * @base: a base URI
+ * @uri_string: the URI
+ *
+ * Parses @uri_string relative to @base.
+ *
+ * Return value: a parsed SoupUri.
+ **/
+SoupUri *
+soup_uri_new_with_base (const SoupUri *base, const char *uri_string)
+{
+       SoupUri *uri;
+       const char *end, *hash, *colon, *semi, *at, *slash, *question;
+       const char *p;
 
-       /* Keep everything we've seen so far.  */
-       out = cur;
+       uri = g_new0 (SoupUri, 1);
 
-       /*
-        * Analyze each segment in sequence for cases (c) and (d).
+       /* See RFC2396 for details. IF YOU CHANGE ANYTHING IN THIS
+        * FUNCTION, RUN tests/uri-parsing AFTERWARDS.
         */
-       while (cur[0] != '\0') {
-               /*
-                * c) All occurrences of "./", where "." is a complete path
-                * segment, are removed from the buffer string.  
-                */
-               if ((cur[0] == '.') && (cur[1] == '/')) {
-                       cur += 2;
-                       /* 
-                        * '//' normalization should be done at this point too
-                        */
-                       while (cur[0] == '/')
-                               cur++;
-                       continue;
-               }
 
-               /*
-                * d) If the buffer string ends with "." as a complete path
-                * segment, that "." is removed.  
-                */
-               if ((cur[0] == '.') && (cur[1] == '\0'))
-                       break;
-
-               /* Otherwise keep the segment.  */
-               while (cur[0] != '/') {
-                       if (cur[0] == '\0')
-                               goto done_cd;
-                       (out++)[0] = (cur++)[0];
+       /* Find fragment. */
+       end = hash = strchr (uri_string, '#');
+       if (hash && hash[1]) {
+               uri->fragment = g_strdup (hash + 1);
+               soup_uri_decode (uri->fragment);
+       } else
+               end = uri_string + strlen (uri_string);
+
+       /* Find protocol: initial [a-z+.-]* substring until ":" */
+       p = uri_string;
+       while (p < end && (isalnum ((unsigned char)*p) ||
+                          *p == '.' || *p == '+' || *p == '-'))
+               p++;
+
+       if (p > uri_string && *p == ':') {
+               uri->protocol = soup_uri_get_protocol (uri_string, p - uri_string);
+               if (!uri->protocol) {
+                       soup_uri_free (uri);
+                       return NULL;
                }
-               /* nomalize '//' */
-               while ((cur[0] == '/') && (cur[1] == '/'))
-                       cur++;
-               
-               (out++)[0] = (cur++)[0];
+               uri_string = p + 1;
        }
- done_cd:
-       out[0] = '\0';
-
-       /* Reset to the beginning of the first segment for the next sequence. */
-       cur = path;
-       while (cur[0] == '/')
-               ++cur;
-       if (cur[0] == '\0')
-               return;
-
-       /*
-        * Analyze each segment in sequence for cases (e) and (f).
-        *
-        * e) All occurrences of "<segment>/../", where <segment> is a
-        *    complete path segment not equal to "..", are removed from the
-        *    buffer string.  Removal of these path segments is performed
-        *    iteratively, removing the leftmost matching pattern on each
-        *    iteration, until no matching pattern remains.
-        *
-        * f) If the buffer string ends with "<segment>/..", where <segment>
-        *    is a complete path segment not equal to "..", that
-        *    "<segment>/.." is removed.
-        *
-        * To satisfy the "iterative" clause in (e), we need to collapse the
-        * string every time we find something that needs to be removed.  Thus,
-        * we don't need to keep two pointers into the string: we only need a
-        * "current position" pointer.
-        */
-       while (1) {
-               char *segp;
-
-               /* 
-                * At the beginning of each iteration of this loop, "cur" points
-                * to the first character of the segment we want to examine.  
-                */
-
-               /* Find the end of the current segment.  */
-               segp = cur;
-               while ((segp[0] != '/') && (segp[0] != '\0'))
-                       ++segp;
-
-               /* 
-                * If this is the last segment, we're done (we need at least two
-                * segments to meet the criteria for the (e) and (f) cases).
-                */
-               if (segp[0] == '\0')
-                       break;
-
-               /* 
-                * If the first segment is "..", or if the next segment _isn't_
-                * "..", keep this segment and try the next one.  
-                */
-               ++segp;
-               if (((cur[0] == '.') && (cur[1] == '.') && (segp == cur+3))
-                   || ((segp[0] != '.') || (segp[1] != '.')
-                       || ((segp[2] != '/') && (segp[2] != '\0')))) {
-                       cur = segp;
-                       continue;
+
+       if (!*uri_string && !base)
+               return uri;
+
+       /* Check for authority */
+       if (strncmp (uri_string, "//", 2) == 0) {
+               uri_string += 2;
+
+               slash = uri_string + strcspn (uri_string, "/#");
+               at = strchr (uri_string, '@');
+               if (at && at < slash) {
+                       colon = strchr (uri_string, ':');
+                       if (colon && colon < at) {
+                               uri->passwd = g_strndup (colon + 1,
+                                                        at - colon - 1);
+                               soup_uri_decode (uri->passwd);
+                       } else {
+                               uri->passwd = NULL;
+                               colon = at;
+                       }
+
+                       semi = strchr (uri_string, ';');
+                       if (semi && semi < colon &&
+                           !strncasecmp (semi, ";auth=", 6)) {
+                               uri->authmech = g_strndup (semi + 6,
+                                                          colon - semi - 6);
+                               soup_uri_decode (uri->authmech);
+                       } else {
+                               uri->authmech = NULL;
+                               semi = colon;
+                       }
+
+                       uri->user = g_strndup (uri_string, semi - uri_string);
+                       soup_uri_decode (uri->user);
+                       uri_string = at + 1;
+               } else
+                       uri->user = uri->passwd = uri->authmech = NULL;
+
+               /* Find host and port. */
+               colon = strchr (uri_string, ':');
+               if (colon && colon < slash) {
+                       uri->host = g_strndup (uri_string, colon - uri_string);
+                       uri->port = strtoul (colon + 1, NULL, 10);
+               } else {
+                       uri->host = g_strndup (uri_string, slash - uri_string);
+                       soup_uri_decode (uri->host);
                }
 
-               /* 
-                * If we get here, remove this segment and the next one and back
-                * up to the previous segment (if there is one), to implement
-                * the "iteratively" clause.  It's pretty much impossible to
-                * back up while maintaining two pointers into the buffer, so
-                * just compact the whole buffer now.  
-                */
-
-               /* If this is the end of the buffer, we're done.  */
-               if (segp[2] == '\0') {
-                       cur[0] = '\0';
-                       break;
+               uri_string = slash;
+       }
+
+       /* Find query */
+       question = memchr (uri_string, '?', end - uri_string);
+       if (question) {
+               if (question[1]) {
+                       uri->query = g_strndup (question + 1,
+                                               end - (question + 1));
+                       soup_uri_decode (uri->query);
                }
-               strcpy(cur, segp + 3);
-
-               /* 
-                * If there are no previous segments, then keep going from
-                * here. 
-                */
-               segp = cur;
-               while ((segp > path) && ((--segp)[0] == '/'))
-                       ;
-               if (segp == path)
-                       continue;
-
-               /* 
-                * "segp" is pointing to the end of a previous segment; find
-                * it's start.  We need to back up to the previous segment and
-                * start over with that to handle things like "foo/bar/../..".
-                * If we don't do this, then on the first pass we'll remove the
-                * "bar/..", but be pointing at the second ".." so we won't
-                * realize we can also remove the "foo/..".  
-                */
-               cur = segp;
-               while ((cur > path) && (cur[-1] != '/'))
-                       --cur;
+               end = question;
        }
-       out[0] = '\0';
-
-       /*
-        * g) If the resulting buffer string still begins with one or more
-        *    complete path segments of "..", then the reference is
-        *    considered to be in error. Implementations may handle this
-        *    error by retaining these components in the resolved path (i.e.,
-        *    treating them as part of the final URI), by removing them from
-        *    the resolved path (i.e., discarding relative levels above the
-        *    root), or by avoiding traversal of the reference.
-        *
-        * We discard them from the final path.
-        */
-       if (path[0] == '/') {
-               cur = path;
-               while ((cur[1] == '.') && (cur[2] == '.')
-                      && ((cur[3] == '/') || (cur[3] == '\0')))
-                       cur += 3;
-
-               if (cur != path) {
-                       out = path;
-                       while (cur[0] != '\0')
-                               (out++)[0] = (cur++)[0];
-                       out[0] = 0;
+
+       if (end != uri_string) {
+               uri->path = g_strndup (uri_string, end - uri_string);
+               soup_uri_decode (uri->path);
+       }
+
+       /* Apply base URI. Again, this is spelled out in RFC 2396. */
+       if (base && !uri->protocol && uri->host)
+               uri->protocol = base->protocol;
+       else if (base && !uri->protocol) {
+               uri->protocol = base->protocol;
+               uri->user = g_strdup (base->user);
+               uri->authmech = g_strdup (base->authmech);
+               uri->passwd = g_strdup (base->passwd);
+               uri->host = g_strdup (base->host);
+               uri->port = base->port;
+
+               if (!uri->path) {
+                       if (uri->query)
+                               uri->path = g_strdup ("");
+                       else {
+                               uri->path = g_strdup (base->path);
+                               uri->query = g_strdup (base->query);
+                       }
+               }
+
+               if (*uri->path != '/') {
+                       char *newpath, *last, *p, *q;
+
+                       last = strrchr (base->path, '/');
+                       if (last) {
+                               newpath = g_strdup_printf ("%.*s/%s",
+                                                          last - base->path,
+                                                          base->path,
+                                                          uri->path);
+                       } else
+                               newpath = g_strdup_printf ("/%s", uri->path);
+
+                       /* Remove "./" where "." is a complete segment. */
+                       for (p = newpath + 1; *p; ) {
+                               if (*(p - 1) == '/' &&
+                                   *p == '.' && *(p + 1) == '/')
+                                       memmove (p, p + 2, strlen (p + 2) + 1);
+                               else
+                                       p++;
+                       }
+                       /* Remove "." at end. */
+                       if (p > newpath + 2 &&
+                           *(p - 1) == '.' && *(p - 2) == '/')
+                               *(p - 1) = '\0';
+                       /* Remove "<segment>/../" where <segment> != ".." */
+                       for (p = newpath + 1; *p; ) {
+                               if (!strncmp (p, "../", 3)) {
+                                       p += 3;
+                                       continue;
+                               }
+                               q = strchr (p + 1, '/');
+                               if (!q)
+                                       break;
+                               if (strncmp (q, "/../", 4) != 0) {
+                                       p = q + 1;
+                                       continue;
+                               }
+                               memmove (p, q + 4, strlen (q + 4) + 1);
+                               p = newpath + 1;
+                       }
+                       /* Remove "<segment>/.." at end where <segment> != ".." */
+                       q = strrchr (newpath, '/');
+                       if (q && !strcmp (q, "/..")) {
+                               p = q - 1;
+                               while (p > newpath && *p != '/')
+                                       p--;
+                               if (strncmp (p, "/../", 4) != 0)
+                                       *(p + 1) = 0;
+                       }
+
+                       g_free (uri->path);
+                       uri->path = newpath;
                }
        }
 
-       return;
+       if (!uri->port)
+               uri->port = soup_protocol_default_port (uri->protocol);
+       if (!uri->path)
+               uri->path = g_strdup ("");
+
+       return uri;
 }
 
 /**
- * soup_uri_new: create a SoupUri object from a string
- * @uri_string: The string containing the URL to scan
+ * soup_uri_new:
+ * @uri_string: a URI
  *
- * This routine takes a gchar and parses it as a
- * URL of the form:
- *   protocol://user;AUTH=mech:password@host:port/path?querystring
- * There is no test on the values. For example,
- * "port" can be a string, not only a number!
- * The SoupUri structure fields are filled with
- * the scan results. When a member of the
- * general URL can not be found, the corresponding
- * SoupUri member is NULL.
- * Fields filled in the SoupUri structure are allocated
- * and url_string is not modified.
+ * Parses an absolute URI.
  *
- * Return value: a SoupUri structure containing the URL items.
+ * Return value: a SoupUri, or %NULL.
  **/
 SoupUri *
-soup_uri_new (const gchar* uri_string)
+soup_uri_new (const char *uri_string)
 {
-       SoupUri *g_uri;
-       char *semi, *colon, *at, *slash, *path, *query = NULL;
-       char **split;
-
-       g_uri = g_new0 (SoupUri,1);
-
-       /* Find protocol: initial substring until "://" */
-       colon = strchr (uri_string, ':');
-       if (colon) {
-               gint protolen;
-               g_uri->protocol = soup_uri_get_protocol (uri_string, &protolen);
-               uri_string += protolen;
-       }
+       SoupUri *uri;
 
-       /* Must have a protocol */
-       if (!g_uri->protocol) {
-               g_free (g_uri);
+       uri = soup_uri_new_with_base (NULL, uri_string);
+       if (!uri)
+               return NULL;
+       if (!uri->protocol) {
+               soup_uri_free (uri);
                return NULL;
        }
 
-       /* If there is an @ sign, look for user, authmech, and
-        * password before it.
-        */
-       slash = strchr (uri_string, '/');
-       at = strchr (uri_string, '@');
-       if (at && (!slash || at < slash)) {
-               colon = strchr (uri_string, ':');
-               if (colon && colon < at)
-                       g_uri->passwd = g_strndup (colon + 1, at - colon - 1);
-               else {
-                       g_uri->passwd = NULL;
-                       colon = at;
-               }
-
-               semi = strchr(uri_string, ';');
-               if (semi && semi < colon && !g_strncasecmp (semi, ";auth=", 6))
-                       g_uri->authmech = g_strndup (semi + 6,
-                                                    colon - semi - 6);
-               else {
-                       g_uri->authmech = NULL;
-                       semi = colon;
-               }
+       return uri;
+}
 
-               g_uri->user = g_strndup (uri_string, semi - uri_string);
-               uri_string = at + 1;
-       } else
-               g_uri->user = g_uri->passwd = g_uri->authmech = NULL;
+/**
+ * soup_uri_to_string:
+ * @uri: a SoupUri
+ * @just_path: if %TRUE, output just the path and query portions
+ *
+ * Return value: a string representing @uri, which the caller must free.
+ **/
+char *
+soup_uri_to_string (const SoupUri *uri, gboolean just_path)
+{
+       GString *str;
+       char *return_result;
 
-       /* Find host (required) and port. */
-       colon = strchr (uri_string, ':');
-       if (slash && colon > slash)
-               colon = 0;
+       /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN
+        * tests/uri-parsing AFTERWARD.
+        */
 
-       if (colon) {
-               g_uri->host = g_strndup (uri_string, colon - uri_string);
-               if (slash)
-                       g_uri->port = atoi(colon + 1);
-               else
-                       g_uri->port = atoi(colon + 1);
-       } else if (slash) {
-               g_uri->host = g_strndup (uri_string, slash - uri_string);
-               g_uri->port = soup_uri_get_default_port (g_uri->protocol);
-       } else {
-               g_uri->host = g_strdup (uri_string);
-               g_uri->port = soup_uri_get_default_port (g_uri->protocol);
+       str = g_string_sized_new (20);
+
+       if (uri->protocol && !just_path)
+               g_string_sprintfa (str, "%s:", soup_protocol_name (uri->protocol));
+       if (uri->host && !just_path) {
+               g_string_append (str, "//");
+               if (uri->user) {
+                       append_uri_encoded (str, uri->user, ":;@/");
+                       if (uri->authmech && *uri->authmech) {
+                               g_string_append (str, ";auth=");
+                               append_uri_encoded (str, uri->authmech, ":@/");
+                       }
+                       g_string_append_c (str, '@');
+               }
+               append_uri_encoded (str, uri->host, ":/");
+               if (uri->port && uri->port != soup_protocol_default_port (uri->protocol))
+                       g_string_append_printf (str, ":%d", uri->port);
+               if (!uri->path && (uri->query || uri->fragment))
+                       g_string_append_c (str, '/');
        }
 
-       /* setup a fallback, if relative, then empty string, else
-          it will be from root */
-       if (slash == NULL) {
-               slash = "/";
+       if (uri->path)
+               append_uri_encoded (str, uri->path, "?");
+       else if (just_path)
+               g_string_append_c (str, '/');
+
+       if (uri->query) {
+               g_string_append_c (str, '?');
+               append_uri_encoded (str, uri->query, NULL);
        }
-       if (slash && *slash && !g_uri->protocol)
-               slash++;
-
-       split = g_strsplit(slash, " ", 0);
-       path = g_strjoinv("%20", split);
-       g_strfreev(split);
-
-       if (path)
-               query = strchr (path, '?');
-
-       if (path && query) {
-               g_uri->path = g_strndup (path, query - path);
-               g_uri->querystring = g_strdup (++query);
-               g_free (path);
-       } else {
-               g_uri->path = path;
-               g_uri->querystring = NULL;
+       if (uri->fragment && !just_path) {
+               g_string_append_c (str, '#');
+               append_uri_encoded (str, uri->fragment, NULL);
        }
 
-       if (g_uri->path)
-               normalize_path (g_uri->path);
+       return_result = str->str;
+       g_string_free (str, FALSE);
 
-       return g_uri;
-}
-
-/* Need to handle mailto which apparantly doesn't use the "//" after the : */
-gchar *
-soup_uri_to_string (const SoupUri *uri, gboolean show_passwd)
-{
-       g_return_val_if_fail (uri != NULL, NULL);
-
-       if (uri->port != -1 &&
-           uri->port != soup_uri_get_default_port (uri->protocol))
-               return g_strdup_printf(
-                       "%s%s%s%s%s%s%s%s:%d%s%s%s%s",
-                       soup_uri_protocol_to_string (uri->protocol),
-                       uri->user ? uri->user : "",
-                       uri->authmech ? ";auth=" : "",
-                       uri->authmech ? uri->authmech : "",
-                       uri->passwd && show_passwd ? ":" : "",
-                       uri->passwd && show_passwd ? uri->passwd : "",
-                       uri->user ? "@" : "",
-                       uri->host,
-                       uri->port,
-                       uri->path && *uri->path != '/' ? "/" : "",
-                       uri->path ? uri->path : "",
-                       uri->querystring ? "?" : "",
-                       uri->querystring ? uri->querystring : "");
-       else
-               return g_strdup_printf(
-                       "%s%s%s%s%s%s%s%s%s%s%s%s",
-                       soup_uri_protocol_to_string (uri->protocol),
-                       uri->user ? uri->user : "",
-                       uri->authmech ? ";auth=" : "",
-                       uri->authmech ? uri->authmech : "",
-                       uri->passwd && show_passwd ? ":" : "",
-                       uri->passwd && show_passwd ? uri->passwd : "",
-                       uri->user ? "@" : "",
-                       uri->host,
-                       uri->path && *uri->path != '/' ? "/" : "",
-                       uri->path ? uri->path : "",
-                       uri->querystring ? "?" : "",
-                       uri->querystring ? uri->querystring : "");
+       return return_result;
 }
 
 SoupUri *
-soup_uri_copy (const SoupUriuri)
+soup_uri_copy (const SoupUri *uri)
 {
        SoupUri *dup;
 
        g_return_val_if_fail (uri != NULL, NULL);
 
        dup = g_new0 (SoupUri, 1);
-       dup->protocol    = uri->protocol;
-       dup->user        = g_strdup (uri->user);
-       dup->authmech    = g_strdup (uri->authmech);
-       dup->passwd      = g_strdup (uri->passwd);
-       dup->host        = g_strdup (uri->host);
-       dup->port        = uri->port;
-       dup->path        = g_strdup (uri->path);
-       dup->querystring = g_strdup (uri->querystring);
+       dup->protocol = uri->protocol;
+       dup->user     = g_strdup (uri->user);
+       dup->authmech = g_strdup (uri->authmech);
+       dup->passwd   = g_strdup (uri->passwd);
+       dup->host     = g_strdup (uri->host);
+       dup->port     = uri->port;
+       dup->path     = g_strdup (uri->path);
+       dup->query    = g_strdup (uri->query);
+       dup->fragment = g_strdup (uri->fragment);
 
        return dup;
 }
 
-gboolean 
-soup_uri_equal (const SoupUri *u1, 
-               const SoupUri *u2)
+static inline gboolean
+parts_equal (const char *one, const char *two)
 {
-       if (u1->protocol == u2->protocol            &&
-           u1->port     == u2->port                &&
-           !strcmp (u1->user,        u2->user)     &&
-           !strcmp (u1->authmech,    u2->authmech) &&
-           !strcmp (u1->passwd,      u2->passwd)   &&
-           !strcmp (u1->host,        u2->host)     &&
-           !strcmp (u1->path,        u2->path)     &&
-           !strcmp (u1->querystring, u2->querystring))
+       if (!one && !two)
                return TRUE;
+       if (!one || !two)
+               return FALSE;
+       return !strcmp (one, two);
+}
 
-       return FALSE;
+gboolean 
+soup_uri_equal (const SoupUri *u1, const SoupUri *u2)
+{
+       if (u1->protocol != u2->protocol              ||
+           u1->port     != u2->port                  ||
+           !parts_equal (u1->user, u2->user)         ||
+           !parts_equal (u1->authmech, u2->authmech) ||
+           !parts_equal (u1->passwd, u2->passwd)     ||
+           !parts_equal (u1->host, u2->host)         ||
+           !parts_equal (u1->path, u2->path)         ||
+           !parts_equal (u1->query, u2->query)       ||
+           !parts_equal (u1->fragment, u2->fragment))
+               return FALSE;
+
+       return TRUE;
 }
 
 void
@@ -479,16 +376,17 @@ soup_uri_free (SoupUri *uri)
        g_free (uri->passwd);
        g_free (uri->host);
        g_free (uri->path);
-       g_free (uri->querystring);
+       g_free (uri->query);
+       g_free (uri->fragment);
 
        g_free (uri);
 }
 
 void
-soup_uri_set_auth  (SoupUri       *uri, 
-                   const gchar   *user, 
-                   const gchar   *passwd, 
-                   const gchar   *authmech)
+soup_uri_set_auth  (SoupUri    *uri, 
+                   const char *user, 
+                   const char *passwd, 
+                   const char *authmech)
 {
        g_return_if_fail (uri != NULL);
 
@@ -501,17 +399,84 @@ soup_uri_set_auth  (SoupUri       *uri,
        uri->authmech = g_strdup (authmech);
 }
 
-void
-soup_debug_print_uri (SoupUri *uri)
+/* From RFC 2396 2.4.3, the characters that should always be encoded */
+static const char uri_encoded_char[] = {
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x00 - 0x0f */
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x10 - 0x1f */
+       1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  ' ' - '/'  */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,  /*  '0' - '?'  */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  '@' - 'O'  */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,  /*  'P' - '_'  */
+       1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  '`' - 'o'  */
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,  /*  'p' - 0x7f */
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+static void
+append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars)
 {
-       g_return_if_fail (uri != NULL);
+       const unsigned char *s = (const unsigned char *)in;
+
+       while (*s) {
+               if (uri_encoded_char[*s] ||
+                   (extra_enc_chars && strchr (extra_enc_chars, *s)))
+                       g_string_append_printf (str, "%%%02x", (int)*s++);
+               else
+                       g_string_append_c (str, *s++);
+       }
+}
+
+/**
+ * soup_uri_encode:
+ * @part: a URI part
+ * @escape_extra: additional characters beyond " \"%#<>{}|\^[]`"
+ * to escape (or %NULL)
+ *
+ * This %-encodes the given URI part and returns the escaped version
+ * in allocated memory, which the caller must free when it is done.
+ **/
+char *
+soup_uri_encode (const char *part, const char *escape_extra)
+{
+       GString *str;
+       char *encoded;
+
+       str = g_string_new (NULL);
+       append_uri_encoded (str, part, escape_extra);
+       encoded = str->str;
+       g_string_free (str, FALSE);
 
-       g_print ("Protocol: %s\n", soup_uri_protocol_to_string (uri->protocol));
-       g_print ("User:     %s\n", uri->user);
-       g_print ("Authmech: %s\n", uri->authmech);
-       g_print ("Password: %s\n", uri->passwd);
-       g_print ("Host:     %s\n", uri->host);
-       g_print ("Path:     %s\n", uri->path);
-       g_print ("Querystr: %s\n", uri->querystring);
+       return encoded;
 }
 
+/**
+ * soup_uri_decode:
+ * @part: a URI part
+ *
+ * %-decodes the passed-in URI *in place*. The decoded version is
+ * never longer than the encoded version, so there does not need to
+ * be any additional space at the end of the string.
+ */
+void
+soup_uri_decode (char *part)
+{
+       unsigned char *s, *d;
+
+#define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
+
+       s = d = (unsigned char *)part;
+       do {
+               if (*s == '%' && s[1] && s[2]) {
+                       *d++ = (XDIGIT (s[1]) << 4) + XDIGIT (s[2]);
+                       s += 2;
+               } else
+                       *d++ = *s;
+       } while (*s++);
+}
index 5a24f70..0aa4bae 100644 (file)
 
 #include <glib.h>
 
-typedef enum {
-       SOUP_PROTOCOL_HTTP = 1,
-       SOUP_PROTOCOL_HTTPS,
-       SOUP_PROTOCOL_SMTP,
-       SOUP_PROTOCOL_SOCKS4,
-       SOUP_PROTOCOL_SOCKS5,
-       SOUP_PROTOCOL_FILE
-} SoupProtocol;
+typedef GQuark SoupProtocol;
+#define SOUP_PROTOCOL_HTTP (g_quark_from_static_string ("http"))
+#define SOUP_PROTOCOL_HTTPS (g_quark_from_static_string ("https"))
+#define SOUP_PROTOCOL_SOCKS4 (g_quark_from_static_string ("socks4"))
+#define SOUP_PROTOCOL_SOCKS5 (g_quark_from_static_string ("socks5"))
 
 typedef struct {
-       SoupProtocol        protocol;
+       SoupProtocol  protocol;
 
-       gchar              *user;
-       gchar              *authmech;
-       gchar              *passwd;
+       char         *user;
+       char         *authmech;
+       char         *passwd;
 
-       gchar              *host;
-       gint                port;
+       char         *host;
+       guint         port;
 
-       gchar              *path;
-       gchar              *querystring;
+       char         *path;
+       char         *query;
+
+       char         *fragment;
 } SoupUri;
 
-SoupUri *soup_uri_new       (const gchar   *uri_string);
+SoupUri *soup_uri_new_with_base (const SoupUri *base,
+                                const char    *uri_string);
+SoupUri *soup_uri_new           (const char    *uri_string);
+
+char    *soup_uri_to_string     (const SoupUri *uri, 
+                                gboolean       just_path);
 
-gchar   *soup_uri_to_string (const SoupUri *uri, 
-                            gboolean       show_password);
+SoupUri *soup_uri_copy          (const SoupUri *uri);
 
-SoupUri *soup_uri_copy      (const SoupUri *uri);
+gboolean soup_uri_equal         (const SoupUri *uri1, 
+                                const SoupUri *uri2);
 
-gboolean soup_uri_equal     (const SoupUri *uri1, 
-                            const SoupUri *uri2);
+void     soup_uri_free          (SoupUri       *uri);
 
-void     soup_uri_free      (SoupUri       *uri);
+void     soup_uri_set_auth      (SoupUri       *uri, 
+                                const char    *user, 
+                                const char    *passwd, 
+                                const char    *authmech);
 
-void     soup_uri_set_auth  (SoupUri       *uri, 
-                            const gchar   *user, 
-                            const gchar   *passwd, 
-                            const gchar   *authmech);
+char    *soup_uri_encode        (const char    *part,
+                                const char    *escape_extra);
+void     soup_uri_decode        (char          *part);
 
 #endif /*SOUP_URI_H*/
index c7b7d69..14ba20c 100644 (file)
@@ -1,5 +1,8 @@
 Makefile
 Makefile.in
+auth-test
 get
+simple-httpd
+simple-proxy
 timeserver
-auth-test
+uri-parsing
index 0420614..7419ee0 100644 (file)
@@ -2,17 +2,16 @@ INCLUDES =            \
        -I$(top_srcdir) \
        $(GLIB_CFLAGS)
 
-noinst_PROGRAMS = get timeserver
+LIBS = $(top_builddir)/libsoup/libsoup-$(SOUP_API_VERSION).la
 
-get_SOURCES = get.c
-
-get_LDADD = $(top_builddir)/libsoup/libsoup-$(SOUP_API_VERSION).la
+noinst_PROGRAMS = get timeserver simple-httpd simple-proxy
 
+get_SOURCES = get.c
 timeserver_SOURCES = timeserver.c
+simple_httpd_SOURCES = simple-httpd.c
+simple_proxy_SOURCES = simple-proxy.c
 
-timeserver_LDADD = $(top_builddir)/libsoup/libsoup-$(SOUP_API_VERSION).la
-
-check_PROGRAMS = auth-test
+check_PROGRAMS = auth-test uri-parsing
 
 auth_test_SOURCES = auth-test.c
-auth_test_LDADD = $(top_builddir)/libsoup/libsoup-$(SOUP_API_VERSION).la
+uri_parsing_SOURCES = uri-parsing.c
index 5d83d34..ffb402f 100644 (file)
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2001-2003, Ximian, Inc.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
 #include <libsoup/soup.h>
 
-int
-main (int argc, char **argv)
+gboolean recurse = FALSE;
+GMainLoop *loop;
+char *base;
+SoupUri *base_uri;
+int pending;
+
+static GPtrArray *
+find_hrefs (const SoupUri *base, const char *body, int length)
+{
+       GPtrArray *hrefs = g_ptr_array_new ();
+       char *buf = g_strndup (body, length);
+       char *start = buf, *end;
+       char *href, *frag;
+       SoupUri *uri;
+
+       while ((start = strstr (start, "href"))) {
+               start += 4;
+               while (isspace ((unsigned char) *start))
+                       start++;
+               if (*start++ != '=') 
+                       continue;
+               while (isspace ((unsigned char) *start))
+                       start++;
+               if (*start++ != '"')
+                       continue;
+
+               end = strchr (start, '"');
+               if (!end)
+                       break;
+
+               href = g_strndup (start, end - start);
+               start = end;
+               frag = strchr (href, '#');
+               if (frag)
+                       *frag = '\0';
+
+               uri = soup_uri_new_with_base (base, href);
+               g_free (href);
+
+               if (!uri)
+                       continue;
+               if (base->protocol != uri->protocol ||
+                   base->port != uri->port ||
+                   g_strcasecmp (base->host, uri->host) != 0) {
+                       soup_uri_free (uri);
+                       continue;
+               }
+
+               if (strncmp (base->path, uri->path, strlen (base->path)) != 0) {
+                       soup_uri_free (uri);
+                       continue;
+               }
+
+               g_ptr_array_add (hrefs, soup_uri_to_string (uri, FALSE));
+               soup_uri_free (uri);
+       }
+       g_free (buf);
+
+       return hrefs;
+}
+
+static void
+mkdirs (const char *path)
+{
+       char *slash;
+
+       for (slash = strchr (path, '/'); slash; slash = strchr (slash + 1, '/')) {
+               *slash = '\0';
+               if (*path && mkdir (path, 0755) == -1 && errno != EEXIST) {
+                       fprintf (stderr, "Could not create '%s'\n", path);
+                       g_main_loop_quit (loop);
+                       return;
+               }
+               *slash = '/';
+       }
+}
+
+static void get_url (const char *url);
+
+static void
+got_url (SoupMessage *msg, gpointer uri)
 {
+       char *name;
+       int fd, i;
+       GPtrArray *hrefs;
+       const char *header;
        SoupContext *ctx;
-       SoupMessage *msg;
 
-       if (argc != 2) {
-               fprintf (stderr, "Usage: %s URL\n", argv[0]);
-               exit (1);
+       ctx = soup_message_get_context (msg);
+       name = soup_context_get_uri (ctx)->path;
+       if (strncmp (base_uri->path, name, strlen (base_uri->path)) != 0) {
+               fprintf (stderr, "  Error: not under %s\n", base_uri->path);
+               goto DONE;
+       }
+       printf ("%s: %d %s\n", name, msg->errorcode, msg->errorphrase);
+       if (SOUP_ERROR_IS_REDIRECTION (msg->errorcode)) {
+               header = soup_message_get_header (msg->response_headers, "Location");
+               if (header) {
+                       printf ("  -> %s\n", header);
+                       get_url (header);
+               }
+               goto DONE;
        }
 
-       ctx = soup_context_get (argv[1]);
-       if (!ctx) {
-               fprintf (stderr, "Could not parse '%s' as a URL\n", argv[1]);
-               exit (1);
+       if (!SOUP_ERROR_IS_SUCCESSFUL (msg->errorcode))
+               goto DONE;
+
+       name += strlen (base_uri->path);
+       if (*name == '/')
+               name++;
+
+       if (recurse)
+               fd = open (name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+       else
+               fd = STDOUT_FILENO;
+       write (fd, msg->response.body, msg->response.length);
+       if (!recurse)
+               goto DONE;
+       close (fd);
+
+       header = soup_message_get_header (msg->response_headers, "Content-Type");
+       if (header && g_strncasecmp (header, "text/html", 9) != 0)
+               goto DONE;
+
+       hrefs = find_hrefs (uri, msg->response.body, msg->response.length);
+       for (i = 0; i < hrefs->len; i++) {
+               get_url (hrefs->pdata[i]);
+               g_free (hrefs->pdata[i]);
        }
+       g_ptr_array_free (hrefs, TRUE);
+
+ DONE:
+       if (!--pending)
+               g_main_quit (loop);
+}
+
+static void
+get_url (const char *url)
+{
+       char *url_to_get, *slash, *name;
+       SoupContext *ctx;
+       SoupMessage *msg;
+       int fd;
+
+       if (strncmp (url, base, strlen (base)) != 0)
+               return;
 
+       slash = strrchr (url, '/');
+       if (slash && !slash[1])
+               url_to_get = g_strdup_printf ("%sindex.html", url);
+       else
+               url_to_get = g_strdup (url);
+
+       if (recurse) {
+               /* See if we're already downloading it, and create the
+                * file if not.
+                */
+
+               name = url_to_get + strlen (base);
+               if (access (name, F_OK) == 0) {
+                       g_free (url_to_get);
+                       return;
+               }
+
+               mkdirs (name);
+               fd = open (name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+               close (fd);
+       }
+
+       ctx = soup_context_get (url_to_get);
        msg = soup_message_new (ctx, SOUP_METHOD_GET);
+       soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
+
+       pending++;
+       soup_message_queue (msg, got_url, soup_uri_new (url));
        soup_context_unref (ctx);
+       g_free (url_to_get);
+}
 
-       soup_message_send (msg);
+static void
+usage (void)
+{
+       fprintf (stderr, "Usage: get [-r] URL\n");
+       exit (1);
+}
+
+int
+main (int argc, char **argv)
+{
+       int opt;
 
-       printf ("%d %s\n", msg->errorcode, msg->errorphrase);
-       if (SOUP_ERROR_IS_SUCCESSFUL (msg->errorcode)) {
-               fwrite (msg->response.body, msg->response.length, 1, stdout);
-               printf ("\n");
+       while ((opt = getopt (argc, argv, "r")) != -1) {
+               switch (opt) {
+               case 'r':
+                       recurse = TRUE;
+                       break;
+
+               case '?':
+                       usage ();
+                       break;
+               }
        }
+       argc -= optind;
+       argv += optind;
+
+       if (argc != 1)
+               usage ();
+       base = argv[0];
+       base_uri = soup_uri_new (base);
+       if (!base_uri) {
+               fprintf (stderr, "Could not parse '%s' as a URL\n", base);
+               exit (1);
+       }
+
+       if (recurse) {
+               char *outdir;
+
+               outdir = g_strdup_printf ("%lu", (unsigned long)getpid ());
+               if (mkdir (outdir, 0755) != 0) {
+                       fprintf (stderr, "Could not make output directory\n");
+                       exit (1);
+               }
+               printf ("Output directory is '%s'\n", outdir);
+               chdir (outdir);
+               g_free (outdir);
+       }
+
+       get_url (base);
+
+       loop = g_main_loop_new (NULL, TRUE);
+       g_main_run (loop);
 
-       soup_message_free (msg);
        return 0;
 }
diff --git a/tests/simple-httpd.c b/tests/simple-httpd.c
new file mode 100644 (file)
index 0000000..3c147b1
--- /dev/null
@@ -0,0 +1,159 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2001-2003, Ximian, Inc.
+ */
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <libsoup/soup-message.h>
+#include <libsoup/soup-server.h>
+
+static void
+print_header (gpointer name, gpointer value, gpointer data)
+{
+       printf ("%s: %s\n", (char *)name, (char *)value);
+}
+
+static void
+server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data)
+{
+       char *path, *path_to_open, *slash;
+       struct stat st;
+       int fd;
+
+       path = soup_uri_to_string (soup_context_get_uri (msg->context), TRUE);
+       printf ("%s %s HTTP/1.%d\n", msg->method, path,
+               soup_message_get_http_version (msg));
+       soup_message_foreach_header (msg->request_headers, print_header, NULL);
+       if (msg->request.length)
+               printf ("%.*s\n", msg->request.length, msg->request.body);
+
+       if (soup_method_get_id (msg->method) != SOUP_METHOD_ID_GET) {
+               soup_message_set_error (msg, SOUP_ERROR_NOT_IMPLEMENTED);
+               goto DONE;
+       }
+
+       if (path) {
+               if (*path != '/') {
+                       soup_message_set_error (msg, SOUP_ERROR_BAD_REQUEST);
+                       goto DONE;
+               }
+       } else
+               path = "";
+
+       path_to_open = g_strdup_printf (".%s", path);
+
+ TRY_AGAIN:
+       if (stat (path_to_open, &st) == -1) {
+               g_free (path_to_open);
+               if (errno == EPERM)
+                       soup_message_set_error (msg, SOUP_ERROR_FORBIDDEN);
+               else if (errno == ENOENT)
+                       soup_message_set_error (msg, SOUP_ERROR_NOT_FOUND);
+               else
+                       soup_message_set_error (msg, SOUP_ERROR_INTERNAL);
+               goto DONE;
+       }
+
+       if (S_ISDIR (st.st_mode)) {
+               slash = strrchr (path_to_open, '/');
+               if (!slash || slash[1]) {
+                       char *uri, *redir_uri;
+
+                       uri = soup_uri_to_string (soup_context_get_uri (msg->context), FALSE);
+                       redir_uri = g_strdup_printf ("%s/", uri);
+                       soup_message_add_header (msg->response_headers,
+                                                "Location", redir_uri);
+                       soup_message_set_error (msg, SOUP_ERROR_MOVED_PERMANENTLY);
+                       g_free (redir_uri);
+                       g_free (uri);
+                       g_free (path_to_open);
+                       goto DONE;
+               }
+
+               g_free (path_to_open);
+               path_to_open = g_strdup_printf (".%s/index.html", path);
+               goto TRY_AGAIN;
+       }
+
+       fd = open (path_to_open, O_RDONLY);
+       g_free (path_to_open);
+       if (fd == -1) {
+               soup_message_set_error (msg, SOUP_ERROR_INTERNAL);
+               goto DONE;
+       }
+
+       msg->response.owner = SOUP_BUFFER_SYSTEM_OWNED;
+       msg->response.length = st.st_size;
+       msg->response.body = g_malloc (msg->response.length);
+
+       read (fd, msg->response.body, msg->response.length);
+       close (fd);
+
+       soup_message_set_error (msg, SOUP_ERROR_OK);
+
+ DONE:
+       printf ("  -> %d %s\n\n", msg->errorcode, msg->errorphrase);
+}
+
+int
+main (int argc, char **argv)
+{
+       GMainLoop *loop;
+       int opt;
+       int port = SOUP_SERVER_ANY_PORT;
+       int ssl_port = SOUP_SERVER_ANY_PORT;
+       SoupServer *server, *ssl_server;
+
+       while ((opt = getopt (argc, argv, "p:s:")) != -1) {
+               switch (opt) {
+               case 'p':
+                       port = atoi (optarg);
+                       break;
+               case 's':
+                       ssl_port = atoi (optarg);
+                       break;
+               default:
+                       fprintf (stderr, "Usage: %s [-p port] [-s ssl-port]\n",
+                                argv[0]);
+                       exit (1);
+               }
+       }
+
+       server = soup_server_new (SOUP_PROTOCOL_HTTP, port);
+       if (!server) {
+               fprintf (stderr, "Unable to bind to server port %d\n", port);
+               exit (1);
+       }
+       soup_server_register (server, NULL, NULL, server_callback, NULL, NULL);
+
+       ssl_server = soup_server_new (SOUP_PROTOCOL_HTTPS, ssl_port);
+       if (!ssl_server) {
+               fprintf (stderr, "Unable to bind to SSL server port %d\n", ssl_port);
+               exit (1);
+       }
+       soup_server_register (ssl_server, NULL, NULL, server_callback, NULL, NULL);
+
+       printf ("\nStarting Server on port %d\n",
+               soup_server_get_port (server));
+       soup_server_run_async (server);
+
+       printf ("Starting SSL Server on port %d\n", 
+               soup_server_get_port (ssl_server));
+       soup_server_run_async (ssl_server);
+
+       printf ("\nWaiting for requests...\n");
+
+       loop = g_main_loop_new (NULL, TRUE);
+       g_main_loop_run (loop);
+
+       return 0;
+}
diff --git a/tests/simple-proxy.c b/tests/simple-proxy.c
new file mode 100644 (file)
index 0000000..821836a
--- /dev/null
@@ -0,0 +1,77 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2001-2003, Ximian, Inc.
+ */
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <libsoup/soup-message.h>
+#include <libsoup/soup-server.h>
+
+/* WARNING: this is really really really not especially compliant with
+ * RFC 2616. But it does work for basic stuff.
+ */
+
+static void
+server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data)
+{
+       char *uristr;
+
+       uristr = soup_uri_to_string (soup_context_get_uri (msg->context), FALSE);
+       printf ("%s %s HTTP/1.%d\n", msg->method, uristr,
+               soup_message_get_http_version (msg));
+
+       if (soup_method_get_id (msg->method) == SOUP_METHOD_ID_CONNECT) {
+               /* FIXME */
+               return;
+       }
+
+       soup_message_send (msg);
+}
+
+int
+main (int argc, char **argv)
+{
+       GMainLoop *loop;
+       int opt;
+       int port = SOUP_SERVER_ANY_PORT;
+       SoupServer *server;
+
+       while ((opt = getopt (argc, argv, "p:s:")) != -1) {
+               switch (opt) {
+               case 'p':
+                       port = atoi (optarg);
+                       break;
+               default:
+                       fprintf (stderr, "Usage: %s [-p port] [-n]\n",
+                                argv[0]);
+                       exit (1);
+               }
+       }
+
+       server = soup_server_new (SOUP_PROTOCOL_HTTP, port);
+       if (!server) {
+               fprintf (stderr, "Unable to bind to server port %d\n", port);
+               exit (1);
+       }
+       soup_server_register (server, NULL, NULL, server_callback, NULL, NULL);
+
+       printf ("\nStarting proxy on port %d\n",
+               soup_server_get_port (server));
+       soup_server_run_async (server);
+
+       printf ("\nWaiting for requests...\n");
+
+       loop = g_main_loop_new (NULL, TRUE);
+       g_main_loop_run (loop);
+
+       return 0;
+}
index 387db6e..53b49f6 100644 (file)
@@ -28,7 +28,7 @@ main (int argc, char **argv)
                argc--;
                argv++;
        } else
-               addr = soup_address_ipv6_any ();
+               addr = soup_address_ipv4_any ();
 
        if (argc > 2) {
                fprintf (stderr, "Usage: %s [-6] [port]\n", argv[0]);
@@ -38,7 +38,7 @@ main (int argc, char **argv)
        if (argc == 2)
                port = atoi (argv[1]);
        else
-               port = 0;
+               port = SOUP_SERVER_ANY_PORT;
        listener = soup_socket_server_new (addr, port);
        if (!listener) {
                fprintf (stderr, "Could not create listening socket\n");
diff --git a/tests/uri-parsing.c b/tests/uri-parsing.c
new file mode 100644 (file)
index 0000000..2861dd5
--- /dev/null
@@ -0,0 +1,147 @@
+#include <config.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "libsoup/soup-uri.h"
+
+struct {
+       char *uri_string, *result;
+} abs_tests[] = {
+       { "foo:", "foo:" },
+       { "file:/dev/null", "file:/dev/null" },
+       { "file:///dev/null", "file:///dev/null" },
+       { "ftp://user@host/path", "ftp://user@host/path" },
+       { "ftp://user@host:9999/path", "ftp://user@host:9999/path" },
+       { "ftp://user:password@host/path", "ftp://user@host/path" },
+       { "ftp://user:password@host:9999/path", "ftp://user@host:9999/path" },
+       { "http://us%65r@host", "http://user@host" },
+       { "http://us%40r@host", "http://us%40r@host" },
+       { "http://us%3ar@host", "http://us%3ar@host" },
+       { "http://us%2fr@host", "http://us%2fr@host" }
+};
+int num_abs_tests = G_N_ELEMENTS(abs_tests);
+
+/* From RFC 2396. */
+char *base = "http://a/b/c/d;p?q";
+struct {
+       char *uri_string, *result;
+} rel_tests[] = {
+       { "g:h", "g:h" },
+       { "g", "http://a/b/c/g" },
+       { "./g", "http://a/b/c/g" },
+       { "g/", "http://a/b/c/g/" },
+       { "/g", "http://a/g" },
+       { "//g", "http://g" },
+       { "?y", "http://a/b/c/?y" },
+       { "g?y", "http://a/b/c/g?y" },
+       { "#s", "http://a/b/c/d;p?q#s" },
+       { "g#s", "http://a/b/c/g#s" },
+       { "g?y#s", "http://a/b/c/g?y#s" },
+       { ";x", "http://a/b/c/;x" },
+       { "g;x", "http://a/b/c/g;x" },
+       { "g;x?y#s", "http://a/b/c/g;x?y#s" },
+       { ".", "http://a/b/c/" },
+       { "./", "http://a/b/c/" },
+       { "..", "http://a/b/" },
+       { "../", "http://a/b/" },
+       { "../g", "http://a/b/g" },
+       { "../..", "http://a/" },
+       { "../../", "http://a/" },
+       { "../../g", "http://a/g" },
+       { "", "http://a/b/c/d;p?q" },
+       { "../../../g", "http://a/../g" },
+       { "../../../../g", "http://a/../../g" },
+       { "/./g", "http://a/./g" },
+       { "/../g", "http://a/../g" },
+       { "g.", "http://a/b/c/g." },
+       { ".g", "http://a/b/c/.g" },
+       { "g..", "http://a/b/c/g.." },
+       { "..g", "http://a/b/c/..g" },
+       { "./../g", "http://a/b/g" },
+       { "./g/.", "http://a/b/c/g/" },
+       { "g/./h", "http://a/b/c/g/h" },
+       { "g/../h", "http://a/b/c/h" },
+       { "g;x=1/./y", "http://a/b/c/g;x=1/y" },
+       { "g;x=1/../y", "http://a/b/c/y" },
+       { "g?y/./x", "http://a/b/c/g?y/./x" },
+       { "g?y/../x", "http://a/b/c/g?y/../x" },
+       { "g#s/./x", "http://a/b/c/g#s/./x" },
+       { "g#s/../x", "http://a/b/c/g#s/../x" },
+       { "http:g", "http:g" }
+};
+int num_rel_tests = G_N_ELEMENTS(rel_tests);
+
+static gboolean
+do_uri (SoupUri *base_uri, const char *base_str,
+       const char *in_uri, const char *out_uri)
+{
+       SoupUri *uri;
+       char *uri_string;
+
+       if (base_uri) {
+               printf ("<%s> + <%s> = <%s>? ", base_str, in_uri, out_uri);
+               uri = soup_uri_new_with_base (base_uri, in_uri);
+       } else {
+               printf ("<%s> => <%s>? ", in_uri, out_uri);
+               uri = soup_uri_new (in_uri);
+       }
+
+       if (!uri) {
+               printf ("ERR\n  Could not parse %s\n", in_uri);
+               return FALSE;
+       }
+
+       uri_string = soup_uri_to_string (uri, FALSE);
+       soup_uri_free (uri);
+
+       if (strcmp (uri_string, out_uri) != 0) {
+               printf ("NO\n  Unparses to <%s>\n", uri_string);
+               g_free (uri_string);
+               return FALSE;
+       }
+       g_free (uri_string);
+
+       printf ("OK\n");
+       return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+       SoupUri *base_uri, *uri;
+       char *uri_string;
+       int i, errs = 0;
+
+       printf ("Absolute URI parsing\n");
+       for (i = 0; i < num_abs_tests; i++) {
+               if (!do_uri (NULL, NULL, abs_tests[i].uri_string,
+                            abs_tests[i].result))
+                       errs++;
+       }
+
+       printf ("\nRelative URI parsing\n");
+       base_uri = soup_uri_new (base);
+       if (!base_uri) {
+               fprintf (stderr, "Could not parse %s!\n", base);
+               exit (1);
+       }
+
+       uri_string = soup_uri_to_string (base_uri, FALSE);
+       if (strcmp (uri_string, base) != 0) {
+               fprintf (stderr, "URI <%s> unparses to <%s>\n",
+                        base, uri_string);
+               errs++;
+       }
+       g_free (uri_string);
+
+       for (i = 0; i < num_rel_tests; i++) {
+               if (!do_uri (base_uri, base, rel_tests[i].uri_string,
+                            rel_tests[i].result))
+                       errs++;
+       }
+
+       printf ("\n%d errors\n", errs);
+       return errs;
+}