blob: e82819208363f2fbb8a5448e0b05aadf851a0c10 [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// @dart = 2.9
import "package:expect/expect.dart";
import "dart:async";
import "dart:io";
import "dart:mirrors";
Future<HttpServer> setupServer({Uri targetServer}) {
Completer completer = new Completer<HttpServer>();
HttpServer.bind("127.0.0.1", 0).then((server) {
var handlers = new Map<String, Function>();
addRequestHandler(
String path, void handler(HttpRequest request, HttpResponse response)) {
handlers[path] = handler;
}
server.listen((HttpRequest request) {
if (handlers.containsKey(request.uri.path)) {
handlers[request.uri.path](request, request.response);
} else {
request.listen((_) {}, onDone: () {
request.response.statusCode = 404;
request.response.close();
});
}
});
void addRedirectHandler(int number, int statusCode) {
addRequestHandler("/$number",
(HttpRequest request, HttpResponse response) {
response.redirect(
Uri.parse("http://127.0.0.1:${server.port}/${number + 1}"));
});
}
// Setup simple redirect.
addRequestHandler("/redirect",
(HttpRequest request, HttpResponse response) {
response.redirect(Uri.parse("http://127.0.0.1:${server.port}/location"),
status: HttpStatus.movedPermanently);
});
addRequestHandler("/location",
(HttpRequest request, HttpResponse response) {
response.close();
});
// Setup redirects with relative url.
addRequestHandler("/redirectUrl",
(HttpRequest request, HttpResponse response) {
response.headers.set(HttpHeaders.locationHeader, "/some/relativeUrl");
response.statusCode = HttpStatus.movedPermanently;
response.close();
});
addRequestHandler("/some/redirectUrl",
(HttpRequest request, HttpResponse response) {
response.headers.set(HttpHeaders.locationHeader, "relativeUrl");
response.statusCode = HttpStatus.movedPermanently;
response.close();
});
addRequestHandler("/some/relativeUrl",
(HttpRequest request, HttpResponse response) {
response.close();
});
addRequestHandler("/some/relativeToAbsolute",
(HttpRequest request, HttpResponse response) {
response.redirect(Uri.parse("xxx"), status: HttpStatus.seeOther);
});
addRequestHandler("/redirectUrl2",
(HttpRequest request, HttpResponse response) {
response.headers.set(HttpHeaders.locationHeader, "location");
response.statusCode = HttpStatus.movedPermanently;
response.close();
});
addRequestHandler("/redirectUrl3",
(HttpRequest request, HttpResponse response) {
response.headers.set(HttpHeaders.locationHeader, "./location");
response.statusCode = HttpStatus.movedPermanently;
response.close();
});
addRequestHandler("/redirectUrl4",
(HttpRequest request, HttpResponse response) {
response.headers.set(HttpHeaders.locationHeader, "./a/b/../../location");
response.statusCode = HttpStatus.movedPermanently;
response.close();
});
addRequestHandler("/redirectUrl5",
(HttpRequest request, HttpResponse response) {
response.headers.set(
HttpHeaders.locationHeader, "//127.0.0.1:${server.port}/location");
response.statusCode = HttpStatus.movedPermanently;
response.close();
});
// Setup redirect chain.
int n = 1;
addRedirectHandler(n++, HttpStatus.movedPermanently);
addRedirectHandler(n++, HttpStatus.movedTemporarily);
addRedirectHandler(n++, HttpStatus.seeOther);
addRedirectHandler(n++, HttpStatus.temporaryRedirect);
addRedirectHandler(n++, HttpStatus.permanentRedirect);
for (int i = n; i < 10; i++) {
addRedirectHandler(i, HttpStatus.movedPermanently);
}
// Setup redirect loop.
addRequestHandler("/A", (HttpRequest request, HttpResponse response) {
response.headers
.set(HttpHeaders.locationHeader, "http://127.0.0.1:${server.port}/B");
response.statusCode = HttpStatus.movedPermanently;
response.close();
});
addRequestHandler("/B", (HttpRequest request, HttpResponse response) {
response.headers
.set(HttpHeaders.locationHeader, "http://127.0.0.1:${server.port}/A");
response.statusCode = HttpStatus.movedTemporarily;
response.close();
});
// Setup redirect checking headers.
addRequestHandler("/src", (HttpRequest request, HttpResponse response) {
Expect.equals("value", request.headers.value("X-Request-Header"));
Expect.isNotNull(request.headers.value("Authorization"),
"expected 'Authorization' header to be set");
response.headers.set(
HttpHeaders.locationHeader, "http://127.0.0.1:${server.port}/target");
response.statusCode = HttpStatus.movedPermanently;
response.close();
});
addRequestHandler("/target", (HttpRequest request, HttpResponse response) {
Expect.equals("value", request.headers.value("X-Request-Header"));
Expect.isNotNull(request.headers.value("Authorization"),
"expected 'Authorization' header to be set");
response.close();
});
if (targetServer != null) {
addRequestHandler("/src-crossdomain",
(HttpRequest request, HttpResponse response) {
Expect.equals("value", request.headers.value("X-Request-Header"));
Expect.isNotNull(request.headers.value("Authorization"),
"expected 'Authorization' header to be set");
response.headers
.set(HttpHeaders.locationHeader, targetServer.toString());
response.statusCode = HttpStatus.movedPermanently;
response.close();
});
}
// Setup redirect for 301 where POST should not redirect.
addRequestHandler("/301src", (HttpRequest request, HttpResponse response) {
Expect.equals("POST", request.method);
request.listen((_) {}, onDone: () {
response.headers.set(HttpHeaders.locationHeader,
"http://127.0.0.1:${server.port}/301target");
response.statusCode = HttpStatus.movedPermanently;
response.close();
});
});
addRequestHandler("/301target",
(HttpRequest request, HttpResponse response) {
Expect.fail("Redirect of POST should not happen");
});
// Setup redirect for 303 where POST should turn into GET.
addRequestHandler("/303src", (HttpRequest request, HttpResponse response) {
request.listen((_) {}, onDone: () {
Expect.equals("POST", request.method);
response.headers.set(HttpHeaders.locationHeader,
"http://127.0.0.1:${server.port}/303target");
response.statusCode = HttpStatus.seeOther;
response.close();
});
});
addRequestHandler("/303target",
(HttpRequest request, HttpResponse response) {
Expect.equals("GET", request.method);
response.close();
});
// Setup redirect where we close the connection.
addRequestHandler("/closing", (HttpRequest request, HttpResponse response) {
response.headers
.set(HttpHeaders.locationHeader, "http://127.0.0.1:${server.port}/");
response.statusCode = HttpStatus.found;
response.persistentConnection = false;
response.close();
});
completer.complete(server);
});
return completer.future;
}
// A second HTTP server used to validate that redirect requests accross domains
// do *not* include security-related headers.
Future<HttpServer> setupTargetServer() {
Completer completer = new Completer<HttpServer>();
HttpServer.bind("127.0.0.1", 0).then((server) {
var handlers = new Map<String, Function>();
addRequestHandler(
String path, void handler(HttpRequest request, HttpResponse response)) {
handlers[path] = handler;
}
server.listen((HttpRequest request) {
if (request.uri.path == "/target") {
Expect.equals("value", request.headers.value("X-Request-Header"));
Expect.isNull(request.headers.value("Authorization"),
"expected 'Authorization' header to be removed on redirect");
request.response.close();
} else {
request.listen((_) {}, onDone: () {
request.response.statusCode = 404;
request.response.close();
});
}
});
completer.complete(server);
});
return completer.future;
}
void checkRedirects(int redirectCount, HttpClientResponse response) {
if (redirectCount < 2) {
Expect.isTrue(response.redirects.isEmpty);
} else {
Expect.equals(redirectCount - 1, response.redirects.length);
for (int i = 0; i < redirectCount - 2; i++) {
Expect.equals(response.redirects[i].location.path, "/${i + 2}");
}
}
}
void testManualRedirect() {
setupServer().then((server) {
HttpClient client = new HttpClient();
int redirectCount = 0;
handleResponse(HttpClientResponse response) {
response.listen((_) => Expect.fail("Response data not expected"),
onDone: () {
redirectCount++;
if (redirectCount < 10) {
Expect.isTrue(response.isRedirect);
checkRedirects(redirectCount, response);
response.redirect().then(handleResponse);
} else {
Expect.equals(HttpStatus.notFound, response.statusCode);
server.close();
client.close();
}
});
}
client
.getUrl(Uri.parse("http://127.0.0.1:${server.port}/1"))
.then((HttpClientRequest request) {
request.followRedirects = false;
return request.close();
}).then(handleResponse);
});
}
void testManualRedirectWithHeaders() {
setupServer().then((server) {
HttpClient client = new HttpClient();
int redirectCount = 0;
handleResponse(HttpClientResponse response) {
response.listen((_) => Expect.fail("Response data not expected"),
onDone: () {
redirectCount++;
if (redirectCount < 2) {
Expect.isTrue(response.isRedirect);
response.redirect().then(handleResponse);
} else {
Expect.equals(HttpStatus.ok, response.statusCode);
server.close();
client.close();
}
});
}
client
.getUrl(Uri.parse("http://127.0.0.1:${server.port}/src"))
.then((HttpClientRequest request) {
request.followRedirects = false;
request.headers.add("X-Request-Header", "value");
request.headers.add("Authorization", "Basic ...");
return request.close();
}).then(handleResponse);
});
}
void testAutoRedirect() {
setupServer().then((server) {
HttpClient client = new HttpClient();
client
.getUrl(Uri.parse("http://127.0.0.1:${server.port}/redirect"))
.then((HttpClientRequest request) {
return request.close();
}).then((HttpClientResponse response) {
response.listen((_) => Expect.fail("Response data not expected"),
onDone: () {
Expect.equals(1, response.redirects.length);
server.close();
client.close();
});
});
});
}
void testAutoRedirectZeroMaxRedirects() {
setupServer().then((server) {
HttpClient client = new HttpClient();
client
.getUrl(Uri.parse("http://127.0.0.1:${server.port}/redirect"))
.then((HttpClientRequest request) {
request
..followRedirects = true
..maxRedirects = 0;
return request.close();
}).then((HttpClientResponse response) {
response.drain();
Expect.fail("Response data not expected");
}, onError: (error) {
final httpException = error as HttpException;
Expect.equals(httpException.message, "Redirect limit exceeded");
Expect.equals(httpException.uri, null);
server.close();
client.close();
});
});
}
void testAutoRedirectWithHeaders() {
setupServer().then((server) {
HttpClient client = new HttpClient();
client
.getUrl(Uri.parse("http://127.0.0.1:${server.port}/src"))
.then((HttpClientRequest request) {
request.headers.add("X-Request-Header", "value");
request.headers.add("Authorization", "Basic ...");
return request.close();
}).then((HttpClientResponse response) {
response.listen((_) => Expect.fail("Response data not expected"),
onDone: () {
Expect.equals(1, response.redirects.length);
server.close();
client.close();
});
});
});
}
void testShouldCopyHeadersOnRedirect() {
final clientClass = reflect(HttpClient()).type;
final fnName = Symbol("shouldCopyHeaderOnRedirect");
shouldCopyHeaderOnRedirect(
String headerKey, Uri originalUrl, Uri redirectUri) =>
clientClass.invoke(
fnName, [headerKey, originalUrl, redirectUri]).reflectee as bool;
checkShouldCopyHeader(
String headerKey, String originalUrl, String redirectUri, bool expected) {
if (shouldCopyHeaderOnRedirect(
headerKey, Uri.parse(originalUrl), Uri.parse(redirectUri)) !=
expected) {
Expect.fail(
"shouldCopyHeaderOnRedirect($headerKey, $originalUrl, $redirectUri) => ${!expected}");
}
}
// Redirect on localhost.
checkShouldCopyHeader(
"authorization", "http://localhost", "http://localhost/foo", true);
checkShouldCopyHeader(
"cat", "http://localhost", "http://localhost/foo", true);
// Redirect to same IP address.
checkShouldCopyHeader("authorization", "http://192.168.20.20",
"http://192.168.20.20/foo", true);
checkShouldCopyHeader(
"cat", "http://192.168.20.20", "http://192.168.20.20/foo", true);
// Redirect to different IP address.
checkShouldCopyHeader(
"authorization", "http://192.168.20.20", "http://192.168.20.99", false);
checkShouldCopyHeader(
"cat", "http://192.168.20.20", "http://192.168.20.99", true);
// Redirect to same domain.
checkShouldCopyHeader(
"authorization", "http://foo.com", "http://foo.com/foo", true);
checkShouldCopyHeader("cat", "http://foo.com", "http://foo.com/foo", true);
// Redirect to same domain with explicit ports.
checkShouldCopyHeader(
"authorization", "http://foo.com", "http://foo.com:80/foo", true);
checkShouldCopyHeader("cat", "http://foo.com", "http://foo.com:80/foo", true);
// Redirect to subdomain.
checkShouldCopyHeader(
"authorization", "https://foo.com", "https://www.foo.com", true);
checkShouldCopyHeader("cat", "https://foo.com", "https://www.foo.com", true);
// Redirect to different domain.
checkShouldCopyHeader(
"authorization", "https://foo.com", "https://wwwfoo.com", false);
checkShouldCopyHeader("cat", "https://foo.com", "https://wwwfoo.com", true);
// Redirect to different port.
checkShouldCopyHeader(
"authorization", "http://foo.com", "http://foo.com:81", false);
checkShouldCopyHeader("cat", "http://foo.com", "http://foo.com:81", true);
// Redirect from secure to insecure.
checkShouldCopyHeader(
"authorization", "https://foo.com", "http://foo.com", false);
checkShouldCopyHeader("cat", "https://foo.com", "http://foo.com", true);
// Redirect from secure to insecure, same port.
checkShouldCopyHeader(
"authorization", "https://foo.com:8888", "http://foo.com:8888", false);
checkShouldCopyHeader(
"cat", "https://foo.com:8888", "http://foo.com:8888", true);
// Redirect from insecure to secure.
checkShouldCopyHeader(
"authorization", "http://foo.com", "https://foo.com", false);
checkShouldCopyHeader("cat", "http://foo.com", "https://foo.com", true);
// Redirect to subdomain, different port.
checkShouldCopyHeader(
"authorization", "https://foo.com:80", "https://www.foo.com:81", false);
checkShouldCopyHeader(
"cat", "https://foo.com:80", "https://www.foo.com:81", true);
// Different header casting:
checkShouldCopyHeader(
"AuThOrIzAtiOn", "https://foo.com", "https://bar.com", false);
}
void testCrossDomainAutoRedirectWithHeaders() {
setupTargetServer().then((targetServer) {
setupServer(
targetServer:
Uri.parse("http://127.0.0.1:${targetServer.port}/target"))
.then((server) {
HttpClient client = new HttpClient();
client
.getUrl(Uri.parse("http://127.0.0.1:${server.port}/src-crossdomain"))
.then((HttpClientRequest request) {
request.headers.add("X-Request-Header", "value");
request.headers.add("Authorization", "Basic ...");
return request.close();
}).then((HttpClientResponse response) {
response.listen((_) => Expect.fail("Response data not expected"),
onDone: () {
Expect.equals(1, response.redirects.length);
targetServer.close();
server.close();
client.close();
});
});
});
});
}
void testAutoRedirect301POST() {
setupServer().then((server) {
HttpClient client = new HttpClient();
client
.postUrl(Uri.parse("http://127.0.0.1:${server.port}/301src"))
.then((HttpClientRequest request) {
return request.close();
}).then((HttpClientResponse response) {
Expect.equals(HttpStatus.movedPermanently, response.statusCode);
response.listen((_) => Expect.fail("Response data not expected"),
onDone: () {
Expect.equals(0, response.redirects.length);
server.close();
client.close();
});
});
});
}
void testAutoRedirect303POST() {
setupServer().then((server) {
HttpClient client = new HttpClient();
client
.postUrl(Uri.parse("http://127.0.0.1:${server.port}/303src"))
.then((HttpClientRequest request) {
return request.close();
}).then((HttpClientResponse response) {
Expect.equals(HttpStatus.ok, response.statusCode);
response.listen((_) => Expect.fail("Response data not expected"),
onDone: () {
Expect.equals(1, response.redirects.length);
server.close();
client.close();
});
});
});
}
void testAutoRedirectLimit() {
setupServer().then((server) {
HttpClient client = new HttpClient();
client
.getUrl(Uri.parse("http://127.0.0.1:${server.port}/1"))
.then((HttpClientRequest request) => request.close())
.catchError((error) {
Expect.equals(5, error.redirects.length);
server.close();
client.close();
}, test: (e) => e is RedirectException);
});
}
void testRedirectLoop() {
setupServer().then((server) {
HttpClient client = new HttpClient();
int redirectCount = 0;
client
.getUrl(Uri.parse("http://127.0.0.1:${server.port}/A"))
.then((HttpClientRequest request) => request.close())
.catchError((error) {
Expect.equals(2, error.redirects.length);
server.close();
client.close();
}, test: (e) => e is RedirectException);
});
}
void testRedirectClosingConnection() {
setupServer().then((server) {
HttpClient client = new HttpClient();
client
.getUrl(Uri.parse("http://127.0.0.1:${server.port}/closing"))
.then((request) => request.close())
.then((response) {
response.listen((_) {}, onDone: () {
Expect.equals(1, response.redirects.length);
server.close();
client.close();
});
});
});
}
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");
}
void testRedirectRelativeToAbsolute() {
setupServer().then((server) {
HttpClient client = new HttpClient();
int redirectCount = 0;
handleResponse(HttpClientResponse response) {
response.listen((_) => Expect.fail("Response data not expected"),
onDone: () {
Expect.equals(HttpStatus.seeOther, response.statusCode);
Expect.equals("xxx", response.headers["Location"][0]);
Expect.isTrue(response.isRedirect);
server.close();
client.close();
});
}
client
.getUrl(Uri.parse(
"http://127.0.0.1:${server.port}/some/relativeToAbsolute"))
.then((HttpClientRequest request) {
request.followRedirects = false;
return request.close();
}).then(handleResponse);
});
}
main() {
testManualRedirect();
testManualRedirectWithHeaders();
testAutoRedirect();
testAutoRedirectZeroMaxRedirects();
testAutoRedirectWithHeaders();
testShouldCopyHeadersOnRedirect();
testCrossDomainAutoRedirectWithHeaders();
testAutoRedirect301POST();
testAutoRedirect303POST();
testAutoRedirectLimit();
testRedirectLoop();
testRedirectClosingConnection();
testRedirectRelativeUrl();
testRedirectRelativeToAbsolute();
}