Support redirect to relative Uri, as specified by rfc3986#4.2.

BUG=https://code.google.com/p/dart/issues/detail?id=10210
R=sgjesse@google.com

Review URL: https://codereview.chromium.org//14988003

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@22413 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/sdk/lib/io/http_impl.dart b/sdk/lib/io/http_impl.dart
index e3e4945..8ea7174 100644
--- a/sdk/lib/io/http_impl.dart
+++ b/sdk/lib/io/http_impl.dart
@@ -1412,6 +1412,29 @@
   Future<HttpClientRequest> _openUrlFromRequest(String method,
                                                 Uri uri,
                                                 _HttpClientRequest previous) {
+    // If the new URI is relative (to either '/' or some sub-path),
+    // construct a full URI from the previous one.
+    // See http://tools.ietf.org/html/rfc3986#section-4.2
+    replaceComponents({scheme, domain, port, path}) {
+      uri = new Uri.fromComponents(
+          scheme: scheme != null ? scheme : uri.scheme,
+          domain: domain != null ? domain : uri.domain,
+          port: port != null ? port : uri.port,
+          path: path != null ? path : uri.path,
+          query: uri.query,
+          fragment: uri.fragment);
+    }
+    if (uri.domain == '') {
+      replaceComponents(domain: previous.uri.domain, port: previous.uri.port);
+    }
+    if (uri.scheme == '') {
+      replaceComponents(scheme: previous.uri.scheme);
+    }
+    if (!uri.path.startsWith('/') && previous.uri.path.startsWith('/')) {
+      var absolute = new Path.raw(previous.uri.path).directoryPath;
+      absolute = absolute.join(new Path.raw(uri.path));
+      replaceComponents(path: absolute.canonicalize().toString());
+    }
     return openUrl(method, uri).then((_HttpClientRequest request) {
           // Only follow redirects if initial request did.
           request.followRedirects = previous.followRedirects;
diff --git a/tests/standalone/io/http_redirect_test.dart b/tests/standalone/io/http_redirect_test.dart
index 363f8d0..b3caef5 100644
--- a/tests/standalone/io/http_redirect_test.dart
+++ b/tests/standalone/io/http_redirect_test.dart
@@ -57,6 +57,69 @@
        }
     );
 
+    // Setup redirects with relative url.
+    addRequestHandler(
+       "/redirectUrl",
+       (HttpRequest request, HttpResponse response) {
+         response.headers.set(HttpHeaders.LOCATION, "/some/relativeUrl");
+         response.statusCode = HttpStatus.MOVED_PERMANENTLY;
+         response.close();
+       }
+    );
+
+    addRequestHandler(
+       "/some/redirectUrl",
+       (HttpRequest request, HttpResponse response) {
+         response.headers.set(HttpHeaders.LOCATION, "relativeUrl");
+         response.statusCode = HttpStatus.MOVED_PERMANENTLY;
+         response.close();
+       }
+    );
+
+    addRequestHandler(
+       "/some/relativeUrl",
+       (HttpRequest request, HttpResponse response) {
+         response.close();
+       }
+    );
+
+    addRequestHandler(
+       "/redirectUrl2",
+       (HttpRequest request, HttpResponse response) {
+         response.headers.set(HttpHeaders.LOCATION, "location");
+         response.statusCode = HttpStatus.MOVED_PERMANENTLY;
+         response.close();
+       }
+    );
+
+    addRequestHandler(
+       "/redirectUrl3",
+       (HttpRequest request, HttpResponse response) {
+         response.headers.set(HttpHeaders.LOCATION, "./location");
+         response.statusCode = HttpStatus.MOVED_PERMANENTLY;
+         response.close();
+       }
+    );
+
+    addRequestHandler(
+       "/redirectUrl4",
+       (HttpRequest request, HttpResponse response) {
+         response.headers.set(HttpHeaders.LOCATION, "./a/b/../../location");
+         response.statusCode = HttpStatus.MOVED_PERMANENTLY;
+         response.close();
+       }
+    );
+
+    addRequestHandler(
+       "/redirectUrl5",
+       (HttpRequest request, HttpResponse response) {
+         response.headers.set(HttpHeaders.LOCATION,
+                              "//127.0.0.1:${server.port}/location");
+         response.statusCode = HttpStatus.MOVED_PERMANENTLY;
+         response.close();
+       }
+    );
+
     // Setup redirect chain.
     int n = 1;
     addRedirectHandler(n++, HttpStatus.MOVED_PERMANENTLY);
@@ -370,6 +433,34 @@
   });
 }
 
+void testRedirectRelativeUrl() {
+  testPath(String path) {
+    setupServer().then((server) {
+      HttpClient client = new HttpClient();
+
+      print(path);
+      client.getUrl(Uri.parse("http://127.0.0.1:${server.port}$path"))
+          .then((request) => request.close())
+          .then((response) {
+            response.listen(
+                (_) {},
+                onDone: () {
+                  Expect.equals(HttpStatus.OK, response.statusCode);
+                  Expect.equals(1, response.redirects.length);
+                  server.close();
+                  client.close();
+                });
+            });
+    });
+  }
+  testPath("/redirectUrl");
+  testPath("/some/redirectUrl");
+  testPath("/redirectUrl2");
+  testPath("/redirectUrl3");
+  testPath("/redirectUrl4");
+  testPath("/redirectUrl5");
+}
+
 main() {
   testManualRedirect();
   testManualRedirectWithHeaders();
@@ -380,4 +471,5 @@
   testAutoRedirectLimit();
   testRedirectLoop();
   testRedirectClosingConnection();
+  testRedirectRelativeUrl();
 }