Improve attachment handling using redirect on list root folder
Code Review : https://codereview.appspot.com/72720043/
diff --git a/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java b/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
index 7f9955e..c71092e 100644
--- a/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
+++ b/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
@@ -2135,11 +2135,33 @@
         log.exiting("SiteAdaptor", "getAttachmentDocContent", false);
         return false;
       }
-      Holder<String> listIdHolder = new Holder<String>();
-      // TODO(ejona): Find a more reliable way to determine the list's id.
-      // Hope the list's default view is AllItems.aspx.
-      boolean result = siteDataClient.getUrlSegments(
-          listBase + "/AllItems.aspx", listIdHolder, null);
+      String listRedirectLocation = httpClient.getRedirectLocation(
+          spUrlToUri(listBase).toURL(),
+          authenticationHandler.getAuthenticationCookies());
+      // if listRedirectLocation is null, use listBase as list url. This is
+      // possible if list has no views defined.
+      // if listRedirectLocation is not null, it should begin with listBase
+      // to be considered as valid list location else use listBase as listUrl. 
+      String listUrl 
+          = listRedirectLocation == null ? listBase
+          : listRedirectLocation.startsWith(listBase) 
+          ? listRedirectLocation : listBase;
+
+      log.log(Level.FINER, "List url {0}", listUrl);
+      Holder<String> listIdHolder = new Holder<String>();      
+      boolean result = siteDataClient.getUrlSegments(listUrl,
+          listIdHolder, null);
+      // There is a possiblity that default view url for a list is empty but
+      // list contains additional non default view.
+      // In this case, SharePoint will redirect to non default view url.
+      // getUrlSegments call fails for a non default view url.
+      // So lets try with listBase for getUrlSegments.
+      if (!result && !listUrl.equals(listBase)) {
+        result = siteDataClient.getUrlSegments(listBase, listIdHolder, null);
+      }
+      // For valid lists, one of the getUrlSegments calls above should work on
+      // SP2010 and SP2013. It can still fail for SP2007. So lets try with
+      // ItemId url. This will fail if parent item is inside a folder.
       if (!result) {
         log.fine("Could not get list id from list url");
         // AllItems.aspx may not be the default view, so hope that list items
@@ -2498,6 +2520,9 @@
      */
     public FileInfo issueGetRequest(URL url, List<String> authenticationCookies)
         throws IOException;
+    
+    public String getRedirectLocation(URL url,
+        List<String> authenticationCookies) throws IOException;
   }
 
   static class HttpClientImpl implements HttpClient {
@@ -2511,7 +2536,7 @@
         throw new IOException(ex);
       }
       HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-     
+      // TODO :Close connection for non 200 responses.
       if (authenticationCookies.isEmpty()) {
         conn.addRequestProperty("X-FORMS_BASED_AUTH_ACCEPTED", "f");
       } else {
@@ -2560,6 +2585,42 @@
       return new FileInfo.Builder(conn.getInputStream()).setHeaders(headers)
           .build();
     }
+
+    @Override
+    public String getRedirectLocation(URL url,
+        List<String> authenticationCookies) throws IOException {
+
+      // Handle Unicode. Java does not properly encode the GET.
+      try {
+        url = new URL(url.toURI().toASCIIString());
+      } catch (URISyntaxException ex) {
+        throw new IOException(ex);
+      }
+      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+      try {
+        if (authenticationCookies.isEmpty()) {
+          conn.addRequestProperty("X-FORMS_BASED_AUTH_ACCEPTED", "f");
+        } else {
+          for (String cookie : authenticationCookies) {
+            conn.addRequestProperty("Cookie", cookie);
+          }
+        }
+        conn.setDoInput(true);
+        conn.setDoOutput(false);
+        conn.setInstanceFollowRedirects(false);       
+        if (conn.getResponseCode() != HttpURLConnection.HTTP_MOVED_TEMP) {
+          log.log(Level.WARNING,
+              "Received response code {0} instead of 302 for URL {1}",
+              new Object[]{conn.getResponseCode(), url});
+          return null;        
+        }
+        return conn.getHeaderField("Location");
+      } finally {
+        InputStream inputStream = conn.getResponseCode() >= 400
+            ? conn.getErrorStream() : conn.getInputStream();
+        inputStream.close();
+      }
+    }
   }
 
   @VisibleForTesting
diff --git a/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java b/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
index f786d17..586134b 100644
--- a/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
+++ b/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
@@ -911,6 +911,17 @@
             "Last-Modified", "Tue, 01 May 2012 22:14:41 GMT");
         return new FileInfo.Builder(contents).setHeaders(headers).build();
       }
+
+      @Override
+      public String getRedirectLocation(URL url,
+          List<String> authenticationCookies) throws IOException {
+        assertEquals(
+            "http://localhost:1/sites/SiteCollection/Lists/Custom%20List",
+            url.toString());
+
+        return "http://localhost:1/sites/SiteCollection/Lists/Custom List"
+            + "/AllItems.aspx";
+      }
     }, executorFactory);
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -960,6 +971,17 @@
             "Content-Type", "application/vnd.ms-excel.12");
         return new FileInfo.Builder(contents).setHeaders(headers).build();
       }
+
+      @Override
+      public String getRedirectLocation(URL url,
+          List<String> authenticationCookies) throws IOException {
+        assertEquals(
+            "http://localhost:1/sites/SiteCollection/Lists/Custom%20List",
+            url.toString());
+        
+        return "http://localhost:1/sites/SiteCollection/Lists/Custom List"
+            + "/AllItems.aspx";
+      }
     }, executorFactory);
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -1837,6 +1859,12 @@
         List<String> authenticationCookies) {
       throw new UnsupportedOperationException();
     }
+
+    @Override
+    public String getRedirectLocation(URL url,
+        List<String> authenticationCookies) throws IOException {
+      throw new UnsupportedOperationException();
+    }
   }
   
   private abstract static class DelegatingPeopleSoap implements PeopleSoap {