SoupSession: add some API for handling redirections
authorDan Winship <danw@gnome.org>
Thu, 13 Mar 2008 01:39:14 +0000 (21:39 -0400)
committerDan Winship <danw@gnome.org>
Sat, 5 Nov 2011 13:10:07 +0000 (09:10 -0400)
docs/reference/libsoup-2.4-sections.txt
libsoup/soup-message.c
libsoup/soup-message.h
libsoup/soup-session.c
libsoup/soup-session.h
tests/chunk-test.c
tests/forms-test.c
tests/misc-test.c
tests/redirect-test.c
tests/simple-httpd.c

index 7d462c8..c647eb0 100644 (file)
@@ -18,6 +18,7 @@ soup_message_get_address
 <SUBSECTION>
 soup_message_set_status
 soup_message_set_status_full
+soup_message_set_redirect
 soup_message_is_keepalive
 soup_message_get_https_status
 <SUBSECTION>
@@ -391,6 +392,9 @@ soup_session_cancel_message
 soup_session_prepare_for_uri
 soup_session_abort
 <SUBSECTION>
+soup_session_would_redirect
+soup_session_redirect_message
+<SUBSECTION>
 soup_session_pause_message
 soup_session_unpause_message
 <SUBSECTION>
index 82f39de..dea3748 100644 (file)
@@ -1934,3 +1934,38 @@ soup_message_get_https_status (SoupMessage           *msg,
                *errors = priv->tls_errors;
        return priv->tls_certificate != NULL;
 }
+
+/**
+ * soup_message_set_redirect:
+ * @msg: a #SoupMessage
+ * @status_code: a 3xx status code
+ * @redirect_uri: the URI to redirect @msg to
+ *
+ * Sets @msg's status_code to @status_code and adds a Location header
+ * pointing to @redirect_uri. Use this from a #SoupServer when you
+ * want to redirect the client to another URI.
+ *
+ * @redirect_uri can be a relative URI, in which case it is
+ * interpreted relative to @msg's current URI. In particular, if
+ * @redirect_uri is just a path, it will replace the path
+ * <emphasis>and query</emphasis> of @msg's URI.
+ *
+ * Since: 2.38
+ */
+void
+soup_message_set_redirect (SoupMessage *msg, guint status_code,
+                          const char *redirect_uri)
+{
+       SoupURI *location;
+       char *location_str;
+
+       location = soup_uri_new_with_base (soup_message_get_uri (msg), redirect_uri);
+       g_return_if_fail (location != NULL);
+
+       soup_message_set_status (msg, status_code);
+       location_str = soup_uri_to_string (location, FALSE);
+       soup_message_headers_replace (msg->response_headers, "Location",
+                                     location_str);
+       g_free (location_str);
+       soup_uri_free (location);
+}
index 8505f00..f1a8c6d 100644 (file)
@@ -155,6 +155,10 @@ void           soup_message_set_status_full     (SoupMessage       *msg,
                                                 guint              status_code, 
                                                 const char        *reason_phrase);
 
