Set displayUrl on responses
diff --git a/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java b/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
index 7020ab9..f1de9e9 100644
--- a/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
+++ b/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
@@ -573,6 +573,34 @@
       return docId;
     }
 
+    private URI docIdToUri(DocId docId) throws IOException {
+      String url = docId.getUniqueId();
+      // Because SP is silly, the path of the URI is unencoded, but the rest of
+      // the URI is correct. Thus, we split up the path from the host, and then
+      // turn them into URIs separately, and then turn everything into a
+      // properly-escaped string.
+      String[] parts = url.split("/", 4);
+      String host = parts[0] + "/" + parts[1] + "/" + parts[2] + "/";
+      // Host must be properly-encoded already.
+      URI hostUri = URI.create(host);
+      URI pathUri;
+      try {
+        pathUri = new URI(null, null, parts[3], null);
+      } catch (URISyntaxException ex) {
+        throw new IOException(ex);
+      }
+      return hostUri.resolve(pathUri);
+    }
+
+    /**
+     * Handles converting from relative paths to fully qualified URIs and
+     * dealing with SharePoint's lack of encoding paths (spaces in SP are kept
+     * as spaces in URLs, instead of becoming %20).
+     */
+    private URI sharePointUrlToUri(String path) throws IOException {
+      return docIdToUri(encodeDocId(path));
+    }
+
     private void getVirtualServerDocContent(Request request, Response response)
         throws IOException {
       log.entering("SiteDataClient", "getVirtualServerDocContent",
@@ -670,6 +698,7 @@
           .setInheritanceType(Acl.InheritanceType.PARENT_OVERRIDES)
           .build());
 
+      response.setDisplayUrl(URI.create(w.getMetadata().getURL()));
       response.setContentType("text/html");
       HtmlResponseWriter writer = createHtmlResponseWriter(response);
       writer.start(request.getDocId(), ObjectType.SITE,
@@ -741,6 +770,8 @@
           .setInheritanceType(Acl.InheritanceType.PARENT_OVERRIDES)
           .build());
 
+      response.setDisplayUrl(sharePointUrlToUri(
+          l.getMetadata().getDefaultViewUrl()));
       response.setContentType("text/html");
       HtmlResponseWriter writer = createHtmlResponseWriter(response);
       writer.start(request.getDocId(), ObjectType.LIST,
@@ -902,28 +933,14 @@
         throws IOException {
       log.entering("SiteDataClient", "getFileDocContent",
           new Object[] {request, response});
-      String url = request.getDocId().getUniqueId();
-      // Because SP is silly, the path of the URI is unencoded, but the rest of
-      // the URI is correct. Thus, we split up the path from the host, and then
-      // turn them into URIs separately, and then turn everything into a
-      // properly-escaped string.
-      String[] parts = url.split("/", 4);
-      String host = parts[0] + "/" + parts[1] + "/" + parts[2] + "/";
-      // Host must be properly-encoded already.
-      URI hostUri = URI.create(host);
-      URI pathUri;
-      try {
-        pathUri = new URI(null, null, parts[3], null);
-      } catch (URISyntaxException ex) {
-        throw new IOException(ex);
-      }
-      URL finalUrl = hostUri.resolve(pathUri).toURL();
-      FileInfo fi = httpClient.issueGetRequest(finalUrl);
+      URI displayUrl = docIdToUri(request.getDocId());
+      FileInfo fi = httpClient.issueGetRequest(displayUrl.toURL());
       if (fi == null) {
         response.respondNotFound();
         return;
       }
       try {
+        response.setDisplayUrl(displayUrl);
         String contentType = fi.getFirstHeaderWithName("Content-Type");
         if (contentType != null) {
           response.setContentType(contentType);
@@ -1051,6 +1068,23 @@
         if (!folder.startsWith(root)) {
           throw new AssertionError();
         }
+        URI displayPage
+            = sharePointUrlToUri(l.getMetadata().getDefaultViewUrl());
+        if (serverUrl.contains("&") || serverUrl.contains("=")
+            || serverUrl.contains("%")) {
+          throw new AssertionError();
+        }
+        try {
+          // SharePoint percent-encodes '/'s in serverUrl, but accepts them
+          // encoded or unencoded. We leave them unencoded for simplicity of
+          // implementation and to not deal with the possibility of
+          // double-encoding.
+          response.setDisplayUrl(new URI(displayPage.getScheme(),
+              displayPage.getAuthority(), displayPage.getPath(),
+              "RootFolder=" + serverUrl, null));
+        } catch (URISyntaxException ex) {
+          throw new IOException(ex);
+        }
         response.setContentType("text/html");
         HtmlResponseWriter writer = createHtmlResponseWriter(response);
         writer.start(request.getDocId(), ObjectType.FOLDER, null);
@@ -1067,6 +1101,15 @@
         getFileDocContent(request, response);
       } else {
         // Some list item.
+        URI displayPage
+            = sharePointUrlToUri(l.getMetadata().getDefaultViewItemUrl());
+        try {
+          response.setDisplayUrl(new URI(displayPage.getScheme(),
+              displayPage.getAuthority(), displayPage.getPath(),
+              "ID=" + itemId, null));
+        } catch (URISyntaxException ex) {
+          throw new IOException(ex);
+        }
         response.setContentType("text/html");
         HtmlResponseWriter writer = createHtmlResponseWriter(response);
         writer.start(request.getDocId(), ObjectType.LIST_ITEM, title);
diff --git a/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java b/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
index 70112c4..1b93d11 100644
--- a/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
+++ b/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
@@ -45,6 +45,7 @@
 import org.junit.rules.ExpectedException;
 
 import java.io.*;
+import java.net.URI;
 import java.net.URL;
 import java.nio.charset.Charset;
 import java.util.*;
@@ -281,13 +282,14 @@
         .setInheritanceType(Acl.InheritanceType.PARENT_OVERRIDES)
         .setPermitUsers(users(permit)).setPermitGroups(groups(permit)).build(),
         response.getAcl());
+    assertNull(response.getDisplayUrl());
   }
 
   @Test
   public void testGetDocContentSiteCollection() throws IOException {
     final String getContentSiteCollection
         = "<Site>"
-        + "<Metadata URL=\"http://localhost:1\""
+        + "<Metadata URL=\"http://localhost:1/sites/SiteCollection\""
         + " ID=\"{bb3bb2dd-6ea7-471b-a361-6fb67988755c}\""
         + " LastModified=\"2012-06-25 22:29:58Z\" PortalURL=\"\""
         + " UserProfileGUID=\"\""
@@ -325,7 +327,7 @@
         + " SharePoint site: chinese1\" OwnerID=\"3\" OwnerIsUser=\"False\" />"
         + "<Users /></Group></Groups>"
         + "<Web>"
-        + "<Metadata URL=\"http://localhost:1\""
+        + "<Metadata URL=\"http://localhost:1/sites/SiteCollection\""
         + " LastModified=\"2012-06-25 22:29:58Z\""
         + " Created=\"2011-10-14 18:59:25Z\""
         + " ID=\"{b2ea1067-3a54-4ab7-a459-c8ec864b97eb}\" Title=\"chinese1\""
@@ -354,16 +356,18 @@
         + "<permission memberid='6' mask='206292717568' />"
         + "</permissions></ACL>"
         + "<Webs>"
-        + "<Web URL=\"http://localhost:1/somesite\""
+        + "<Web URL=\"http://localhost:1/sites/SiteCollection/somesite\""
         + " ID=\"{ee63e7d0-da23-4553-9f14-359f1cc1bf1c}\""
         + " LastModified=\"2012-06-25 22:29:58Z\" />"
         + "</Webs><Lists>"
         + "<List ID=\"{133fcb96-7e9b-46c9-b5f3-09770a35ad8a}\""
         + " LastModified=\"2012-06-01 22:00:07Z\""
-        + " DefaultViewUrl=\"/Lists/Announcements/AllItems.aspx\" />"
+        + " DefaultViewUrl=\"/sites/SiteCollection/Lists/Announcements/AllItems"
+        +   ".aspx\" />"
         + "<List ID=\"{648f6636-3d90-4565-86b9-2dd7611fc855}\""
         + " LastModified=\"2012-06-01 22:39:22Z\""
-        + " DefaultViewUrl=\"/Shared Documents/Forms/AllItems.aspx\" />"
+        + " DefaultViewUrl=\"/sites/SiteCollection/Shared Documents/Forms/"
+        +   "AllItems.aspx\" />"
         + "</Lists>"
         + "<FPFolder><Folders>"
         + "<Folder URL=\"Lists\" ID=\"{c2c6cfcc-439e-4372-8a7a-87bec657eebf}\""
@@ -375,7 +379,7 @@
         + "</Files></FPFolder></Web></Site>";
     final String getContentSite
         = "<Web>"
-        + "<Metadata URL=\"http://localhost:1\""
+        + "<Metadata URL=\"http://localhost:1/sites/SiteCollection\""
         + " LastModified=\"2012-05-15 19:07:39Z\""
         + " Created=\"2011-10-14 18:59:25Z\""
         + " ID=\"{b2ea1067-3a54-4ab7-a459-c8ec864b97eb}\""
@@ -403,16 +407,18 @@
         + "</permissions>"
         + "</ACL>"
         + "<Webs>"
-        + "<Web URL=\"http://localhost:1/somesite\""
+        + "<Web URL=\"http://localhost:1/sites/SiteCollection/somesite\""
         + " ID=\"{ee63e7d0-da23-4553-9f14-359f1cc1bf1c}\""
         + " LastModified=\"2012-05-15 19:07:39Z\" />"
         + "</Webs><Lists>"
         + "<List ID=\"{133fcb96-7e9b-46c9-b5f3-09770a35ad8a}\""
         + " LastModified=\"2012-05-15 18:21:38Z\""
-        + " DefaultViewUrl=\"/Lists/Announcements/AllItems.aspx\" />"
+        + " DefaultViewUrl=\"/sites/SiteCollection/Lists/Announcements/AllItems"
+        +   ".aspx\" />"
         + "<List ID=\"{648f6636-3d90-4565-86b9-2dd7611fc855}\""
         + " LastModified=\"2012-05-15 19:07:40Z\""
-        + " DefaultViewUrl=\"/Shared Documents/Forms/AllItems.aspx\" />"
+        + " DefaultViewUrl=\"/sites/SiteCollection/Shared Documents/Forms/"
+        +   "AllItems.aspx\" />"
         + "</Lists>"
         + "<FPFolder><Folders>"
         + "<Folder URL=\"Lists\" ID=\"{c2c6cfcc-439e-4372-8a7a-87bec657eebf}\""
@@ -495,13 +501,15 @@
         + "<html><head><title>Site chinese1</title></head>"
         + "<body><h1>Site chinese1</h1>"
         + "<p>Sites</p>"
-        + "<ul><li><a href=\"../somesite\">"
-        + "http://localhost:1/somesite</a></li></ul>"
+        + "<ul><li><a href=\"SiteCollection/somesite\">"
+        + "http://localhost:1/sites/SiteCollection/somesite</a></li></ul>"
         + "<p>Lists</p>"
-        + "<ul><li><a href=\"../Lists/Announcements/AllItems.aspx\">"
-        + "/Lists/Announcements/AllItems.aspx</a></li>"
-        + "<li><a href=\"../Shared%20Documents/Forms/AllItems.aspx\">"
-        + "/Shared Documents/Forms/AllItems.aspx</a>"
+        + "<ul><li><a href=\"SiteCollection/Lists/Announcements/"
+        +   "AllItems.aspx\">"
+        + "/sites/SiteCollection/Lists/Announcements/AllItems.aspx</a></li>"
+        + "<li><a href=\"SiteCollection/Shared%20Documents/Forms/"
+        +   "AllItems.aspx\">"
+        + "/sites/SiteCollection/Shared Documents/Forms/AllItems.aspx</a>"
         + "</li></ul>"
         + "<p>Folders</p>"
         + "<ul><li><a href=\"SiteCollection/Lists\">Lists</a></li></ul>"
@@ -518,6 +526,8 @@
             "chinese1 Visitors"))
         .setPermitUsers(users("GDC-PSL\\spuser1")).build(),
         response.getAcl());
+    assertEquals(URI.create("http://localhost:1/sites/SiteCollection"),
+        response.getDisplayUrl());
   }
 
   @Test
