1
0
mirror of https://github.com/etesync/android synced 2024-12-23 07:08:16 +00:00

Handle redirections to relative URLs correctly (see #282) + tests; minor GUI change

This commit is contained in:
rfc2822 2014-07-20 14:29:52 +02:00
parent 1cde020619
commit d712238700
10 changed files with 193 additions and 57 deletions

View File

@ -26,7 +26,8 @@
<EditText
android:id="@+id/baseURL"
android:layout_gravity="fill_horizontal"
android:hint="myserver/dav/"
android:hint="my.server.com"
android:imeOptions="flagForceAscii|actionNext"
android:inputType="textUri"
android:layout_width="0dp"
android:scrollHorizontally="true"
@ -52,6 +53,7 @@
android:id="@+id/userName"
android:layout_gravity="fill_horizontal"
android:inputType="textNoSuggestions|textEmailAddress"
android:imeOptions="actionNext"
android:layout_width="0dp"
android:scrollHorizontally="true"
android:scrollbars="horizontal"
@ -65,6 +67,7 @@
android:id="@+id/password"
android:layout_gravity="fill_horizontal"
android:inputType="textPassword"
android:imeOptions="actionGo"
android:layout_width="0dp"
android:scrollHorizontally="true"
android:scrollbars="horizontal"

View File

@ -5,6 +5,7 @@ import java.net.URISyntaxException;
import android.util.Log;
import ch.boye.httpclientandroidlib.Header;
import ch.boye.httpclientandroidlib.HttpHost;
import ch.boye.httpclientandroidlib.HttpRequest;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.ProtocolException;
@ -13,6 +14,7 @@ import ch.boye.httpclientandroidlib.client.RedirectStrategy;
import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
import ch.boye.httpclientandroidlib.client.methods.RequestBuilder;
import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext;
import ch.boye.httpclientandroidlib.client.utils.URIUtils;
import ch.boye.httpclientandroidlib.protocol.HttpContext;
/**
@ -75,13 +77,15 @@ public class DavRedirectStrategy implements RedirectStrategy {
// some servers don't return absolute URLs as required by RFC 2616
if (!location.isAbsolute()) {
Log.w(TAG, "Received invalid redirection with relative URL, repairing");
// determine original URL
final HttpClientContext clientContext = HttpClientContext.adapt(context);
final URI originalURI = new URI(clientContext.getTargetHost() + request.getRequestLine().getUri());
// determine new location relative to original URL
Log.w(TAG, "Received invalid redirection to relative URL, repairing");
URI originalURI = new URI(request.getRequestLine().getUri());
if (!originalURI.isAbsolute()) {
final HttpHost target = HttpClientContext.adapt(context).getTargetHost();
if (target != null)
originalURI = URIUtils.rewriteURI(originalURI, target);
else
return null;
}
location = originalURI.resolve(location);
}
return location;

View File

@ -55,6 +55,10 @@ import ch.boye.httpclientandroidlib.message.BasicLineParser;
import ch.boye.httpclientandroidlib.util.EntityUtils;
/**
* Represents a WebDAV resource (file or collection).
* This class is used for all CalDAV/CardDAV communcation.
*/
@ToString
public class WebDavResource {
private static final String TAG = "davdroid.WebDavResource";
@ -119,9 +123,10 @@ public class WebDavResource {
}
}
private WebDavResource(WebDavResource parent) { // based on existing WebDavResource, reuse settings
WebDavResource(WebDavResource parent) { // copy constructor: based on existing WebDavResource, reuse settings
httpClient = parent.httpClient;
context = parent.context;
location = parent.location;
}
protected WebDavResource(WebDavResource parent, URI uri) {

View File

@ -1,6 +1,6 @@
{"plugins":[
"assets",
"redirect",
"dav-default",
"dav",
"dav-invalid"
]}

View File

@ -1,10 +1,25 @@
var RoboHydraHead = require("robohydra").heads.RoboHydraHead;
require('../simple');
var RoboHydraHead = require('robohydra').heads.RoboHydraHead;
exports.getBodyParts = function(conf) {
return {
heads: [
// well-known URIs
new SimpleResponseHead({
path: '/.well-known/caldav',
status: 302,
headers: { Location: '/dav/' }
}),
new SimpleResponseHead({
path: '/.well-known/caldav',
status: 302,
headers: { Location: '/dav/' }
}),
// generic redirections
new RoboHydraHead({
path: "/redirect/301",
path: '/redirect/301',
handler: function(req,res,next) {
res.statusCode = 301;
var location = req.queryParams['to'] || '/assets/test.random';
@ -15,7 +30,7 @@ exports.getBodyParts = function(conf) {
}
}),
new RoboHydraHead({
path: "/redirect/302",
path: '/redirect/302',
handler: function(req,res,next) {
res.statusCode = 302;
var location = req.queryParams['to'] || '/assets/test.random';
@ -24,7 +39,19 @@ exports.getBodyParts = function(conf) {
}
res.end();
}
}),
// special redirections
new SimpleResponseHead({
path: '/redirect/relative',
status: 302,
headers: { Location: '/new/location' }
}),
new SimpleResponseHead({
path: '/redirect/without-location',
status: 302
})
]
};
};

View File

@ -0,0 +1,28 @@
var roboHydra = require("robohydra"),
roboHydraHeads = roboHydra.heads,
roboHydraHead = roboHydraHeads.RoboHydraHead;
SimpleResponseHead = roboHydraHeads.roboHydraHeadType({
name: 'Simple HTTP Response',
mandatoryProperties: [ 'path', 'status' ],
optionalProperties: [ 'headers', 'body' ],
parentPropBuilder: function() {
var head = this;
return {
path: this.path,
handler: function(req,res,next) {
res.statusCode = head.status;
if (typeof head.headers != 'undefined')
res.headers = head.headers;
if (typeof head.body != 'undefined')
res.write(head.body);
else
res.write();
res.end();
}
}
}
});
module.exports = SimpleResponseHead;

View File

@ -0,0 +1,19 @@
package at.bitfire.davdroid.test;
import java.net.URI;
import java.net.URISyntaxException;
import android.util.Log;
public class Constants {
public static final String ROBOHYDRA_BASE = "http://10.0.0.11:3000/";
public static URI roboHydra;
static {
try {
roboHydra = new URI(ROBOHYDRA_BASE);
} catch(URISyntaxException e) {
Log.wtf("davdroid.test.Constants", "Invalid RoboHydra base URL");
}
}
}

View File

@ -0,0 +1,73 @@
package at.bitfire.davdroid.webdav;
import java.io.IOException;
import android.test.InstrumentationTestCase;
import at.bitfire.davdroid.test.Constants;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.client.methods.HttpOptions;
import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder;
import ch.boye.httpclientandroidlib.protocol.HttpContext;
public class DavRedirectStrategyTest extends InstrumentationTestCase {
CloseableHttpClient httpClient;
DavRedirectStrategy strategy = DavRedirectStrategy.INSTANCE;
@Override
protected void setUp() {
httpClient = HttpClientBuilder.create()
.useSystemProperties()
.disableRedirectHandling()
.build();
}
@Override
protected void tearDown() throws IOException {
httpClient.close();
}
// happy cases
public void testNonRedirection() throws Exception {
HttpUriRequest request = new HttpOptions(Constants.roboHydra);
HttpResponse response = httpClient.execute(request);
assertFalse(strategy.isRedirected(request, response, null));
}
public void testDefaultRedirection() throws Exception {
final String newLocation = "/new-location";
HttpContext context = HttpClientContext.create();
HttpUriRequest request = new HttpOptions(Constants.roboHydra.resolve("redirect/301?to=" + newLocation));
HttpResponse response = httpClient.execute(request, context);
assertTrue(strategy.isRedirected(request, response, context));
HttpUriRequest redirected = strategy.getRedirect(request, response, context);
assertEquals(Constants.roboHydra.resolve(newLocation), redirected.getURI());
}
// error cases
public void testMissingLocation() throws Exception {
HttpContext context = HttpClientContext.create();
HttpUriRequest request = new HttpOptions(Constants.roboHydra.resolve("redirect/without-location"));
HttpResponse response = httpClient.execute(request, context);
assertFalse(strategy.isRedirected(request, response, context));
}
public void testRelativeLocation() throws Exception {
HttpContext context = HttpClientContext.create();
HttpUriRequest request = new HttpOptions(Constants.roboHydra.resolve("redirect/relative"));
HttpResponse response = httpClient.execute(request, context);
assertTrue(strategy.isRedirected(request, response, context));
HttpUriRequest redirected = strategy.getRedirect(request, response, context);
assertEquals(Constants.roboHydra.resolve("/new/location"), redirected.getURI());
}
}

View File

@ -5,7 +5,7 @@
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
******************************************************************************/
package at.bitfire.davdroid.webdav.test;
package at.bitfire.davdroid.webdav;
import java.io.IOException;
import java.io.InputStream;
@ -22,6 +22,7 @@ import org.apache.commons.io.IOUtils;
import android.content.res.AssetManager;
import android.test.InstrumentationTestCase;
import at.bitfire.davdroid.test.Constants;
import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.DavHttpClient;
import at.bitfire.davdroid.webdav.DavMultiget;
@ -37,12 +38,14 @@ import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
// tests require running robohydra!
public class WebDavResourceTest extends InstrumentationTestCase {
static final String ROBOHYDRA_BASE = "http://10.0.0.11:3000/";
static byte[] SAMPLE_CONTENT = new byte[] { 1, 2, 3, 4, 5 };
final static String PATH_SIMPLE_FILE = "collection/new.file";
AssetManager assetMgr;
CloseableHttpClient httpClient;
WebDavResource baseDAV;
WebDavResource simpleFile,
davCollection, davNonExistingFile, davExistingFile,
davInvalid;
@ -53,57 +56,35 @@ public class WebDavResourceTest extends InstrumentationTestCase {
assetMgr = getInstrumentation().getContext().getResources().getAssets();
simpleFile = new WebDavResource(httpClient, new URI(ROBOHYDRA_BASE + "assets/test.random"));
baseDAV = new WebDavResource(httpClient, Constants.roboHydra.resolve("/dav/"));
davCollection = new WebDavResource(httpClient, new URI(ROBOHYDRA_BASE + "dav/"));
simpleFile = new WebDavResource(httpClient, new URI(Constants.ROBOHYDRA_BASE + "assets/test.random"));
davCollection = new WebDavResource(httpClient, new URI(Constants.ROBOHYDRA_BASE + "dav/"));
davNonExistingFile = new WebDavResource(davCollection, "collection/new.file");
davExistingFile = new WebDavResource(davCollection, "collection/existing.file");
davInvalid = new WebDavResource(httpClient, new URI(ROBOHYDRA_BASE + "dav-invalid/"));
davInvalid = new WebDavResource(httpClient, new URI(Constants.ROBOHYDRA_BASE + "dav-invalid/"));
}
@Override
protected void tearDown() throws Exception {
protected void tearDown() throws IOException {
httpClient.close();
}
/* test resource name handling */
public void testGetName() {
// collection names should have a trailing slash
assertEquals("dav", davCollection.getName());
// but non-collection names shouldn't
assertEquals("test.random", simpleFile.getName());
}
public void testTrailingSlash() throws URISyntaxException {
// collections should have a trailing slash
assertEquals("/dav/", davCollection.getLocation().getPath());
// but non-collection members shouldn't
assertEquals("/assets/test.random", simpleFile.getLocation().getPath());
}
/* test feature detection */
public void testOptions() throws URISyntaxException, IOException, HttpException {
String[] davMethods = new String[] { "PROPFIND", "GET", "PUT", "DELETE" },
String[] davMethods = new String[] { "PROPFIND", "GET", "PUT", "DELETE", "REPORT" },
davCapabilities = new String[] { "addressbook", "calendar-access" };
// server without DAV
simpleFile.options();
for (String method : davMethods)
assertFalse(simpleFile.supportsMethod(method));
for (String capability : davCapabilities)
assertFalse(simpleFile.supportsDAV(capability));
// server with DAV
davCollection.options();
WebDavResource capable = new WebDavResource(baseDAV);
capable.options();
for (String davMethod : davMethods)
assert(davCollection.supportsMethod(davMethod));
assert(capable.supportsMethod(davMethod));
for (String capability : davCapabilities)
assert(davCollection.supportsDAV(capability));
assert(capable.supportsDAV(capability));
}
public void testPropfindCurrentUserPrincipal() throws IOException, HttpException, DavException {
@ -156,15 +137,11 @@ public class WebDavResourceTest extends InstrumentationTestCase {
/* test normal HTTP/WebDAV */
public void testRedirections() throws URISyntaxException, IOException, DavException, HttpException {
public void testPropfindRedirection() throws URISyntaxException, IOException, DavException, HttpException {
// PROPFIND redirection
WebDavResource redirection = new WebDavResource(httpClient, new URI(ROBOHYDRA_BASE + "redirect/301?to=/dav/"));
redirection.propfind(Mode.CURRENT_USER_PRINCIPAL);
assertEquals("/dav/", redirection.getLocation().getPath());
// normal GET redirection
redirection = new WebDavResource(httpClient, new URI(ROBOHYDRA_BASE + "redirect/301"));
redirection.get();
WebDavResource redirected = new WebDavResource(baseDAV, "/redirect/301?to=/dav/");
redirected.propfind(Mode.CURRENT_USER_PRINCIPAL);
assertEquals("/dav/", redirected.getLocation().getPath());
}
public void testGet() throws URISyntaxException, IOException, HttpException, DavException {
@ -243,7 +220,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
dav.propfind(HttpPropfind.Mode.MEMBERS_COLLECTIONS);
List<WebDavResource> members = dav.getMembers();
assertEquals(2, members.size());
assertEquals(ROBOHYDRA_BASE + "dav/addressbooks/user%40domain/My%20Contacts%3a1.vcf/", members.get(0).getLocation().toString());
assertEquals(Constants.ROBOHYDRA_BASE + "dav/addressbooks/user%40domain/My%20Contacts%3a1.vcf/", members.get(0).getLocation().toString());
assertEquals("HTTPS://example.com/user%40domain/absolute-url.vcf", members.get(1).getLocation().toString());
}