+void           soup_message_set_redirect        (SoupMessage       *msg,
+                                                guint              status_code,
+                                                const char        *redirect_uri);
+
 /* I/O */
 typedef SoupBuffer * (*SoupChunkAllocator)      (SoupMessage       *msg,
                                                 gsize              max_len,
index 0015dba..7f5ea0e 100644 (file)
@@ -1392,32 +1392,126 @@ auth_manager_authenticate (SoupAuthManager *manager, SoupMessage *msg,
                                     method == SOUP_METHOD_OPTIONS || \
                                     method == SOUP_METHOD_PROPFIND)
 
-static void
-redirect_handler (SoupMessage *msg, gpointer user_data)
+#define SOUP_SESSION_WOULD_REDIRECT_AS_GET(session, msg) \
+       ((msg)->status_code == SOUP_STATUS_SEE_OTHER || \
+        ((msg)->status_code == SOUP_STATUS_FOUND && \
+         !SOUP_METHOD_IS_SAFE ((msg)->method)) || \
+        ((msg)->status_code == SOUP_STATUS_MOVED_PERMANENTLY && \
+         (msg)->method == SOUP_METHOD_POST))
+
+#define SOUP_SESSION_WOULD_REDIRECT_AS_SAFE(session, msg) \
+       (((msg)->status_code == SOUP_STATUS_MOVED_PERMANENTLY || \
+         (msg)->status_code == SOUP_STATUS_TEMPORARY_REDIRECT || \
+         (msg)->status_code == SOUP_STATUS_FOUND) && \
+        SOUP_METHOD_IS_SAFE ((msg)->method))
+
+static inline SoupURI *
+redirection_uri (SoupMessage *msg)
 {
-       SoupMessageQueueItem *item = user_data;
-       SoupSession *session = item->session;
-       SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
        const char *new_loc;
        SoupURI *new_uri;
 
        new_loc = soup_message_headers_get_one (msg->response_headers,
                                                "Location");
-       g_return_if_fail (new_loc != NULL);
+       if (!new_loc)
+               return NULL;
+       new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
+       if (!new_uri || !new_uri->host) {
+               if (new_uri)
+                       soup_uri_free (new_uri);
+               return NULL;
+       }
+
+       return new_uri;
+}
+
+/**
+ * soup_session_would_redirect:
+ * @session: a #SoupSession
+ * @msg: a #SoupMessage that has response headers
+ *
+ * Checks if @msg contains a response that would cause @session to
+ * redirect it to a new URL (ignoring @msg's %SOUP_MESSAGE_NO_REDIRECT
+ * flag, and the number of times it has already been redirected).
+ *
+ * Return value: whether @msg would be redirected
+ *
+ * Since: 2.38
+ */
+gboolean
+soup_session_would_redirect (SoupSession *session, SoupMessage *msg)
+{
+       SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+       SoupURI *new_uri;
+
+       /* It must have an appropriate status code and method */
+       if (!SOUP_SESSION_WOULD_REDIRECT_AS_GET (session, msg) &&
+           !SOUP_SESSION_WOULD_REDIRECT_AS_SAFE (session, msg))
+               return FALSE;
+
+       /* and a Location header that parses to an http URI */
+       if (!soup_message_headers_get_one (msg->response_headers, "Location"))
+               return FALSE;
+       new_uri = redirection_uri (msg);
+       if (!new_uri)
+               return FALSE;
+       if (!new_uri->host || !*new_uri->host ||
+           (!uri_is_http (priv, new_uri) && !uri_is_https (priv, new_uri))) {
+               soup_uri_free (new_uri);
+               return FALSE;
+       }
+
+       soup_uri_free (new_uri);
+       return TRUE;
+}
+
+/**
+ * soup_session_redirect_message:
+ * @session: the session
+ * @msg: a #SoupMessage that has received a 3xx response
+ *
+ * Updates @msg's URI according to its status code and "Location"
+ * header, and requeues it on @session. Use this when you have set
+ * %SOUP_MESSAGE_NO_REDIRECT on a message, but have decided to allow a
+ * particular redirection to occur, or if you want to allow a
+ * redirection that #SoupSession will not perform automatically (eg,
+ * redirecting a non-safe method such as DELETE).
+ *
+ * If @msg's status code indicates that it should be retried as a GET
+ * request, then @msg will be modified accordingly.
+ *
+ * If @msg has already been redirected too many times, this will
+ * cause it to fail with %SOUP_STATUS_TOO_MANY_REDIRECTS.
+ *
+ * Return value: %TRUE if a redirection was applied, %FALSE if not
+ * (eg, because there was no Location header, or it could not be
+ * parsed).
+ *
+ * Since: 2.38
+ */
+gboolean
+soup_session_redirect_message (SoupSession *session, SoupMessage *msg)
+{
+       SoupMessageQueueItem *item;
+       SoupURI *new_uri;
+
+       new_uri = redirection_uri (msg);
+       if (!new_uri)
+               return FALSE;
 
+       item = soup_message_queue_lookup (soup_session_get_queue (session), msg);
+       if (!item)
+               return FALSE;
        if (item->redirection_count >= SOUP_SESSION_MAX_REDIRECTION_COUNT) {
                soup_session_cancel_message (session, msg, SOUP_STATUS_TOO_MANY_REDIRECTS);
-               return;
+               soup_message_queue_item_unref (item);
+               return FALSE;
        }
        item->redirection_count++;
+       soup_message_queue_item_unref (item);
 
-       if (msg->status_code == SOUP_STATUS_SEE_OTHER ||
-           (msg->status_code == SOUP_STATUS_FOUND &&
-            !SOUP_METHOD_IS_SAFE (msg->method)) ||
-           (msg->status_code == SOUP_STATUS_MOVED_PERMANENTLY &&
-            msg->method == SOUP_METHOD_POST)) {
+       if (SOUP_SESSION_WOULD_REDIRECT_AS_GET (session, msg)) {
                if (msg->method != SOUP_METHOD_HEAD) {
-                       /* Redirect using a GET */
                        g_object_set (msg,
                                      SOUP_MESSAGE_METHOD, SOUP_METHOD_GET,
                                      NULL);
@@ -1426,66 +1520,39 @@ redirect_handler (SoupMessage *msg, gpointer user_data)
                                          SOUP_MEMORY_STATIC, NULL, 0);
                soup_message_headers_set_encoding (msg->request_headers,
                                                   SOUP_ENCODING_NONE);
-       } else if (msg->status_code == SOUP_STATUS_MOVED_PERMANENTLY ||
-                  msg->status_code == SOUP_STATUS_TEMPORARY_REDIRECT ||
-                  msg->status_code == SOUP_STATUS_FOUND) {
-               /* Don't redirect non-safe methods */
-               if (!SOUP_METHOD_IS_SAFE (msg->method))
-                       return;
-       } else {
-               /* Three possibilities:
-                *
-                *   1) This was a non-3xx response that happened to
-                *      have a "Location" header
-                *   2) It's a non-redirecty 3xx response (300, 304,
-                *      305, 306)
-                *   3) It's some newly-defined 3xx response (308+)
-                *
-                * We ignore all of these cases. In the first two,
-                * redirecting would be explicitly wrong, and in the
-                * last case, we have no clue if the 3xx response is
-                * supposed to be redirecty or non-redirecty. Plus,
-                * 2616 says unrecognized status codes should be
-                * treated as the equivalent to the x00 code, and we
-                * don't redirect on 300, so therefore we shouldn't
-                * redirect on 308+ either.
-                */
-               return;
        }
 
-       /* Location is supposed to be an absolute URI, but some sites
-        * are lame, so we use soup_uri_new_with_base().
-        */
-       new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
-       if (!new_uri || !new_uri->host) {
-               if (new_uri)
-                       soup_uri_free (new_uri);
-               soup_message_set_status_full (msg,
-                                             SOUP_STATUS_MALFORMED,
-                                             "Invalid Redirect URL");
-               return;
-       }
+       soup_message_set_uri (msg, new_uri);
+       soup_uri_free (new_uri);
 
-       /* If the URI is not "http" or "https" or a recognized alias,
-        * then we let the redirect response be returned to the caller.
-        */
-       if (!uri_is_http (priv, new_uri) && !uri_is_https (priv, new_uri)) {
-               soup_uri_free (new_uri);
-               return;
-       }
+       soup_session_requeue_message (session, msg);
+       return TRUE;
+}
 
-       if (!new_uri->host) {
-               soup_uri_free (new_uri);
-               soup_message_set_status_full (msg,
-                                             SOUP_STATUS_MALFORMED,
-                                             "Invalid Redirect URL");
+static void
+redirect_handler (SoupMessage *msg, gpointer user_data)
+{
+       SoupMessageQueueItem *item = user_data;
+       SoupSession *session = item->session;
+
+       if (!soup_session_would_redirect (session, msg)) {
+               SoupURI *new_uri = redirection_uri (msg);
+               gboolean invalid = !new_uri || !new_uri->host;
+
+               if (new_uri)
+                       soup_uri_free (new_uri);
+               if (invalid) {
+                       /* Really we should just leave the status as-is,
+                        * but that would be an API break.
+                        */
+                       soup_message_set_status_full (msg,
+                                                     SOUP_STATUS_MALFORMED,
+                                                     "Invalid Redirect URL");
+               }
                return;
        }
 
-       soup_message_set_uri (msg, new_uri);
-       soup_uri_free (new_uri);
-
-       soup_session_requeue_message (session, msg);
+       soup_session_redirect_message (session, msg);
 }
 
 void
index 6afbbd1..349cfdb 100644 (file)
@@ -104,6 +104,11 @@ void            soup_session_abort            (SoupSession           *session);
 void            soup_session_prepare_for_uri  (SoupSession           *session,
                                               SoupURI               *uri);
 
+gboolean        soup_session_would_redirect   (SoupSession           *session,
+                                              SoupMessage           *msg);
+gboolean        soup_session_redirect_message (SoupSession           *session,
+                                              SoupMessage           *msg);
+
 void                soup_session_add_feature            (SoupSession        *session,
                                                         SoupSessionFeature *feature);
 void                soup_session_add_feature_by_type    (SoupSession        *session,
index 435f7fa..c6b4a42 100644 (file)
@@ -434,9 +434,7 @@ server_callback (SoupServer *server, SoupMessage *msg,
        char *md5;
 
        if (g_str_has_prefix (path, "/redirect")) {
-               soup_message_set_status (msg, SOUP_STATUS_FOUND);
-               soup_message_headers_replace (msg->response_headers,
-                                             "Location", "/");
+               soup_message_set_redirect (msg, SOUP_STATUS_FOUND, "/");
                return;
        }
 
index 4c2846e..fbbd97c 100644 (file)
@@ -395,9 +395,7 @@ md5_post_callback (SoupServer *server, SoupMessage *msg,
                                        NULL);
        redirect_uri = soup_uri_to_string (uri, FALSE);
 
-       soup_message_set_status (msg, SOUP_STATUS_SEE_OTHER);
-       soup_message_headers_replace (msg->response_headers, "Location",
-                                     redirect_uri);
+       soup_message_set_redirect (msg, SOUP_STATUS_SEE_OTHER, redirect_uri);
 
        g_free (redirect_uri);
        soup_uri_free (uri);
index f5ecb57..907e338 100644 (file)
@@ -141,13 +141,7 @@ server_callback (SoupServer *server, SoupMessage *msg,
        }
 
        if (!strcmp (path, "/redirect")) {
-               soup_message_set_status (msg, SOUP_STATUS_FOUND);
-               soup_message_headers_append (msg->response_headers,
-                                            /* Kids: don't try this at home!
-                                             * RFC2616 says to use an
-                                             * absolute URI!
-                                             */
-                                            "Location", "/");
+               soup_message_set_redirect (msg, SOUP_STATUS_FOUND, "/");
                return;
        }
 
index c953f03..aa1fb54 100644 (file)
@@ -430,14 +430,8 @@ server_callback (SoupServer *server, SoupMessage *msg,
        if (*remainder == '/')
                soup_message_set_http_version (msg, SOUP_HTTP_1_0);
 
-       soup_message_set_status (msg, status_code);
-       if (*remainder) {
-               soup_message_headers_replace (msg->response_headers,
-                                             "Location", remainder);
-       } else {
-               soup_message_headers_replace (msg->response_headers,
-                                             "Location", "/");
-       }
+       soup_message_set_redirect (msg, status_code,
+                                  *remainder ? remainder : "/");
 }
 
 static void
index c776d36..535de4b 100644 (file)
@@ -112,15 +112,12 @@ do_get (SoupServer *server, SoupMessage *msg, const char *path)
 
                slash = strrchr (path, '/');
                if (!slash || slash[1]) {
-                       char *uri, *redir_uri;
+                       char *redir_uri;
 
-                       uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
-                       redir_uri = g_strdup_printf ("%s/", uri);
-                       soup_message_headers_append (msg->response_headers,
-                                                    "Location", redir_uri);
-                       soup_message_set_status (msg, SOUP_STATUS_MOVED_PERMANENTLY);
+                       redir_uri = g_strdup_printf ("%s/", soup_message_get_uri (msg)->path);
+                       soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY,
+                                                  redir_uri);
                        g_free (redir_uri);
-                       g_free (uri);
                        return;
                }