]> granicus.if.org Git - curl/commitdiff
- To make it easier for applications that want lots of magic stuff done on
authorDaniel Stenberg <daniel@haxx.se>
Wed, 30 Apr 2008 21:20:08 +0000 (21:20 +0000)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 30 Apr 2008 21:20:08 +0000 (21:20 +0000)
  redirections and thus cannot use CURLOPT_FOLLOWLOCATION easily, we now
  introduce the new CURLINFO_REDIRECT_URL option that lets applications
  extract the URL libcurl would've redirected to if it had been told to. This
  then enables the application to continue to that URL as it thinks is
  suitable, without having to re-implement the magic of creating the new URL
  from the Location: header etc. Test 1029 verifies it.

14 files changed:
CHANGES
RELEASE-NOTES
docs/curl.1
docs/libcurl/curl_easy_getinfo.3
include/curl/curl.h
lib/getinfo.c
lib/multi.c
lib/transfer.c
lib/transfer.h
lib/url.c
lib/urldata.h
src/writeout.c
tests/data/Makefile.am
tests/data/test1029 [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index ac6bdcdf4b76215b8dc5cf4195adcfa6fdb3a843..fc3f3f0e9aea2d3db6e1ee504847c01a98ce530f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -7,6 +7,15 @@
                                   Changelog
 
 
+Daniel Stenberg (29 Apr 2008)
+- To make it easier for applications that want lots of magic stuff done on
+  redirections and thus cannot use CURLOPT_FOLLOWLOCATION easily, we now
+  introduce the new CURLINFO_REDIRECT_URL option that lets applications
+  extract the URL libcurl would've redirected to if it had been told to. This
+  then enables the application to continue to that URL as it thinks is
+  suitable, without having to re-implement the magic of creating the new URL
+  from the Location: header etc. Test 1029 verifies it.
+
 Yang Tse (29 Apr 2008)
 - Improved easy interface resolving timeout handling in c-ares enabled builds
 
index 13bd9e716889879d5e5e1d5685f3bf9787a6cf0a..3a4fffeba59ee54eef4d3cdc4194ab3277bbc33d 100644 (file)
@@ -13,6 +13,7 @@ This release includes the following changes:
  o CURLFORM_STREAM was added
  o CURLOPT_NOBODY is now supported over SFTP
  o curl can now run on Symbian OS
+ o curl -w redirect_url and CURLINFO_REDIRECT_URL
 
 This release includes the following bugfixes:
 
index f5e49dc05db830ccff0c93f933c98faf8466898d..98bfc198c92903ff3e0d519b2fd2191bee2bbcfa 100644 (file)
@@ -1288,7 +1288,9 @@ The URL that was fetched last. This is mostly meaningful if you've told curl
 to follow location: headers.
 .TP
 .B http_code
-The numerical code that was found in the last retrieved HTTP(S) page.
+The numerical response code that was found in the last retrieved HTTP(S) or
+FTP(s) transfer. In 7.18.2 the alias \fBresponse_code\fP was added to show the
+same info.
 .TP
 .B http_connect
 The numerical code that was found in the last response (from a proxy) to a
@@ -1349,6 +1351,10 @@ Number of new connects made in the recent transfer. (Added in 7.12.3)
 .B num_redirects
 Number of redirects that were followed in the request. (Added in 7.12.3)
 .TP
+.B redirect_url
+When a HTTP request was made without -L to follow redirects, this variable
+will show the actual URL a redirect \fIwould\fP take you to. (Added in 7.18.2)
+.TP
 .B ftp_entry_path
 The initial path libcurl ended up in when logging on to the remote FTP
 server. (Added in 7.15.4)
index 95455e3a1607a8b3f9b1becd80adb32952db4d0b..be0f060d47f0b996f676f7cdffebf25cb178ef3b 100644 (file)
@@ -5,7 +5,7 @@
 .\" *                            | (__| |_| |  _ <| |___
 .\" *                             \___|\___/|_| \_\_____|
 .\" *
-.\" * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
 .\" *
 .\" * This software is licensed as described in the file COPYING, which
 .\" * you should have received as part of this distribution. The terms
@@ -89,6 +89,12 @@ complete execution time for multiple redirections.  (Added in 7.9.7)
 .IP CURLINFO_REDIRECT_COUNT
 Pass a pointer to a long to receive the total number of redirections that were
 actually followed.  (Added in 7.9.7)
+.IP CURLINFO_REDIRECT_URL
+Pass a pointer to a char pointer to receive the URL a redirect \fIwould\fP
+take you to if you would enable CURLOPT_FOLLOWLOCATION. This can come very
+handy if you think using the built-in libcurl redirect logic isn't good enough
+for you but you would still prefer to avoid implementing all the magic of
+figuring out the new URL. (Added in 7.18.2)
 .IP CURLINFO_SIZE_UPLOAD
 Pass a pointer to a double to receive the total amount of bytes that were
 uploaded.
index 8b72681884c0cda2e7c40a5d700ae7428eb57ceb..6fcc34faf4be0408cf1bc1c104109e7447c6bbc8 100644 (file)
@@ -1587,9 +1587,10 @@ typedef enum {
   CURLINFO_COOKIELIST       = CURLINFO_SLIST  + 28,
   CURLINFO_LASTSOCKET       = CURLINFO_LONG   + 29,
   CURLINFO_FTP_ENTRY_PATH   = CURLINFO_STRING + 30,
+  CURLINFO_REDIRECT_URL     = CURLINFO_STRING + 31,
   /* Fill in new entries below here! */
 
-  CURLINFO_LASTONE          = 30
+  CURLINFO_LASTONE          = 31
 } CURLINFO;
 
 /* CURLINFO_RESPONSE_CODE is the new name for the option previously known as
index b3ef2f649874bb8f05e74b74be5e06ca76550d1c..145a71b2260f219a81ae8c5a9f2f126df0787a6a 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -231,6 +231,11 @@ CURLcode Curl_getinfo(struct SessionHandle *data, CURLINFO info, ...)
     else
       *param_longp = -1;
     break;
+  case CURLINFO_REDIRECT_URL:
+    /* Return the URL this request would have been redirected to if that
+       option had been enabled! */
+    *param_charp = data->info.wouldredirect;
+    break;
   default:
     return CURLE_BAD_FUNCTION_ARGUMENT;
   }
