Handle relative URIs with ":" in path names correctly

pull/2/head
R Hirner 10 years ago
parent e65a1b5f16
commit 142e229ff3

@ -12,9 +12,6 @@ android {
buildTypes {
debug {
//minifyEnabled false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
release {
minifyEnabled true

@ -32,4 +32,3 @@
# DAVdroid
-keep class at.bitfire.davdroid.** { *; } # all DAVdroid code is required

@ -35,7 +35,7 @@ public class URLUtilsTest extends TestCase {
assertEquals("/test/", URIUtils.ensureTrailingSlash("/test"));
assertEquals("/test/", URIUtils.ensureTrailingSlash("/test/"));
String withoutSlash = "http://www.test.at/dav/collection",
String withoutSlash = "http://www.test.example/dav/collection",
withSlash = withoutSlash + "/";
assertEquals(new URI(withSlash), URIUtils.ensureTrailingSlash(new URI(withoutSlash)));
assertEquals(new URI(withSlash), URIUtils.ensureTrailingSlash(new URI(withSlash)));
@ -44,18 +44,24 @@ public class URLUtilsTest extends TestCase {
public void testParseURI() throws Exception {
// don't escape valid characters
String validPath = "/;:@&=$-_.+!*'(),";
assertEquals(new URI("https://www.test.at:123" + validPath), URIUtils.parseURI("https://www.test.at:123" + validPath));
assertEquals(new URI(validPath), URIUtils.parseURI(validPath));
assertEquals(new URI("https://www.test.example:123" + validPath), URIUtils.parseURI("https://www.test.example:123" + validPath, false));
assertEquals(new URI(validPath), URIUtils.parseURI(validPath, true));
// keep literal IPv6 addresses (only in host name)
assertEquals(new URI("https://[1:2::1]/"), URIUtils.parseURI("https://[1:2::1]/"));
assertEquals(new URI("https://[1:2::1]/"), URIUtils.parseURI("https://[1:2::1]/", false));
// ~ as home directory
assertEquals(new URI("http://www.test.at/~user1/"), URIUtils.parseURI("http://www.test.at/~user1/"));
assertEquals(new URI("http://www.test.at/~user1/"), URIUtils.parseURI("http://www.test.at/%7euser1/"));
// "~" as home directory
assertEquals(new URI("http://www.test.example/~user1/"), URIUtils.parseURI("http://www.test.example/~user1/", false));
assertEquals(new URI("/~user1/"), URIUtils.parseURI("/%7euser1/", true));
// @ in directory name
assertEquals(new URI("http://www.test.at/user@server.com/"), URIUtils.parseURI("http://www.test.at/user@server.com/"));
assertEquals(new URI("http://www.test.at/user@server.com/"), URIUtils.parseURI("http://www.test.at/user%40server.com/"));
// "@" in directory name
assertEquals(new URI("http://www.test.example/user@server.com/"), URIUtils.parseURI("http://www.test.example/user@server.com/", false));
assertEquals(new URI("/user@server.com/"), URIUtils.parseURI("/user%40server.com/", true));
assertEquals(new URI("user@server.com"), URIUtils.parseURI("user%40server.com", true));
// ":" in path names
assertEquals(new URI("http://www.test.example/my:cal.ics"), URIUtils.parseURI("http://www.test.example/my:cal.ics", false));
assertEquals(new URI("/my:cal.ics"), URIUtils.parseURI("/my%3Acal.ics", true));
assertEquals(new URI(null, null, "my:cal.ics", null, null), URIUtils.parseURI("my%3Acal.ics", true));
}
}

@ -36,9 +36,8 @@ public class WebDavResourceTest extends InstrumentationTestCase {
CloseableHttpClient httpClient;
WebDavResource baseDAV;
WebDavResource simpleFile,
davCollection, davNonExistingFile, davExistingFile,
davInvalid;
WebDavResource davAssets,
davCollection, davNonExistingFile, davExistingFile;
@Override
protected void setUp() throws Exception {
@ -47,14 +46,11 @@ public class WebDavResourceTest extends InstrumentationTestCase {
assetMgr = getInstrumentation().getContext().getResources().getAssets();
baseDAV = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("/dav/"));
simpleFile = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("assets/test.random"));
davAssets = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("assets/"));
davCollection = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("dav/"));
davNonExistingFile = new WebDavResource(davCollection, "collection/new.file");
davExistingFile = new WebDavResource(davCollection, "collection/existing.file");
davInvalid = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("dav-invalid/"));
}
@Override
@ -80,7 +76,8 @@ public class WebDavResourceTest extends InstrumentationTestCase {
public void testPropfindCurrentUserPrincipal() throws Exception {
davCollection.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL);
assertEquals("/dav/principals/users/test", davCollection.getCurrentUserPrincipal());
WebDavResource simpleFile = new WebDavResource(davAssets, "test.random");
try {
simpleFile.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL);
fail();
@ -152,6 +149,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
}
public void testGet() throws Exception {
WebDavResource simpleFile = new WebDavResource(davAssets, "test.random");
simpleFile.get("*/*");
@Cleanup InputStream is = assetMgr.open("test.random", AssetManager.ACCESS_STREAMING);
byte[] expected = IOUtils.toByteArray(is);
@ -163,7 +161,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
boolean sniWorking = false;
try {
file.get("*/*");
file.get("* /*");
sniWorking = true;
} catch (SSLPeerUnverifiedException e) {
}
@ -222,12 +220,14 @@ public class WebDavResourceTest extends InstrumentationTestCase {
/* special test */
public void testInvalidURLs() throws Exception {
WebDavResource dav = new WebDavResource(davInvalid, "addressbooks/%7euser1/");
dav.propfind(HttpPropfind.Mode.CARDDAV_COLLECTIONS);
List<WebDavResource> members = dav.getMembers();
assertEquals(1, members.size());
assertEquals(TestConstants.ROBOHYDRA_BASE + "dav-invalid/addressbooks/~user1/My%20Contacts:1.vcf/", members.get(0).getLocation().toASCIIString());
public void testGetSpecialURLs() throws Exception {
WebDavResource dav = new WebDavResource(davAssets, "member-with:colon.vcf");
try {
dav.get("*/*");
fail();
} catch(NotFoundException e) {
assertTrue(true);
}
}
}

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

@ -1,36 +0,0 @@
var roboHydraHeadDAV = require("../headdav");
exports.getBodyParts = function(conf) {
return {
heads: [
/* address-book home set */
new RoboHydraHeadDAV({
path: "/dav-invalid/addressbooks/~user1/",
handler: function(req,res,next) {
if (req.method == "PROPFIND" && req.rawBody.toString().match(/addressbook-description/)) {
res.statusCode = 207;
res.write('\<?xml version="1.0" encoding="utf-8" ?>\
<multistatus xmlns="DAV:">\
<response>\
<href>/dav-invalid/addressbooks/~user1/My%20Contacts:1.vcf/</href>\
<propstat>\
<prop xmlns:CARD="urn:ietf:params:xml:ns:carddav">\
<resourcetype>\
<collection/>\
<CARD:addressbook/>\
</resourcetype>\
<CARD:addressbook-description>\
Address Book with dubious characters in path\
</CARD:addressbook-description>\
</prop>\
<status>HTTP/1.1 200 OK</status>\
</propstat>\
</response>\
</multistatus>\
');
}
}
})
]
};
};

@ -39,15 +39,32 @@ public class URIUtils {
/**
* Parse a received absolute/relative URL and generate a normalized URI that can be compared.
* @param original URI to be parsed, may be absolute or relative
* @return normalized URI
* @param original URI to be parsed, may be absolute or relative
* @param mustBePath true if it's known that original is a path (may contain ":") and not an URI, i.e. ":" is not the scheme separator
* @return normalized URI
* @throws URISyntaxException
*/
public static URI parseURI(String original) throws URISyntaxException {
URI raw = URI.create(original);
URI uri = new URI(raw.getScheme(), raw.getAuthority(), raw.getPath(), raw.getQuery(), raw.getFragment());
Log.v(TAG, "Normalized URL " + original + " -> " + uri.toASCIIString());
return uri;
public static URI parseURI(String original, boolean mustBePath) throws URISyntaxException {
if (mustBePath) {
// may contain ":"
// case 1: "my:file" won't be parsed by URI correctly because it would consider "my" as URI scheme
// case 2: "path/my:file" will be parsed by URI correctly
// case 3: "my:path/file" won't be parsed by URI correctly because it would consider "my" as URI scheme
int idxSlash = original.indexOf('/'),
idxColon = original.indexOf(':');
if (idxColon != -1) {
// colon present
if ((idxSlash != -1) && idxSlash < idxColon) // There's a slash, and it's before the colon → everything OK
;
else // No slash before the colon; we have to put it there
original = "./" + original;
}
}
URI uri = new URI(original);
URI normalized = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), uri.getQuery(), uri.getFragment());
Log.v(TAG, "Normalized URL " + original + " -> " + normalized.toASCIIString());
return normalized;
}
}

@ -52,7 +52,7 @@ public abstract class RemoteCollection<T extends Resource> {
public RemoteCollection(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
this.httpClient = httpClient;
collection = new WebDavResource(httpClient, URIUtils.parseURI(baseURL), user, password, preemptiveAuth);
collection = new WebDavResource(httpClient, URIUtils.parseURI(baseURL, false), user, password, preemptiveAuth);
}

@ -31,7 +31,7 @@ import at.bitfire.davdroid.URIUtils;
*/
public class DavRedirectStrategy implements RedirectStrategy {
private final static String TAG = "davdroid.DavRedirectStrategy";
final static DavRedirectStrategy INSTANCE = new DavRedirectStrategy();
public final static DavRedirectStrategy INSTANCE = new DavRedirectStrategy();
protected final static String REDIRECTABLE_METHODS[] = {
"OPTIONS", "GET", "PUT", "DELETE"
@ -82,12 +82,12 @@ public class DavRedirectStrategy implements RedirectStrategy {
return null;
}
try {
URI location = URIUtils.parseURI(locationHdr.getValue());
URI location = URIUtils.parseURI(locationHdr.getValue(), false);
// some servers don't return absolute URLs as required by RFC 2616
if (!location.isAbsolute()) {
Log.w(TAG, "Received invalid redirection to relative URL, repairing");
URI originalURI = URIUtils.parseURI(request.getRequestLine().getUri());
URI originalURI = URIUtils.parseURI(request.getRequestLine().getUri(), false);
if (!originalURI.isAbsolute()) {
final HttpHost target = HttpClientContext.adapt(context).getTargetHost();
if (target != null)

@ -36,7 +36,7 @@ import javax.net.ssl.SSLSocket;
public class TlsSniSocketFactory implements LayeredConnectionSocketFactory {
private static final String TAG = "davdroid.SNISocketFactory";
final static TlsSniSocketFactory INSTANCE = new TlsSniSocketFactory();
public final static TlsSniSocketFactory INSTANCE = new TlsSniSocketFactory();
private final static SSLCertificateSocketFactory sslSocketFactory =
(SSLCertificateSocketFactory)SSLCertificateSocketFactory.getDefault(0);

@ -123,7 +123,7 @@ public class WebDavResource {
}
}
WebDavResource(WebDavResource parent) { // copy constructor: based on existing WebDavResource, reuse settings
public WebDavResource(WebDavResource parent) { // copy constructor: based on existing WebDavResource, reuse settings
httpClient = parent.httpClient;
context = parent.context;
location = parent.location;
@ -133,10 +133,16 @@ public class WebDavResource {
this(parent);
location = parent.location.resolve(url);
}
/**
* Creates a WebDavResource representing a member of the parent collection.
* @param parent Parent collection
* @param member File name of the member. This may contain ":" without leading "./"!
* @throws URISyntaxException
*/
public WebDavResource(WebDavResource parent, String member) throws URISyntaxException {
this(parent);
location = parent.location.resolve(URIUtils.parseURI(member));
location = parent.location.resolve(URIUtils.parseURI(member, true));
}
public WebDavResource(WebDavResource parent, String member, String ETag) throws URISyntaxException {
@ -454,7 +460,7 @@ public class WebDavResource {
for (DavResponse singleResponse : multiStatus.response) {
URI href;
try {
href = location.resolve(URIUtils.parseURI(singleResponse.getHref().href));
href = location.resolve(URIUtils.parseURI(singleResponse.getHref().href, false));
} catch(Exception ex) {
Log.w(TAG, "Ignoring illegal member URI in multi-status response", ex);
continue;

Loading…
Cancel
Save