@@ -977,6 +987,8 @@
         .setPermitGroups(groups("SiteCollection Members",
             "SiteCollection Owners", "SiteCollection Visitors")).build(),
         response.getAcl());
+    assertEquals(URI.create("http://localhost:1/sites/SiteCollection/Lists/"
+          + "Custom%20List/AllItems.aspx"), response.getDisplayUrl());
   }
 
   @Test
@@ -1319,6 +1331,9 @@
             "http://localhost:1/Lists/Custom List/Test Folder/2_.000"))
         .build(),
         response.getAcl());
+    assertEquals(URI.create(
+          "http://localhost:1/Lists/Custom%20List/Attachments/2/1046000.pdf"),
+        response.getDisplayUrl());
   }
 
   @Test
@@ -2009,6 +2024,9 @@
             + "Lists/Custom List/Test Folder"))
         .setInheritanceType(Acl.InheritanceType.PARENT_OVERRIDES).build(),
         response.getAcl());
+    assertEquals(URI.create("http://localhost:1/sites/SiteCollection/Lists/"
+          + "Custom%20List/DispForm.aspx?ID=2"),
+        response.getDisplayUrl());
   }
 
   @Test
@@ -3191,6 +3209,10 @@
             "SiteCollection Owners", "SiteCollection Visitors"))
         .setPermitUsers(users("GDC-PSL\\administrator")).build(),
         response.getAcl());
+    assertEquals(URI.create("http://localhost:1/sites/SiteCollection/Lists/"
+          + "Custom%20List/AllItems.aspx?RootFolder=/sites/SiteCollection/"
+          + "Lists/Custom%20List/Test%20Folder"),
+        response.getDisplayUrl());
   }
 
   @Test