index 73e8e7e3dc200333d1481242d893ff999365b5c5..df287129bf5cb424c536ae6b04c448f4409967a9 100644 (file)
@@ -1262,6 +1262,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
       else if(TRUE == done) {
         char *newurl;
         bool retry = Curl_retry_request(easy->easy_conn, &newurl);
+        followtype follow=FOLLOW_NONE;
 
         /* call this even if the readwrite function returned error */
         Curl_posttransfer(easy->easy_handle);
@@ -1278,10 +1279,13 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
                then figure out the URL here */
             newurl = easy->easy_handle->req.newurl;
             easy->easy_handle->req.newurl = NULL;
+            follow = FOLLOW_REDIR;
           }
+          else
+            follow = FOLLOW_RETRY;
           easy->result = Curl_done(&easy->easy_conn, CURLE_OK, FALSE);
           if(easy->result == CURLE_OK)
-            easy->result = Curl_follow(easy->easy_handle, newurl, retry);
+            easy->result = Curl_follow(easy->easy_handle, newurl, follow);
           if(CURLE_OK == easy->result) {
             multistate(easy, CURLM_STATE_CONNECT);
             result = CURLM_CALL_MULTI_PERFORM;
index 0856d2a551b6b70a052befedc6576b495c7a4051..0ebe19652355cf03bec2f63f6da35bada1206fce 100644 (file)
@@ -250,7 +250,7 @@ CURLcode Curl_readrewind(struct connectdata *conn)
 
       err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET);
       if(err) {
-       failf(data, "seek callback returned error %d", (int)err);
+        failf(data, "seek callback returned error %d", (int)err);
         return CURLE_SEND_FAIL_REWIND;
       }
     }
