blob: e1d4b8e331365b43f7eb11659a343f7c8e3e75e9 [file] [log] [blame]
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package adaptorlib;
import static org.junit.Assert.*;
import com.sun.net.httpserver.HttpExchange;
import org.junit.*;
import org.junit.rules.ExpectedException;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
/**
* Tests for {@link DocumentHandlerTest}.
*/
public class DocumentHandlerTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
private SessionManager<HttpExchange> sessionManager
= new SessionManager<HttpExchange>(
new SessionManager.HttpExchangeClientStore(), 1000, 1000);
private MockAdaptor mockAdaptor = new MockAdaptor();
private DocumentHandler handler = createDefaultHandlerForAdaptor(mockAdaptor);
private MockHttpExchange ex = new MockHttpExchange("http", "GET", "/",
new MockHttpContext(null, "/"));
@Test
public void testSecurityDeny() throws Exception {
DocumentHandler handler = createDefaultHandlerForAdaptor(
new PrivateMockAdaptor());
handler.handle(ex);
assertEquals(403, ex.getResponseCode());
}
@Test
public void testSecurityDenyWithAuthnHandler() throws Exception {
AbstractHandler authnHandler
= new AbstractHandler("localhost", Charset.forName("UTF-8")) {
@Override
protected void meteredHandle(HttpExchange ex) throws IOException {
cannedRespond(ex, 1234, "text/plain", "Testing");
}
};
DocumentHandler handler = new DocumentHandler("localhost",
Charset.forName("UTF-8"), new MockDocIdDecoder(),
new Journal(new MockTimeProvider()), new PrivateMockAdaptor(), false,
"localhost", new String[0], authnHandler, sessionManager, null, 0,
false);
handler.handle(ex);
assertEquals(1234, ex.getResponseCode());
}
@Test
public void testSecurityDenySession() throws Exception {
// Create a new session for this HttpExchange.
Session session = sessionManager.getSession(ex, true);
DocumentHandler handler = createDefaultHandlerForAdaptor(
new UserPrivateMockAdaptor());
handler.handle(ex);
assertEquals(403, ex.getResponseCode());
}
@Test
public void testSecurityDenyUnauthSession() throws Exception {
// Create a new unauthenticated session for this HttpExchange.
Session session = sessionManager.getSession(ex, true);
AuthnState authn = new AuthnState();
session.setAttribute(AuthnState.SESSION_ATTR_NAME, authn);
DocumentHandler handler = createDefaultHandlerForAdaptor(
new UserPrivateMockAdaptor());
handler.handle(ex);
assertEquals(403, ex.getResponseCode());
}
@Test
public void testSecurityPermit() throws Exception {
// Create a new authenticated session for this HttpExchange.
Session session = sessionManager.getSession(ex, true);
AuthnState authn = new AuthnState();
session.setAttribute(AuthnState.SESSION_ATTR_NAME, authn);
authn.authenticated("test", Collections.<String>emptySet(), Long.MAX_VALUE);
DocumentHandler handler = createDefaultHandlerForAdaptor(
new UserPrivateMockAdaptor());
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
}
@Test
public void testSecurityNotFound() throws Exception {
Adaptor mockAdaptor = new MockAdaptor() {
@Override
public Map<DocId, AuthzStatus> isUserAuthorized(String userIdentifier,
Set<String> groups,
Collection<DocId> ids) {
Map<DocId, AuthzStatus> result
= new HashMap<DocId, AuthzStatus>(ids.size() * 2);
for (DocId id : ids) {
result.put(id, AuthzStatus.INDETERMINATE);
}
return Collections.unmodifiableMap(result);
}
@Override
public void getDocContent(Request request, Response response) {
throw new UnsupportedOperationException();
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(mockAdaptor);
handler.handle(ex);
assertEquals(404, ex.getResponseCode());
}
@Test
public void testSecurityDisallowedUser() throws Exception {
// Create a new authenticated session for this HttpExchange.
Session session = sessionManager.getSession(ex, true);
AuthnState authn = new AuthnState();
session.setAttribute(AuthnState.SESSION_ATTR_NAME, authn);
authn.authenticated("test", Collections.<String>emptySet(), Long.MAX_VALUE);
DocumentHandler handler = createDefaultHandlerForAdaptor(
new PrivateMockAdaptor());
handler.handle(ex);
assertEquals(403, ex.getResponseCode());
}
@Test
public void testSecurityFromGsa() throws Exception {
// 127.0.0.3 is the address hard-coded in MockHttpExchange
DocumentHandler handler = new DocumentHandler(
"localhost", Charset.forName("UTF-8"),
new MockDocIdDecoder(), new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), false, "localhost",
new String[] {"127.0.0.3", " "}, null, sessionManager, null, 0, false);
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertArrayEquals(mockAdaptor.documentBytes, ex.getResponseBytes());
}
@Test
public void testSecurityFromGsaNoWhitelist() throws Exception {
// 127.0.0.3 is the address hard-coded in MockHttpExchange
DocumentHandler handler = new DocumentHandler(
"localhost", Charset.forName("UTF-8"),
new MockDocIdDecoder(), new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), false, "127.0.0.3",
new String[0], null, sessionManager, null, 0, false);
handler.handle(ex);
assertEquals(403, ex.getResponseCode());
}
@Test
public void testSecurityFromGsaAutoAddWhitelist() throws Exception {
// 127.0.0.3 is the address hard-coded in MockHttpExchange
DocumentHandler handler = new DocumentHandler(
"localhost", Charset.forName("UTF-8"),
new MockDocIdDecoder(), new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), true, "127.0.0.3",
new String[0], null, sessionManager, null, 0, false);
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertArrayEquals(mockAdaptor.documentBytes, ex.getResponseBytes());
}
@Test
public void testNoSuchHost() throws Exception {
thrown.expect(RuntimeException.class);
DocumentHandler handler = new DocumentHandler(
"localhost", Charset.forName("UTF-8"),
new MockDocIdDecoder(), new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), false, "localhost",
new String[] {"-no-such-host-"}, null, sessionManager, null, 0, false);
}
@Test
public void testNoSuchGsaHost() throws Exception {
thrown.expect(RuntimeException.class);
DocumentHandler handler = new DocumentHandler(
"localhost", Charset.forName("UTF-8"),
new MockDocIdDecoder(), new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), true, "-no-such-host-",
new String[0], null, sessionManager, null, 0, false);
}
@Test
public void testNormal() throws Exception {
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertArrayEquals(mockAdaptor.documentBytes, ex.getResponseBytes());
}
@Test
public void testNormalHead() throws Exception {
MockHttpExchange ex = new MockHttpExchange("http", "HEAD", "/",
new MockHttpContext(handler, "/"));
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertArrayEquals(new byte[] {}, ex.getResponseBytes());
}
@Test
public void testNormalPost() throws Exception {
MockHttpExchange ex = new MockHttpExchange("http", "POST", "/",
new MockHttpContext(handler, "/"));
handler.handle(ex);
assertEquals(405, ex.getResponseCode());
}
@Test
public void testTransform() throws Exception {
final byte[] golden = new byte[] {2, 3, 4};
final String key = "testing key";
TransformPipeline transform = new TransformPipeline();
transform.add(new DocumentTransform("testing") {
@Override
public void transform(ByteArrayOutputStream contentIn,
OutputStream contentOut,
Map<String, String> metadata,
Map<String, String> params) throws IOException {
assertArrayEquals(mockAdaptor.documentBytes, contentIn.toByteArray());
contentOut.write(golden);
metadata.put(key, metadata.get(key).toUpperCase());
metadata.put("docid", params.get("DocId"));
}
});
mockAdaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
Set<MetaItem> metaSet = new HashSet<MetaItem>();
metaSet.add(MetaItem.raw(key, "testing value"));
response.setMetadata(new Metadata(metaSet));
super.getDocContent(request, response);
}
};
// 127.0.0.3 is the address hard-coded in MockHttpExchange
DocumentHandler handler = new DocumentHandler("localhost",
Charset.forName("UTF-8"), new MockDocIdDecoder(),
new Journal(new MockTimeProvider()), mockAdaptor, false, "localhost",
new String[] {"127.0.0.3"}, null, sessionManager, transform, 100,
false);
mockAdaptor.documentBytes = new byte[] {1, 2, 3};
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertArrayEquals(golden, ex.getResponseBytes());
assertEquals("docid=http%3A%2F%2Flocalhost%2F,"
+ "testing%20key=TESTING%20VALUE",
ex.getResponseHeaders().getFirst("X-Gsa-External-Metadata"));
}
@Test
public void testTransformDocumentTooLarge() throws Exception {
TransformPipeline transform = new TransformPipeline();
transform.add(new DocumentTransform("testing") {
@Override
public void transform(ByteArrayOutputStream contentIn,
OutputStream contentOut,
Map<String, String> metadata,
Map<String, String> params) throws IOException {
// This is not the content we are looking for.
contentOut.write(new byte[] {2, 3, 4});
}
});
final byte[] golden = new byte[] {-1, 2, -3, 4, 5};
mockAdaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
OutputStream os = response.getOutputStream();
// Just for the heck of it, test using the single byte version.
os.write(golden[0]);
os.write(golden[1]);
// Write out too much content for the buffer to hold here.
os.write(golden, 2, golden.length - 2 - 1);
os.write(golden, golden.length - 1, 1);
}
};
// 127.0.0.3 is the address hard-coded in MockHttpExchange
DocumentHandler handler = new DocumentHandler("localhost",
Charset.forName("UTF-8"), new MockDocIdDecoder(),
new Journal(new MockTimeProvider()), mockAdaptor, false, "localhost",
new String[] {"127.0.0.3"}, null, sessionManager, transform, 3, false);
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertArrayEquals(golden, ex.getResponseBytes());
assertNull(ex.getResponseHeaders().getFirst("X-Gsa-External-Metadata"));
}
@Test
public void testTransformDocumentTooLargeButRequired() throws Exception {
TransformPipeline transform = new TransformPipeline();
class CheckFailAdaptor extends MockAdaptor {
public boolean failedAtCorrectTime = false;
@Override
public void getDocContent(Request request, Response response)
throws IOException {
OutputStream os = response.getOutputStream();
os.write(new byte[] {-1, 2, -3});
failedAtCorrectTime = true;
// Write out too much content for the buffer to hold here.
os.write(4);
failedAtCorrectTime = false;
}
};
CheckFailAdaptor mockAdaptor = new CheckFailAdaptor();
// 127.0.0.3 is the address hard-coded in MockHttpExchange
DocumentHandler handler = new DocumentHandler("localhost",
Charset.forName("UTF-8"), new MockDocIdDecoder(),
new Journal(new MockTimeProvider()), mockAdaptor, false, "localhost",
new String[] {"127.0.0.3"}, null, sessionManager, transform, 3, true);
handler.handle(ex);
assertEquals(500, ex.getResponseCode());
assertTrue(mockAdaptor.failedAtCorrectTime);
}
@Test
public void testNullAuthzResponse() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public Map<DocId, AuthzStatus> isUserAuthorized(String userIdentifier,
Set<String> groups, Collection<DocId> ids) {
return null;
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
handler.handle(ex);
assertEquals(403, ex.getResponseCode());
}
@Test
public void testFileNotFound() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
throw new FileNotFoundException();
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
handler.handle(ex);
assertEquals(404, ex.getResponseCode());
}
@Test
public void testIOException() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
throw new IOException();
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
handler.handle(ex);
assertEquals(500, ex.getResponseCode());
}
@Test
public void testRuntimeException() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
throw new RuntimeException();
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
handler.handle(ex);
assertEquals(500, ex.getResponseCode());
}
@Test
public void testNoOutput() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
handler.handle(ex);
assertEquals(500, ex.getResponseCode());
}
@Test
public void testNotModified() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
response.respondNotModified();
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
handler.handle(ex);
assertEquals(304, ex.getResponseCode());
}
@Test
public void testNotModifiedThenOutputStream() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
response.respondNotModified();
response.getOutputStream();
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
handler.handle(ex);
assertEquals(500, ex.getResponseCode());
}
@Test
public void testOutputStreamThenNotModified() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
response.getOutputStream();
response.respondNotModified();
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
handler.handle(ex);
assertEquals(500, ex.getResponseCode());
}
@Test
public void testOutputStreamTwice() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
response.getOutputStream();
response.getOutputStream();
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
}
@Test
public void testSetContentTypeLate() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
response.getOutputStream();
response.setContentType("text/plain");
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
handler.handle(ex);
assertEquals(500, ex.getResponseCode());
}
@Test
public void testSetMetadataLate() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
response.getOutputStream();
response.setMetadata(Metadata.EMPTY);
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
handler.handle(ex);
assertEquals(500, ex.getResponseCode());
}
@Test
public void testSmartAdaptor() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
if (!request.getDocId().equals(new DocId("http://localhost/"))) {
throw new FileNotFoundException();
}
if (!request.hasChangedSinceLastAccess(new Date(1 * 1000))) {
response.respondNotModified();
return;
}
response.setContentType("text/plain");
response.setMetadata(Metadata.EMPTY);
response.getOutputStream();
// It is free to get it multiple times
response.getOutputStream();
}
};
DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor);
ex.getRequestHeaders().set("If-Modified-Since",
"Thu, 1 Jan 1970 00:00:01 GMT");
handler.handle(ex);
assertEquals(304, ex.getResponseCode());
MockHttpExchange ex = new MockHttpExchange("http", "GET", "/",
new MockHttpContext(handler, "/"));
ex.getRequestHeaders().set("If-Modified-Since",
"Thu, 1 Jan 1970 00:00:00 GMT");
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertEquals("text/plain",
ex.getResponseHeaders().getFirst("Content-Type"));
ex = new MockHttpExchange("http", "HEAD", "/",
new MockHttpContext(handler, "/"));
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
}
@Test
public void testMetadataNonGsa() throws Exception {
MockAdaptor adaptor = new MockAdaptor() {
@Override
public void getDocContent(Request request, Response response)
throws IOException {
response.setMetadata(new Metadata(Collections.singleton(
MetaItem.raw("test", "ing"))));
response.getOutputStream();
}
};
DocumentHandler handler = new DocumentHandler(
"localhost", Charset.forName("UTF-8"),
new MockDocIdDecoder(), new Journal(new MockTimeProvider()),
adaptor, false, "localhost", new String[0], null,
sessionManager, null, 0, false);
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertNull(ex.getResponseHeaders().getFirst("X-Gsa-External-Metadata"));
}
private DocumentHandler createDefaultHandlerForAdaptor(Adaptor adaptor) {
return new DocumentHandler("localhost", Charset.forName("UTF-8"),
new MockDocIdDecoder(), new Journal(new MockTimeProvider()),
adaptor, false, "localhost", new String[0], null,
sessionManager, null, 0, false);
}
@Test
public void testFormMetadataHeader() {
Set<MetaItem> items = new HashSet<MetaItem>();
items.add(MetaItem.isPublic());
items.add(MetaItem.raw("test", "ing"));
items.add(MetaItem.raw("another", "item"));
items.add(MetaItem.raw("equals=", "=="));
String result = DocumentHandler.formMetadataHeader(new Metadata(items));
assertEquals("another=item,equals%3D=%3D%3D,google%3Aispublic=true,"
+ "test=ing", result);
}
@Test
public void testFormMetadataHeaderEmpty() {
String header = DocumentHandler.formMetadataHeader(
new Metadata(new HashSet<MetaItem>()));
assertEquals("", header);
}
@Test
public void testPercentEncoding() {
String encoded = DocumentHandler.percentEncode(
"AaZz-_.~`=/?+';\\/\"!@#$%^&*()[]{}ë\u0001");
assertEquals("AaZz-_.~%60%3D%2F%3F%2B%27%3B%5C%2F%22%21%40%23%24%25%5E%26"
+ "%2A%28%29%5B%5D%7B%7D%C3%AB%01", encoded);
}
private static class UserPrivateMockAdaptor extends MockAdaptor {
@Override
public Map<DocId, AuthzStatus> isUserAuthorized(String userIdentifier,
Set<String> groups,
Collection<DocId> ids) {
Map<DocId, AuthzStatus> result
= new HashMap<DocId, AuthzStatus>(ids.size() * 2);
for (DocId id : ids) {
result.put(id,
userIdentifier == null ? AuthzStatus.DENY : AuthzStatus.PERMIT);
}
return Collections.unmodifiableMap(result);
}
};
}