send namespaced acls in http headers
diff --git a/src/com/google/enterprise/adaptor/Config.java b/src/com/google/enterprise/adaptor/Config.java
index 2fc1f9e..492cc1e 100644
--- a/src/com/google/enterprise/adaptor/Config.java
+++ b/src/com/google/enterprise/adaptor/Config.java
@@ -36,6 +36,11 @@
* <td><b>name</b></td><td><b>meaning</b></td>
* <tr><td> </td><td>adaptor.autoUnzip </td><td> expand zip files and send
* each file inside separatly. Defaults to false
+ * <tr><td> </td><td>adaptor.sendDocControlsHeader </td><td>use
+ * X-Gsa-Doc-Controls HTTP header with namespaced ACLs,
+ * lock attribute, crawl-once attribute.
+ * Otherwise ACLs are sent without namespace and as metadata.
+ * Also other noted items are not sent at all. Defaults to false
* <tr><td> </td><td>adaptor.fullListingSchedule </td><td> when to invoke
* {@link Adaptor#getDocIds Adaptor.getDocIds}, in cron format (minute,
* hour, day of month, month, day of week). Defaults to 0 3 * * *
@@ -207,6 +212,7 @@
addKey("transform.maxDocumentBytes", "1048576");
addKey("transform.required", "false");
addKey("journal.reducedMem", "true");
+ addKey("adaptor.sendDocControlsHeader", "false");
}
public Set<String> getAllKeys() {
@@ -394,6 +400,10 @@
return Boolean.parseBoolean(getValue("server.useCompression"));
}
+ public boolean sendDocControlsHeader() {
+ return Boolean.parseBoolean(getValue("adaptor.sendDocControlsHeader"));
+ }
+
/**
* Optional (default false): Adds no-recrawl bit with sent records in feed
* file. If connector handles updates and deletes then GSA does not have to
diff --git a/src/com/google/enterprise/adaptor/DocumentHandler.java b/src/com/google/enterprise/adaptor/DocumentHandler.java
index 910d871..a145e1f 100644
--- a/src/com/google/enterprise/adaptor/DocumentHandler.java
+++ b/src/com/google/enterprise/adaptor/DocumentHandler.java
@@ -16,10 +16,15 @@
import static java.util.Map.Entry;
+import com.google.common.annotations.VisibleForTesting;
+
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpsExchange;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -28,7 +33,6 @@
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
-import java.security.Principal;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -64,6 +68,7 @@
private final int transformMaxBytes;
private final boolean transformRequired;
private final boolean useCompression;
+ private final boolean sendDocControls;
/**
* {@code authnHandler} and {@code transform} may be {@code null}.
@@ -75,7 +80,7 @@
SessionManager<HttpExchange> sessionManager,
TransformPipeline transform, int transformMaxBytes,
boolean transformRequired, boolean useCompression,
- Watchdog watchdog) {
+ boolean sendDocControls, Watchdog watchdog) {
if (docIdDecoder == null || docIdEncoder == null || journal == null
|| adaptor == null || sessionManager == null || watchdog == null) {
throw new NullPointerException();
@@ -90,6 +95,7 @@
this.transformMaxBytes = transformMaxBytes;
this.transformRequired = transformRequired;
this.useCompression = useCompression;
+ this.sendDocControls = sendDocControls;
this.watchdog = watchdog;
initFullAccess(gsaHostname, fullAccessHosts);
@@ -123,7 +129,7 @@
private boolean requestIsFromFullyTrustedClient(HttpExchange ex) {
boolean trust;
if (ex instanceof HttpsExchange) {
- Principal principal;
+ java.security.Principal principal;
try {
principal = ((HttpsExchange) ex).getSSLSession().getPeerPrincipal();
} catch (SSLPeerUnverifiedException e) {
@@ -293,7 +299,8 @@
return (sb.length() == 0) ? "" : sb.substring(0, sb.length() - 1);
}
- static String formAclHeader(Acl acl, DocIdEncoder docIdEncoder) {
+ @VisibleForTesting
+ static String formUnqualifiedAclHeader(Acl acl, DocIdEncoder docIdEncoder) {
if (acl == null) {
return "";
}
@@ -301,7 +308,6 @@
acl = Acl.FAKE_EMPTY;
}
StringBuilder sb = new StringBuilder();
- // TODO: Use Principals instead of just names
for (UserPrincipal permitUser : acl.getPermitUsers()) {
String name = permitUser.getName();
percentEncodeMapEntryPair(sb, "google:aclusers", name);
@@ -329,6 +335,80 @@
return sb.substring(0, sb.length() - 1);
}
+ @VisibleForTesting
+ static String formNamespacedAclHeader(Acl acl, DocIdEncoder enc) {
+ if (null == acl) {
+ return "";
+ }
+ if (Acl.EMPTY.equals(acl)) {
+ acl = Acl.FAKE_EMPTY;
+ }
+ Map<String, Object> gsaAcl = makeGsaAclMap(acl, enc);
+ if (null == gsaAcl) {
+ return "";
+ } else {
+ return "acl=" + percentEncode("" + JSONObject.toJSONString(gsaAcl));
+ }
+ }
+
+ /** Gives null if there are no entires, no inherit-from and is LEAF_NODE. */
+ private static Map<String, Object> makeGsaAclMap(Acl acl, DocIdEncoder enc) {
+ boolean didPutSomething = false;
+ Map<String, Object> gsaAcl = new TreeMap<String, Object>();
+ List<Map<String, String>> gsaAclEntries = makeGsaAclEntries(acl);
+ if (!gsaAclEntries.isEmpty()) {
+ gsaAcl.put("entries", gsaAclEntries);
+ didPutSomething = true;
+ }
+ if (null != acl.getInheritFrom()) {
+ URI from = enc.encodeDocId(acl.getInheritFrom());
+ gsaAcl.put("inherit-from", "" + from);
+ didPutSomething = true;
+ }
+ if (acl.getInheritanceType() != Acl.InheritanceType.LEAF_NODE) {
+ String type = acl.getInheritanceType().getCommonForm();
+ gsaAcl.put("inheritance-type", "" + type);
+ didPutSomething = true;
+ }
+ if (didPutSomething) {
+ return gsaAcl;
+ } else {
+ return null;
+ }
+ }
+
+ private static List<Map<String, String>> makeGsaAclEntries(Acl acl) {
+ List<Map<String, String>> princ = new ArrayList<Map<String, String>>();
+ for (Principal p : acl.getPermitGroups()) {
+ princ.add(makeGsaAclEntry("permit", acl, p));
+ }
+ for (Principal p : acl.getDenyGroups()) {
+ princ.add(makeGsaAclEntry("deny", acl, p));
+ }
+ for (Principal p : acl.getPermitUsers()) {
+ princ.add(makeGsaAclEntry("permit", acl, p));
+ }
+ for (Principal p : acl.getDenyUsers()) {
+ princ.add(makeGsaAclEntry("deny", acl, p));
+ }
+ return princ;
+ }
+
+ private static Map<String, String> makeGsaAclEntry(String access,
+ Acl acl, Principal p) {
+ Map<String, String> gsaEntry = new TreeMap<String, String>();
+ gsaEntry.put("access", access);
+ gsaEntry.put("scope", p.isUser()? "user" : "group");
+ gsaEntry.put("name", p.getName());
+ if (!Principal.DEFAULT_NAMESPACE.equals(p.getNamespace())) {
+ gsaEntry.put("namespace", p.getNamespace());
+ }
+ if (!acl.isEverythingCaseSensitive()) {
+ gsaEntry.put("case-sensitivity-type", "" + acl.isEverythingCaseSensitive());
+ }
+ return gsaEntry;
+ }
+
/**
* Format the GSA-specific anchor header value for extra crawl-time anchors.
*/
@@ -480,6 +560,8 @@
private boolean noIndex;
private boolean noFollow;
private boolean noArchive;
+ private boolean crawlOnce;
+ private boolean lock;
public DocumentResponse(HttpExchange ex, DocId docId) {
this.ex = ex;
@@ -600,6 +682,22 @@
}
@Override
+ public void setCrawlOnce(boolean crawlOnlyOnce) {
+ if (state != State.SETUP) {
+ throw new IllegalStateException("Already responded");
+ }
+ this.crawlOnce = crawlOnlyOnce;
+ }
+
+ @Override
+ public void setLock(boolean docLock) {
+ if (state != State.SETUP) {
+ throw new IllegalStateException("Already responded");
+ }
+ this.lock = docLock;
+ }
+
+ @Override
public void setNoFollow(boolean noFollow) {
if (state != State.SETUP) {
throw new IllegalStateException("Already responded");
@@ -686,9 +784,20 @@
// Always specify metadata and ACLs, even when empty, to replace
// previous values.
ex.getResponseHeaders().add("X-Gsa-External-Metadata",
- formMetadataHeader(metadata));
- ex.getResponseHeaders().add("X-Gsa-External-Metadata",
- formAclHeader(acl, docIdEncoder));
+ formMetadataHeader(metadata));
+ if (!sendDocControls) {
+ ex.getResponseHeaders().add("X-Gsa-External-Metadata",
+ formUnqualifiedAclHeader(acl, docIdEncoder));
+ } else {
+ if (crawlOnce) {
+ ex.getResponseHeaders().add("X-Gsa-Doc-Controls", "crawl-once");
+ }
+ if (lock) {
+ ex.getResponseHeaders().add("X-Gsa-Doc-Controls", "lock");
+ }
+ ex.getResponseHeaders().add("X-Gsa-Doc-Controls",
+ formNamespacedAclHeader(acl, docIdEncoder));
+ }
if (!anchorUris.isEmpty()) {
ex.getResponseHeaders().add("X-Gsa-External-Anchor",
formAnchorHeader(anchorUris, anchorTexts));
diff --git a/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java b/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java
index d8b5843..24e9d68 100644
--- a/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java
+++ b/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java
@@ -253,7 +253,9 @@
createTransformPipeline(),
config.getTransformMaxDocumentBytes(),
config.isTransformRequired(),
- config.isServerToUseCompression(), watchdog)));
+ config.isServerToUseCompression(),
+ config.sendDocControlsHeader(),
+ watchdog)));
// Start communicating with other services. As a general rule, by this time
// we want all services we provide to be up and running. However, note that
diff --git a/src/com/google/enterprise/adaptor/Response.java b/src/com/google/enterprise/adaptor/Response.java
index edd13a9..7d61362 100644
--- a/src/com/google/enterprise/adaptor/Response.java
+++ b/src/com/google/enterprise/adaptor/Response.java
@@ -148,4 +148,20 @@
* link in search results
*/
public void setNoArchive(boolean noArchive);
+
+ /**
+ * Whether the GSA should crawl the document just one time and never again.
+ *
+ * @param crawlOnce {@code true} when doc should be crawled only once
+ */
+ public void setCrawlOnce(boolean crawlOnce);
+
+ /**
+ * Prevents the GSA from removing document from index. For example
+ * in cases where GSA is at license limit and is crawling more documents
+ * the GSA is not allowed to remove locked documents.
+ *
+ * @param lock {@code true} when doc is be kept in index
+ */
+ public void setLock(boolean lock);
}
diff --git a/src/com/google/enterprise/adaptor/WrapperAdaptor.java b/src/com/google/enterprise/adaptor/WrapperAdaptor.java
index fba8a82..8e54a27 100644
--- a/src/com/google/enterprise/adaptor/WrapperAdaptor.java
+++ b/src/com/google/enterprise/adaptor/WrapperAdaptor.java
@@ -162,6 +162,16 @@
public void setNoArchive(boolean noArchive) {
response.setNoArchive(noArchive);
}
+
+ @Override
+ public void setCrawlOnce(boolean crawlOnce) {
+ response.setCrawlOnce(crawlOnce);
+ }
+
+ @Override
+ public void setLock(boolean lock) {
+ response.setLock(lock);
+ }
}
/**
@@ -211,6 +221,8 @@
private boolean noIndex;
private boolean noFollow;
private boolean noArchive;
+ private boolean crawlOnce;
+ private boolean lock;
public GetContentsResponse(OutputStream os) {
this.os = os;
@@ -277,6 +289,16 @@
this.noArchive = noArchive;
}
+ @Override
+ public void setCrawlOnce(boolean crawlOnlyOnce) {
+ this.crawlOnce = crawlOnlyOnce;
+ }
+
+ @Override
+ public void setLock(boolean lockDoc) {
+ this.lock = lockDoc;
+ }
+
public String getContentType() {
return contentType;
}
diff --git a/test/com/google/enterprise/adaptor/DocumentHandlerTest.java b/test/com/google/enterprise/adaptor/DocumentHandlerTest.java
index dd3726d..e68f70d 100644
--- a/test/com/google/enterprise/adaptor/DocumentHandlerTest.java
+++ b/test/com/google/enterprise/adaptor/DocumentHandlerTest.java
@@ -54,7 +54,7 @@
thrown.expect(NullPointerException.class);
new DocumentHandler(null, docIdCodec, new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), "localhost", new String[0], handler,
- sessionManager, null, 0, false, false, new MockWatchdog());
+ sessionManager, null, 0, false, false, false, new MockWatchdog());
}
@Test
@@ -62,7 +62,7 @@
thrown.expect(NullPointerException.class);
new DocumentHandler(docIdCodec, null, new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), "localhost", new String[0], handler,
- sessionManager, null, 0, false, false, new MockWatchdog());
+ sessionManager, null, 0, false, false, false, new MockWatchdog());
}
@Test
@@ -70,7 +70,7 @@
thrown.expect(NullPointerException.class);
new DocumentHandler(docIdCodec, docIdCodec, null, new PrivateMockAdaptor(),
"localhost", new String[0], handler, sessionManager, null, 0,
- false, false, new MockWatchdog());
+ false, false, false, new MockWatchdog());
}
@Test
@@ -79,7 +79,7 @@
new DocumentHandler(docIdCodec, docIdCodec,
new Journal(new MockTimeProvider()), null,
"localhost", new String[0], handler, sessionManager, null, 0,
- false, false, new MockWatchdog());
+ false, false, false, new MockWatchdog());
}
@Test
@@ -88,7 +88,7 @@
new DocumentHandler(docIdCodec, docIdCodec,
new Journal(new MockTimeProvider()), new PrivateMockAdaptor(),
"localhost", new String[0], handler, null, null, 0,
- false, false, new MockWatchdog());
+ false, false, false, new MockWatchdog());
}
@Test
@@ -97,7 +97,7 @@
new DocumentHandler(docIdCodec, docIdCodec,
new Journal(new MockTimeProvider()), new PrivateMockAdaptor(),
"localhost", new String[0], handler, sessionManager, null, 0,
- false, false, null);
+ false, false, false, null);
}
@Test
@@ -127,7 +127,7 @@
DocumentHandler handler = new DocumentHandler(docIdCodec, docIdCodec,
new Journal(new MockTimeProvider()), new PrivateMockAdaptor(),
"localhost", new String[0], authnHandler, sessionManager, null, 0,
- false, false, new MockWatchdog());
+ false, false, false, new MockWatchdog());
handler.handle(ex);
assertEquals(1234, ex.getResponseCode());
}
@@ -215,7 +215,7 @@
docIdCodec, docIdCodec, new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), "localhost",
new String[] {remoteIp, " "}, null, sessionManager, null, 0, false,
- false, new MockWatchdog());
+ false, false, new MockWatchdog());
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertArrayEquals(mockAdaptor.documentBytes, ex.getResponseBytes());
@@ -229,7 +229,7 @@
docIdCodec, docIdCodec, new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), "localhost",
new String[0], null, sessionManager, null, 0, false, false,
- new MockWatchdog());
+ false, new MockWatchdog());
MockHttpExchange httpEx = ex;
MockHttpsExchange ex = new MockHttpsExchange(httpEx, new MockSslSession(
new X500Principal("CN=localhost, OU=Unknown, O=Unknown, C=Unknown")));
@@ -246,7 +246,7 @@
docIdCodec, docIdCodec, new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), "localhost",
new String[0], null, sessionManager, null, 0, false, false,
- new MockWatchdog());
+ false, new MockWatchdog());
MockHttpExchange httpEx = ex;
MockHttpsExchange ex = new MockHttpsExchange(httpEx, new MockSslSession(
null));
@@ -260,7 +260,7 @@
docIdCodec, docIdCodec, new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), "localhost",
new String[0], null, sessionManager, null, 0, false, false,
- new MockWatchdog());
+ false, new MockWatchdog());
MockHttpExchange httpEx = ex;
MockHttpsExchange ex = new MockHttpsExchange(httpEx, new MockSslSession(
new KerberosPrincipal("someuser@not-domain")));
@@ -274,7 +274,7 @@
docIdCodec, docIdCodec, new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), "localhost",
new String[0], null, sessionManager, null, 0, false, false,
- new MockWatchdog());
+ false, new MockWatchdog());
MockHttpExchange httpEx = ex;
MockHttpsExchange ex = new MockHttpsExchange(httpEx, new MockSslSession(
new X500Principal("OU=Unknown, O=Unknown, C=Unknown")));
@@ -288,7 +288,7 @@
docIdCodec, docIdCodec, new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), "localhost",
new String[0], null, sessionManager, null, 0, false, false,
- new MockWatchdog());
+ false, new MockWatchdog());
MockHttpExchange httpEx = ex;
MockHttpsExchange ex = new MockHttpsExchange(httpEx, new MockSslSession(
new X500Principal("CN=nottrusted, OU=Unknown, O=Unknown, C=Unknown")));
@@ -303,7 +303,7 @@
docIdCodec, docIdCodec, new Journal(new MockTimeProvider()),
new PrivateMockAdaptor(), remoteIp,
new String[0], null, sessionManager, null, 0, false, false,
- new MockWatchdog());
+ false, new MockWatchdog());
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertArrayEquals(mockAdaptor.documentBytes, ex.getResponseBytes());
@@ -353,7 +353,8 @@
};
handler = new DocumentHandler(docIdCodec, docIdCodec,
new Journal(new MockTimeProvider()), mockAdaptor, "localhost",
- new String[0], null, sessionManager, null, 0, false, false, watchdog);
+ new String[0], null, sessionManager, null, 0, false, false,
+ false, watchdog);
try {
thrown.expect(RuntimeException.class);
handler.handle(ex);
@@ -369,7 +370,8 @@
Watchdog watchdog = new Watchdog(100, executor);
handler = new DocumentHandler(docIdCodec, docIdCodec,
new Journal(new MockTimeProvider()), mockAdaptor, "localhost",
- new String[0], null, sessionManager, null, 0, false, false, watchdog);
+ new String[0], null, sessionManager, null, 0, false, false,
+ false, watchdog);
try {
handler.handle(ex);
} finally {
@@ -408,7 +410,7 @@
DocumentHandler handler = new DocumentHandler(docIdCodec, docIdCodec,
new Journal(new MockTimeProvider()), mockAdaptor, "localhost",
new String[] {remoteIp}, null, sessionManager, transform, 100,
- false, false, new MockWatchdog());
+ false, false, false, new MockWatchdog());
mockAdaptor.documentBytes = new byte[] {1, 2, 3};
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
@@ -449,7 +451,7 @@
DocumentHandler handler = new DocumentHandler(docIdCodec, docIdCodec,
new Journal(new MockTimeProvider()), mockAdaptor, "localhost",
new String[] {remoteIp}, null, sessionManager, transform, 3, false,
- false, new MockWatchdog());
+ false, false, new MockWatchdog());
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertArrayEquals(golden, ex.getResponseBytes());
@@ -480,7 +482,7 @@
DocumentHandler handler = new DocumentHandler(docIdCodec, docIdCodec,
new Journal(new MockTimeProvider()), mockAdaptor, "localhost",
new String[] {remoteIp}, null, sessionManager, transform, 3, true,
- false, new MockWatchdog());
+ false, false, new MockWatchdog());
thrown.expect(IOException.class);
try {
handler.handle(ex);
@@ -844,7 +846,7 @@
DocumentHandler handler = new DocumentHandler(
docIdCodec, docIdCodec, new Journal(new MockTimeProvider()),
adaptor, "localhost", new String[] {remoteIp, "someUnknownHost!@#$"},
- null, sessionManager, null, 0, false, false, new MockWatchdog());
+ null, sessionManager, null, 0, false, false, false, new MockWatchdog());
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertEquals(Arrays.asList("test=ing", "google%3Aaclinheritfrom="
@@ -876,7 +878,7 @@
DocumentHandler handler = new DocumentHandler(
docIdCodec, docIdCodec, new Journal(new MockTimeProvider()),
adaptor, "localhost", new String[0], null,
- sessionManager, null, 0, false, false, new MockWatchdog());
+ sessionManager, null, 0, false, false, false, new MockWatchdog());
handler.handle(ex);
assertEquals(200, ex.getResponseCode());
assertNull(ex.getResponseHeaders().getFirst("X-Gsa-External-Metadata"));
@@ -886,7 +888,7 @@
return new DocumentHandler(docIdCodec, docIdCodec,
new Journal(new MockTimeProvider()), adaptor, "localhost",
new String[0], null, sessionManager, null, 0, false, false,
- new MockWatchdog());
+ false, new MockWatchdog());
}
@Test
@@ -909,10 +911,18 @@
return new UserPrincipal(name);
}
+ private static UserPrincipal U(String name, String ns) {
+ return new UserPrincipal(name, ns);
+ }
+
private static GroupPrincipal G(String name) {
return new GroupPrincipal(name);
}
+ private static GroupPrincipal G(String name, String ns) {
+ return new GroupPrincipal(name, ns);
+ }
+
@Test
public void testFormAclHeader() {
final String golden
@@ -926,7 +936,7 @@
// by percentEncode to give %2520.
+ "google%3Aaclinheritfrom=http%3A%2F%2Flocalhost%2Fsome%2520docId,"
+ "google%3Aaclinheritancetype=parent-overrides";
- String result = DocumentHandler.formAclHeader(new Acl.Builder()
+ String result = DocumentHandler.formUnqualifiedAclHeader(new Acl.Builder()
.setPermitUsers(Arrays.asList(U("pu1"), U("uid=pu2,dc=com")))
.setPermitGroups(Arrays.asList(G("pg1"), G("gid=pg2,dc=com")))
.setDenyUsers(Arrays.asList(U("du1"), U("uid=du2,dc=com")))
@@ -938,14 +948,125 @@
}
@Test
- public void testFormAclHeaderNull() {
- assertEquals("", DocumentHandler.formAclHeader(null, new MockDocIdCodec()));
+ public void testFormUnqualifiedAclHeaderNull() {
+ assertEquals("", DocumentHandler
+ .formUnqualifiedAclHeader(null, new MockDocIdCodec()));
}
@Test
- public void testFormAclHeaderEmpty() {
+ public void testFormUnqualifiedAclHeaderEmpty() {
+ DocIdEncoder enc = new MockDocIdCodec();
assertEquals("google%3Aacldenyusers=google%3AfakeUserToPreventMissingAcl",
- DocumentHandler.formAclHeader(Acl.EMPTY, new MockDocIdCodec()));
+ DocumentHandler.formUnqualifiedAclHeader(Acl.EMPTY, enc));
+ }
+
+ @Test
+ public void testFormNamespacedAclHeaderNull() {
+ assertEquals("", DocumentHandler
+ .formNamespacedAclHeader(null, new MockDocIdCodec()));
+ }
+
+ @Test
+ public void testFormNamespacedAclHeaderEmpty() {
+ DocIdEncoder enc = new MockDocIdCodec();
+ String golden = "acl={\"entries\":[{"
+ + "\"access\":\"deny\""
+ + ","
+ + "\"name\":\"google:fakeUserToPreventMissingAcl\""
+ + ","
+ + "\"scope\":\"user\""
+ + "}]}";
+ String aclHeader = DocumentHandler.formNamespacedAclHeader(Acl.EMPTY, enc);
+ assertTrue(aclHeader.startsWith("acl="));
+ String aclValue = percentDecode(aclHeader.substring("acl=".length()));
+ assertEquals(golden, "acl=" + aclValue);
+ }
+
+ @Test
+ public void testFormNamespacedAclHeaderBusy() {
+ DocIdEncoder enc = new MockDocIdCodec();
+ String golden = "acl={"
+ + "\"entries\":["
+ + "{\"access\":\"permit\",\"case-sensitivity-type\":\"false\","
+ + "\"name\":\"pg1@d.g\",\"scope\":\"group\"},"
+ + "{\"access\":\"permit\",\"case-sensitivity-type\":\"false\","
+ + "\"name\":\"gid=pg2,dc=m\",\"namespace\":\"ns\","
+ + "\"scope\":\"group\"},"
+ + "{\"access\":\"deny\",\"case-sensitivity-type\":\"false\","
+ + "\"name\":\"gid=dg2,dc=com\",\"scope\":\"group\"},"
+ + "{\"access\":\"deny\",\"case-sensitivity-type\":\"false\","
+ + "\"name\":\"dg1@d.g\",\"namespace\":\"ns\","
+ + "\"scope\":\"group\"},"
+ + "{\"access\":\"permit\",\"case-sensitivity-type\":\"false\","
+ + "\"name\":\"uid=pu2,dc=m\",\"scope\":\"user\"},"
+ + "{\"access\":\"permit\",\"case-sensitivity-type\":\"false\","
+ + "\"name\":\"pu1@d.g\",\"namespace\":\"ns\","
+ + "\"scope\":\"user\"},"
+ + "{\"access\":\"deny\",\"case-sensitivity-type\":\"false\","
+ + "\"name\":\"du1@d.g\",\"scope\":\"user\"},"
+ + "{\"access\":\"deny\",\"case-sensitivity-type\":\"false\","
+ + "\"name\":\"uid=du2,dc=com\",\"namespace\":\"ns\","
+ + "\"scope\":\"user\"}"
+ + "],"
+ + "\"inherit-from\":\"http:\\/\\/localhost\\/some%20docId\","
+ + "\"inheritance-type\":\"parent-overrides\""
+ + "}";
+
+ Acl busyAcl = new Acl.Builder()
+ .setPermitUsers(Arrays.asList(U("pu1@d.g", "ns"), U("uid=pu2,dc=m")))
+ .setPermitGroups(Arrays.asList(G("pg1@d.g"), G("gid=pg2,dc=m", "ns")))
+ .setDenyUsers(Arrays.asList(U("du1@d.g"), U("uid=du2,dc=com", "ns")))
+ .setDenyGroups(Arrays.asList(G("dg1@d.g", "ns"), G("gid=dg2,dc=com")))
+ .setInheritFrom(new DocId("some docId"))
+ .setInheritanceType(Acl.InheritanceType.PARENT_OVERRIDES)
+ .setEverythingCaseInsensitive()
+ .build();
+ String aclHeader = DocumentHandler.formNamespacedAclHeader(busyAcl, enc);
+ assertTrue(aclHeader.startsWith("acl="));
+ String aclValue = percentDecode(aclHeader.substring("acl=".length()));
+ assertEquals(golden, "acl=" + aclValue);
+ }
+
+ @Test
+ public void testLockSentIfTrue() throws Exception {
+ MockAdaptor adaptor = new MockAdaptor() {
+ @Override
+ public void getDocContent(Request request, Response response)
+ throws IOException {
+ response.setLock(true);
+ response.getOutputStream();
+ }
+ };
+ String remoteIp = ex.getRemoteAddress().getAddress().getHostAddress();
+ DocumentHandler handler = new DocumentHandler(
+ docIdCodec, docIdCodec, new Journal(new MockTimeProvider()),
+ adaptor, "localhost", new String[] {remoteIp, "someUnknownHost!@#$"},
+ null, sessionManager, null, 0, false, false, true, new MockWatchdog());
+ handler.handle(ex);
+ assertEquals(200, ex.getResponseCode());
+ assertEquals("lock",
+ ex.getResponseHeaders().getFirst("X-Gsa-Doc-Controls"));
+ }
+
+ @Test
+ public void testCrawlOnceSentIfTrue() throws Exception {
+ MockAdaptor adaptor = new MockAdaptor() {
+ @Override
+ public void getDocContent(Request request, Response response)
+ throws IOException {
+ response.setCrawlOnce(true);
+ response.getOutputStream();
+ }
+ };
+ String remoteIp = ex.getRemoteAddress().getAddress().getHostAddress();
+ DocumentHandler handler = new DocumentHandler(
+ docIdCodec, docIdCodec, new Journal(new MockTimeProvider()),
+ adaptor, "localhost", new String[] {remoteIp, "someUnknownHost!@#$"},
+ null, sessionManager, null, 0, false, false, true, new MockWatchdog());
+ handler.handle(ex);
+ assertEquals(200, ex.getResponseCode());
+ assertEquals("crawl-once",
+ ex.getResponseHeaders().getFirst("X-Gsa-Doc-Controls"));
}
@Test
@@ -974,6 +1095,75 @@
}
return Collections.unmodifiableMap(result);
}
- };
+ }
+ /** percentDecode method is defined in this test file */
+ @Test
+ public void testPercentDecoder() {
+ assertEquals("" + ((char)(10)), percentDecode("%0A"));
+ for (char c = 0; c < 256; c++) {
+ String encoded = DocumentHandler.percentEncode("" + c);
+ String decoded = percentDecode(encoded);
+ if (!decoded.equals("" + c)) {
+ int n = c;
+ throw new AssertionError("failed to encode/decode `" + n
+ + "' `" + c + "' `" + encoded + "' `" + decoded + "'");
+ }
+ }
+ String decoded = percentDecode("AaZz09-_.~"
+ + "%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");
+ assertEquals("AaZz09-_.~`=/?+';\\/\"!@#$%^&*()[]{}ë\u0001", decoded);
+ }
+
+ private static int hexToInt(byte b) {
+ if (b >= '0' && b <= '9') {
+ return (byte)(b - '0');
+ } else if (b >= 'a' && b <= 'f') {
+ return (byte)(b - 'a') + 10;
+ } else if (b >= 'A' && b <= 'F') {
+ return (byte)(b - 'A') + 10;
+ } else {
+ throw new IllegalArgumentException("invalid hex byte: " + b);
+ }
+ }
+
+ private static String percentDecode(String encoded) {
+ try {
+ byte bytes[] = encoded.getBytes("ASCII");
+ ByteArrayOutputStream decoded = percentDecode(bytes);
+ return decoded.toString("UTF-8");
+ } catch (UnsupportedEncodingException uee) {
+ throw new RuntimeException(uee);
+ }
+ }
+
+ private static ByteArrayOutputStream percentDecode(byte encoded[]) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int i = 0;
+ while (i < encoded.length) {
+ byte b = encoded[i];
+ if (b == '%') {
+ int iNeeded = i + 2; // need two more bytes
+ if (iNeeded >= encoded.length) {
+ throw new IllegalArgumentException("ends too early");
+ }
+ int highOrder = hexToInt(encoded[i + 1]);
+ int lowOrder = hexToInt(encoded[i + 2]);
+ int byteInInt = (highOrder << 4) | lowOrder;
+ b = (byte) byteInInt; // chops top bytes; could make negative
+ i += 3;
+ } else if ((b >= 'a' && b <= 'z')
+ || (b >= 'A' && b <= 'Z')
+ || (b >= '0' && b <= '9')
+ || b == '-' || b == '_' || b == '.' || b == '~') {
+ // pass through
+ i++;
+ } else {
+ throw new IllegalArgumentException("not percent encoded");
+ }
+ out.write(b);
+ }
+ return out;
+ }
}
diff --git a/test/com/google/enterprise/adaptor/prebuilt/CommandLineAdaptorTest.java b/test/com/google/enterprise/adaptor/prebuilt/CommandLineAdaptorTest.java
index 412382d..39aa96f 100644
--- a/test/com/google/enterprise/adaptor/prebuilt/CommandLineAdaptorTest.java
+++ b/test/com/google/enterprise/adaptor/prebuilt/CommandLineAdaptorTest.java
@@ -294,6 +294,8 @@
private boolean noIndex;
private boolean noFollow;
private boolean noArchive;
+ private boolean crawlOnce;
+ private boolean lock;
public ContentsResponseTestMock(OutputStream os) {
this.os = os;
@@ -361,6 +363,16 @@
this.noArchive = noArchive;
}
+ @Override
+ public void setCrawlOnce(boolean crawlOnlyOnce) {
+ this.crawlOnce = crawlOnlyOnce;
+ }
+
+ @Override
+ public void setLock(boolean lockDoc) {
+ this.lock = lockDoc;
+ }
+
public String getContentType() {
return contentType;
}