@@ -1113,34 +1113,37 @@ CURLcode Curl_readwrite(struct connectdata *conn,
             }
             else if((k->httpcode >= 300 && k->httpcode < 400) &&
                      checkprefix("Location:", k->p)) {
-              if(data->set.http_follow_location) {
-                /* this is the URL that the server advices us to get instead */
-                char *ptr;
-                char *start=k->p;
-                char backup;
-
-                start += 9; /* pass "Location:" */
-
-                /* Skip spaces and tabs. We do this to support multiple
-                   white spaces after the "Location:" keyword. */
-                while(*start && ISSPACE(*start ))
-                  start++;
-
-                /* Scan through the string from the end to find the last
-                   non-space. k->end_ptr points to the actual terminating zero
-                   letter, move pointer one letter back and start from
-                   there. This logic strips off trailing whitespace, but keeps
-                   any embedded whitespace. */
-                ptr = k->end_ptr-1;
-                while((ptr>=start) && ISSPACE(*ptr))
-                  ptr--;
-                ptr++;
-
-                backup = *ptr; /* store the ending letter */
-                if(ptr != start) {
-                  *ptr = '\0';   /* zero terminate */
-                  data->req.newurl = strdup(start); /* clone string */
-                  *ptr = backup; /* restore ending letter */
+              /* this is the URL that the server advices us to use instead */
+              char *ptr;
+              char *start=k->p;
+              char backup;
+
+              start += 9; /* pass "Location:" */
+
+              /* Skip spaces and tabs. We do this to support multiple
+                 white spaces after the "Location:" keyword. */
+              while(*start && ISSPACE(*start ))
+                start++;
+
+              /* Scan through the string from the end to find the last
+                 non-space. k->end_ptr points to the actual terminating zero
+                 letter, move pointer one letter back and start from
+                 there. This logic strips off trailing whitespace, but keeps
+                 any embedded whitespace. */
+              ptr = k->end_ptr-1;
+              while((ptr>=start) && ISSPACE(*ptr))
+                ptr--;
+              ptr++;
+
+              backup = *ptr; /* store the ending letter */
+              if(ptr != start) {
+                *ptr = '\0';   /* zero terminate */
+                data->req.location = strdup(start); /* clone string */
+                *ptr = backup; /* restore ending letter */
+                if(!data->req.location)
+                  return CURLE_OUT_OF_MEMORY;
+                if(data->set.http_follow_location) {
+                  data->req.newurl = strdup(data->req.location); /* clone */
                   if(!data->req.newurl)
                     return CURLE_OUT_OF_MEMORY;
                 }
@@ -1969,16 +1972,16 @@ CURLcode Curl_follow(struct SessionHandle *data,
                      char *newurl, /* this 'newurl' is the Location: string,
                                       and it must be malloc()ed before passed
                                       here */
-                     bool retry) /* set TRUE if this is a request retry as
-                                    opposed to a real redirect following */
+                     followtype type) /* see transfer.h */
 {
   /* Location: redirect */
   char prot[16]; /* URL protocol string storage */
   char letter;   /* used for a silly sscanf */
   size_t newlen;
   char *newest;
+  bool disallowport = FALSE;
 
-  if(!retry) {
+  if(type == FOLLOW_REDIR) {
     if((data->set.maxredirs != -1) &&
         (data->set.followlocation >= data->set.maxredirs)) {
       failf(data,"Maximum (%d) redirects followed", data->set.maxredirs);
@@ -1989,19 +1992,19 @@ CURLcode Curl_follow(struct SessionHandle *data,
     data->state.this_is_a_follow = TRUE;
 
     data->set.followlocation++; /* count location-followers */
-  }
 
-  if(data->set.http_auto_referer) {
-    /* We are asked to automatically set the previous URL as the
-       referer when we get the next URL. We pick the ->url field,
-       which may or may not be 100% correct */
+    if(data->set.http_auto_referer) {
+      /* We are asked to automatically set the previous URL as the referer
+         when we get the next URL. We pick the ->url field, which may or may
+         not be 100% correct */
 
-    if(data->change.referer_alloc)
-      /* If we already have an allocated referer, free this first */
-      free(data->change.referer);
+      if(data->change.referer_alloc)
+        /* If we already have an allocated referer, free this first */
+        free(data->change.referer);
 
-    data->change.referer = strdup(data->change.url);
-    data->change.referer_alloc = TRUE; /* yes, free this later */
+      data->change.referer = strdup(data->change.url);
+      data->change.referer_alloc = TRUE; /* yes, free this later */
+    }
   }
 
   if(2 != sscanf(newurl, "%15[^?&/:]://%c", prot, &letter)) {
@@ -2141,7 +2144,7 @@ CURLcode Curl_follow(struct SessionHandle *data,
   }
   else {
     /* This is an absolute URL, don't allow the custom port number */
-    data->state.allow_port = FALSE;
+    disallowport = TRUE;
 
     if(strchr(newurl, ' ')) {
       /* This new URL contains at least one space, this is a mighty stupid
@@ -2159,6 +2162,16 @@ CURLcode Curl_follow(struct SessionHandle *data,
 
   }
 
+  if(type == FOLLOW_FAKE) {
+    /* we're only figuring out the new url if we would've followed locations
+       but now we're done so we can get out! */
+    data->info.wouldredirect = newurl;
+    return CURLE_OK;
+  }
+
+  if(disallowport)
+    data->state.allow_port = FALSE;
+
   if(data->change.url_alloc)
     free(data->change.url);
   else
@@ -2289,7 +2302,9 @@ connect_host(struct SessionHandle *data,
   return res;
 }
 
-/* Returns TRUE and sets '*url' if a request retry is wanted */
+/* Returns TRUE and sets '*url' if a request retry is wanted.
+
+   NOTE: that the *url is malloc()ed. */
 bool Curl_retry_request(struct connectdata *conn,
                         char **url)
 {
@@ -2335,7 +2350,7 @@ CURLcode Curl_perform(struct SessionHandle *data)
   CURLcode res2;
   struct connectdata *conn=NULL;
   char *newurl = NULL; /* possibly a new URL to follow to! */
-  bool retry = FALSE;
+  int follow = FOLLOW_NONE;
 
   data->state.used_interface = Curl_if_easy;
 
@@ -2366,14 +2381,29 @@ CURLcode Curl_perform(struct SessionHandle *data)
       if(res == CURLE_OK) {
         res = Transfer(conn); /* now fetch that URL please */
         if(res == CURLE_OK) {
-          retry = Curl_retry_request(conn, &newurl);
+          bool retry = Curl_retry_request(conn, &newurl);
 
-          if(!retry)
+          if(retry)
+            follow = FOLLOW_RETRY;
+          else {
             /*
              * We must duplicate the new URL here as the connection data may
-             * be free()ed in the Curl_done() function.
+             * be free()ed in the Curl_done() function. We prefer the newurl
+             * one since that's used for redirects or just further requests
+             * for retries or multi-stage HTTP auth methods etc.
              */
-            newurl = data->req.newurl?strdup(data->req.newurl):NULL;
+            if(data->req.newurl) {
+              follow = FOLLOW_REDIR;
+              newurl = strdup(data->req.newurl);
+            }
+            else if(data->req.location) {
+              follow = FOLLOW_FAKE;
+              newurl = strdup(data->req.location);
+            }
+          }
+
+          /* in the above cases where 'newurl' gets assigned, we have a fresh
+           * allocated memory pointed to */
         }
         else {
           /* The transfer phase returned error, we mark the connection to get
@@ -2409,11 +2439,17 @@ CURLcode Curl_perform(struct SessionHandle *data)
        * in 'Curl_done' or other functions.
        */
 
-      if((res == CURLE_OK) && newurl) {
-        res = Curl_follow(data, newurl, retry);
+      if((res == CURLE_OK) && follow) {
+        res = Curl_follow(data, newurl, follow);
         if(CURLE_OK == res) {
+          /* if things went fine, Curl_follow() freed or otherwise took
+             responsibility for the newurl pointer */
           newurl = NULL;
-          continue;
+          if(follow >= FOLLOW_RETRY) {
+            follow = FOLLOW_NONE;
+            continue;
+          }
+          /* else we break out of the loop below */
         }
       }
     }
index c368c4682ec52b74d12aa1fa914b25339d406786..aad82ebafe730d74071c668e7913671b6eea0d3c 100644 (file)
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -26,7 +26,20 @@ CURLcode Curl_perform(struct SessionHandle *data);
 CURLcode Curl_pretransfer(struct SessionHandle *data);
 CURLcode Curl_second_connect(struct connectdata *conn);
 CURLcode Curl_posttransfer(struct SessionHandle *data);
-CURLcode Curl_follow(struct SessionHandle *data, char *newurl, bool retry);
+
+typedef enum {
+  FOLLOW_NONE,  /* not used within the function, just a placeholder to
+                   allow initing to this */
+  FOLLOW_FAKE,  /* only records stuff, not actually following */
+  FOLLOW_RETRY, /* set if this is a request retry as opposed to a real
+                          redirect following */
+  FOLLOW_REDIR, /* a full true redirect */
+  FOLLOW_LAST   /* never used */
+} followtype;
+
+CURLcode Curl_follow(struct SessionHandle *data, char *newurl, followtype type);
+
+
 CURLcode Curl_readwrite(struct connectdata *conn, bool *done);
 int Curl_single_getsock(const struct connectdata *conn,
                         curl_socket_t *socks,
index 9cda6dbac5a404b172533424c9a9fc86351cb198..906792332d3b8403477095e6980f4f9132eebf0b 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -494,6 +494,7 @@ CURLcode Curl_close(struct SessionHandle *data)
   Curl_digest_cleanup(data);
 
   Curl_safefree(data->info.contenttype);
+  Curl_safefree(data->info.wouldredirect);
 
   /* this destroys the channel and we cannot use it anymore after this */
   ares_destroy(data->state.areschannel);
@@ -4440,6 +4441,10 @@ CURLcode Curl_done(struct connectdata **connp,
     free(data->req.newurl);
     data->req.newurl = NULL;
   }
+  if(data->req.location) {
+    free(data->req.location);
+    data->req.location = NULL;
+  }
 
   if(conn->dns_entry) {
     Curl_resolv_unlock(data, conn->dns_entry); /* done with this */
index ce6287190196ea1cf4526c933b52442536c5015b..fa93a6454c92987a5c6b5e07c18624bba9f0945e 100644 (file)
@@ -760,8 +760,10 @@ struct SingleRequest {
   bool ignorecl;    /* This HTTP response has no body so we ignore the Content-
                        Length: header */
 
-  char *newurl; /* This can only be set if a Location: was in the
-                   document headers */
+  char *location;   /* This points to an allocated version of the Location:
+                       header data */
+  char *newurl;     /* Set to the new URL to use when a redirect or a retry is
+                       wanted */
 
   /* 'upload_present' is used to keep a byte counter of how much data there is
      still left in the buffer, aimed for upload. */
@@ -1021,21 +1023,19 @@ struct connectdata {
  */
 struct PureInfo {
   int httpcode;  /* Recent HTTP or FTP response code */
-  int httpproxycode;
-  int httpversion;
+  int httpproxycode; /* response code from proxy when received separate */
+  int httpversion; /* the http version number X.Y = X*10+Y */
   long filetime; /* If requested, this is might get set. Set to -1 if the time
                     was unretrievable. We cannot have this of type time_t,
                     since time_t is unsigned on several platforms such as
                     OpenVMS. */
   long header_size;  /* size of read header(s) in bytes */
   long request_size; /* the amount of bytes sent in the request(s) */
-
-  long proxyauthavail;
-  long httpauthavail;
-
+  long proxyauthavail; /* what proxy auth types were announced */
+  long httpauthavail;  /* what host auth types were announced */
   long numconnects; /* how many new connection did libcurl created */
-
   char *contenttype; /* the content type of the object */
+  char *wouldredirect; /* URL this would've been redirected to if asked to */
 };
 
 
index b45b7cba49c33f96cd3d98bc54a86d6c1b02cd96..2a0e37af2774fab65e220693d2e5de301b334a6a 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2006, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -61,6 +61,7 @@ typedef enum {
   VAR_REDIRECT_TIME,
   VAR_REDIRECT_COUNT,
   VAR_FTP_ENTRY_PATH,
+  VAR_REDIRECT_URL,
   VAR_NUM_OF_VARS /* must be the last */
 } replaceid;
 
@@ -73,6 +74,7 @@ struct variable {
 static const struct variable replacements[]={
   {"url_effective", VAR_EFFECTIVE_URL},
   {"http_code", VAR_HTTP_CODE},
+  {"response_code", VAR_HTTP_CODE},
   {"http_connect", VAR_HTTP_CODE_PROXY},
   {"time_total", VAR_TOTAL_TIME},
   {"time_namelookup", VAR_NAMELOOKUP_TIME},
@@ -90,6 +92,7 @@ static const struct variable replacements[]={
   {"time_redirect", VAR_REDIRECT_TIME},
   {"num_redirects", VAR_REDIRECT_COUNT},
   {"ftp_entry_path", VAR_FTP_ENTRY_PATH},
+  {"redirect_url", VAR_REDIRECT_URL},
   {NULL, VAR_NONE}
 };
 
@@ -222,6 +225,12 @@ void ourWriteOut(CURL *curl, const char *writeinfo)
                    && stringp)
                   fputs(stringp, stream);
                 break;
+              case VAR_REDIRECT_URL:
+                if((CURLE_OK ==
+                    curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &stringp))
+                   && stringp)
+                  fputs(stringp, stream);
+                break;
               default:
                 break;
               }
index fbb553a939f0bab52022947b15f2848067076cc1..7459c3e9a12472d58d8b02c71bdf0acd9e9750b3 100644 (file)
@@ -50,11 +50,12 @@ EXTRA_DIST = test1 test108 test117 test127 test20 test27 test34 test46         \
  test551 test552 test1016 test1017 test1018 test1019 test1020 test553      \
  test1021 test1022 test1023 test309 test616 test617 test618 test619        \
  test620 test621 test622 test623 test624 test625 test626 test627 test554   \
- test1024 test1025 test555 test1026 test1027 test1028
+ test1024 test1025 test555 test1026 test1027 test1028 test1029
 
 filecheck:
        @mkdir test-place; \
        cp "$(top_srcdir)"/tests/data/test[0-9]* test-place/; \
+       rm test-place/*~; \
        for f in $(EXTRA_DIST); do \
          if test -f "$(top_srcdir)/tests/data/$$f"; then \
            rm -f test-place/$$f; \
diff --git a/tests/data/test1029 b/tests/data/test1029
new file mode 100644 (file)
index 0000000..c91feaf
--- /dev/null
@@ -0,0 +1,56 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+redirect_url
+followlocation
+</keywords>
+</info>
+# Server-side
+<reply>
+<data nocheck="1">
+HTTP/1.1 301 This is a weirdo text message swsclose
+Location: data/10290002.txt?coolsite=yes
+Content-Length: 62
+Connection: close
+
+This server reply is for testing a simple Location: following
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+ <name>
+HTTP Location: and 'redirect_url' check
+ </name>
+ <command>
+http://%HOSTIP:%HTTPPORT/we/want/our/1029 -w '%{redirect_url}\n'
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol>
+GET /we/want/our/1029 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+</protocol>
+<stdout mode="text">
+HTTP/1.1 301 This is a weirdo text message swsclose
+Location: data/10290002.txt?coolsite=yes
+Content-Length: 62
+Connection: close
+
+This server reply is for testing a simple Location: following
+http://127.0.0.1:8990/we/want/our/data/10290002.txt?coolsite=yes
+</stdout>
+</verify>
+</testcase>