...
 
Commits (2)
buildscript {
ext.kotlin_version = '1.2.21'
ext.kotlin_version = '1.2.30'
ext.dokka_version = '0.9.15'
repositories {
......@@ -52,12 +52,12 @@ android {
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile 'com.squareup.okhttp3:okhttp:3.9.1'
compile 'com.squareup.okhttp3:okhttp:3.10.0'
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.9.1'
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.10.0'
androidTestCompile 'com.android.support.test:runner:1.0.1'
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
testCompile 'junit:junit:4.12'
testCompile 'com.squareup.okhttp3:mockwebserver:3.9.1'
testCompile 'com.squareup.okhttp3:mockwebserver:3.10.0'
}
......@@ -40,8 +40,8 @@ class BasicDigestAuthHandler(
private val HEADER_AUTHORIZATION = "Authorization"
// cached digest parameters
@JvmField var clientNonce = h(UUID.randomUUID().toString())
@JvmField var nonceCount = AtomicInteger(1)
var clientNonce = h(UUID.randomUUID().toString())
var nonceCount = AtomicInteger(1)
fun quotedString(s: String) = "\"" + s.replace("\"", "\\\"") + "\""
fun h(data: String) = ByteString.of(ByteBuffer.wrap(data.toByteArray())).md5().hex()!!
......
......@@ -12,7 +12,6 @@ import java.util.logging.Logger
object Constants {
@JvmField
var log = Logger.getLogger("dav4android")!!
}
\ No newline at end of file
......@@ -22,10 +22,7 @@ class DavAddressBook @JvmOverloads constructor(
): DavCollection(httpClient, location, log) {
companion object {
@JvmField
val MIME_VCARD3_UTF8 = MediaType.parse("text/vcard;charset=utf-8")
@JvmField
val MIME_VCARD4 = MediaType.parse("text/vcard;version=4.0")
}
......@@ -36,7 +33,6 @@ class DavAddressBook @JvmOverloads constructor(
* @throws HttpException on HTTP error
* @throws DavException on DAV error
*/
@Throws(IOException::class, HttpException::class, DavException::class)
fun addressbookQuery() {
/* <!ELEMENT addressbook-query ((DAV:allprop |
DAV:propname |
......@@ -78,7 +74,6 @@ class DavAddressBook @JvmOverloads constructor(
* @throws HttpException on HTTP error
* @throws DavException on DAV error
*/
@Throws(IOException::class, HttpException::class, DavException::class)
fun multiget(urls: List<HttpUrl>, vCard4: Boolean) {
/* <!ELEMENT addressbook-multiget ((DAV:allprop |
DAV:propname |
......
......@@ -24,7 +24,6 @@ class DavCalendar @JvmOverloads constructor(
): DavCollection(httpClient, location, log) {
companion object {
@JvmField
val MIME_ICALENDAR = MediaType.parse("text/calendar")
val MIME_ICALENDAR_UTF8 = MediaType.parse("text/calendar;charset=utf-8")
}
......@@ -38,7 +37,6 @@ class DavCalendar @JvmOverloads constructor(
* @throws HttpException on HTTP error
* @throws DavException on DAV error
*/
@Throws(IOException::class, HttpException::class, DavException::class)
fun calendarQuery(component: String, start: Date?, end: Date?) {
/* <!ELEMENT calendar-query ((DAV:allprop |
DAV:propname |
......@@ -100,7 +98,6 @@ class DavCalendar @JvmOverloads constructor(
* @throws HttpException on HTTP error
* @throws DavException on DAV error
*/
@Throws(IOException::class, HttpException::class, DavException::class)
fun multiget(urls: List<HttpUrl>) {
/* <!ELEMENT calendar-multiget ((DAV:allprop |
DAV:propname |
......
......@@ -8,6 +8,8 @@
package at.bitfire.dav4android
import at.bitfire.dav4android.exception.DavException
import at.bitfire.dav4android.exception.HttpException
import at.bitfire.dav4android.property.SyncToken
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
......@@ -16,13 +18,13 @@ import okhttp3.RequestBody
import java.io.StringWriter
import java.util.logging.Logger
open class DavCollection(
open class DavCollection @JvmOverloads constructor(
httpClient: OkHttpClient,
location: HttpUrl,
log: Logger = Constants.log
): DavResource(httpClient, location, log) {
/**
/**
* Sends a REPORT sync-collection request. If a sync-token is returned, it will be made
* available in [properties].
*
......@@ -30,6 +32,10 @@ open class DavCollection(
* @param infiniteDepth sync-level to be sent with the request: false = "1", true = "infinite"
* @param limit maximum number of results (may cause truncation)
* @param properties WebDAV properties to be requested
* @throws java.io.IOException on I/O error
* @throws HttpException on HTTP error
* @throws DavException on DAV error
*/
fun reportChanges(syncToken: String?, infiniteDepth: Boolean, limit: Int?, vararg properties: Property.Name) {
/* <!ELEMENT sync-collection (sync-token, sync-level, limit?, prop)>
......
......@@ -429,11 +429,11 @@ open class DavResource @JvmOverloads constructor(
"prop" ->
prop = parseMultiStatus_Prop()
"status" ->
try {
status = StatusLine.parse(parser.nextText())
status = try {
StatusLine.parse(parser.nextText())
} catch(e: ProtocolException) {
log.warning("Invalid status line, treating as 500 Server Error")
status = StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line")
StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line")
}
}
eventType = parser.next()
......@@ -485,11 +485,11 @@ open class DavResource @JvmOverloads constructor(
href = location.resolve(sHref)
}
"status" ->
try {
status = StatusLine.parse(parser.nextText())
status = try {
StatusLine.parse(parser.nextText())
} catch(e: ProtocolException) {
log.warning("Invalid status line, treating as 500 Server Error")
status = StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line")
StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line")
}
"propstat" ->
parseMultiStatus_PropStat()?.let { properties.merge(it, false) }
......
......@@ -17,13 +17,11 @@ object HttpUtils {
private val authSchemeWithParam = Pattern.compile("^([^ \"]+) +(.*)$")
@JvmStatic
fun listHeader(response: Response, name: String): Array<String> {
val value = response.headers(name).joinToString(",")
return value.split(',').filter { it.isNotEmpty() }.toTypedArray()
}
@JvmStatic
fun parseWwwAuthenticate(wwwAuths: List<String>): List<AuthScheme> {
/* WWW-Authenticate = "WWW-Authenticate" ":" 1#challenge
......
......@@ -11,7 +11,7 @@ package at.bitfire.dav4android
/**
* A WebDAV property.
*
* Every [Property] must define a static field called NAME of type Property.Name,
* Every [Property] must define a static field (use @JvmStatic) called NAME of type [Property.Name],
* which will be accessed by reflection.
*/
interface Property {
......@@ -21,11 +21,11 @@ interface Property {
val name: String
) {
override fun equals(o: Any?): Boolean {
return if (o is Name)
namespace == o.namespace && name == o.name
override fun equals(other: Any?): Boolean {
return if (other is Name)
namespace == other.namespace && name == other.name
else
super.equals(o)
super.equals(other)
}
override fun hashCode() = namespace.hashCode() xor name.hashCode()
......
......@@ -33,12 +33,12 @@ class PropertyCollection {
if (!properties.isInitialized())
return null
try {
return try {
val name = clazz.getDeclaredField("NAME").get(null) as Property.Name
return properties.value[name] as? T
properties.value[name] as? T
} catch (e: NoSuchFieldException) {
Constants.log.severe("$clazz does not have a static NAME field")
return null
null
}
}
......@@ -66,15 +66,14 @@ class PropertyCollection {
/**
* Merges another #{@link PropertyCollection} into #{@link #properties}.
* Merges another [PropertyCollection] into [properties].
* Existing properties will be overwritten.
*
* @param another property collection to take the properties from
* @param removeNullValues Indicates how "another" properties with null values should be treated.
* <ul>
* <li>#{@code true}: If the "another" property value is #{@code null}, the property will be removed in #{@link #properties}.</li>
* <li>#{@code false}: If the "another" property value is #{@code null}, the property in #{@link #properties} will be set to null, too,
but only if it doesn't exist yet. This means values in #{@link #properties} will never be overwritten by #{@code null}.</li>
* </ul>
* - true: If the "another" property value is null, the property will be removed in [properties].
* - false: If the "another" property value is null, the property in [properties] will be set to null, too,
* but only if it doesn't exist yet. This means values in [properties] will never be overwritten by null.
*/
fun merge(another: PropertyCollection, removeNullValues: Boolean) {
val properties = another.getMap()
......
......@@ -20,7 +20,10 @@ interface PropertyFactory {
*/
fun getName(): Property.Name
@Throws(IOException::class, XmlPullParserException::class)
/**
* Parses XML of a property and returns its data class.
* @throws XmlPullParserException in case of parsing errors
*/
fun create(parser: XmlPullParser): Property?
}
......@@ -10,19 +10,10 @@ package at.bitfire.dav4android
object QuotedStringUtils {
@JvmStatic
fun asQuotedString(raw: String?): String? {
return if (raw == null)
null
else
fun asQuotedString(raw: String) =
"\"" + raw.replace("\\" ,"\\\\").replace("\"", "\\\"") + "\""
}
@JvmStatic
fun decodeQuotedString(quoted: String?): String? {
if (quoted == null)
return null
fun decodeQuotedString(quoted: String): String {
/* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
qdtext = <any TEXT except <">>
quoted-pair = "\" CHAR
......@@ -44,4 +35,4 @@ object QuotedStringUtils {
return quoted
}
}
}
\ No newline at end of file
......@@ -14,7 +14,6 @@ import java.net.URISyntaxException
object UrlUtils {
@JvmStatic
fun equals(url1: HttpUrl, url2: HttpUrl): Boolean {
// if okhttp thinks the two URLs are equal, they're in any case
// (and it's a simple String comparison)
......@@ -32,7 +31,6 @@ object UrlUtils {
}
}
@JvmStatic
fun hostToDomain(host: String?): String? {
if (host == null)
return null
......@@ -49,7 +47,6 @@ object UrlUtils {
host
}
@JvmStatic
fun omitTrailingSlash(url: HttpUrl): HttpUrl {
val idxLast = url.pathSize () - 1
val hasTrailingSlash = url.pathSegments()[idxLast] == ""
......@@ -60,7 +57,6 @@ object UrlUtils {
url
}
@JvmStatic
fun withTrailingSlash(url: HttpUrl): HttpUrl {
val idxLast = url.pathSize() - 1
val hasTrailingSlash = url.pathSegments()[idxLast] == ""
......
......@@ -15,11 +15,11 @@ import java.io.IOException
object XmlUtils {
@JvmField val NS_WEBDAV = "DAV:"
@JvmField val NS_CALDAV = "urn:ietf:params:xml:ns:caldav"
@JvmField val NS_CARDDAV = "urn:ietf:params:xml:ns:carddav"
@JvmField val NS_APPLE_ICAL = "http://apple.com/ns/ical/"
@JvmField val NS_CALENDARSERVER = "http://calendarserver.org/ns/"
const val NS_WEBDAV = "DAV:"
const val NS_CALDAV = "urn:ietf:params:xml:ns:caldav"
const val NS_CARDDAV = "urn:ietf:params:xml:ns:carddav"
const val NS_APPLE_ICAL = "http://apple.com/ns/ical/"
const val NS_CALENDARSERVER = "http://calendarserver.org/ns/"
private val factory: XmlPullParserFactory
init {
......@@ -31,10 +31,7 @@ object XmlUtils {
}
}
@JvmStatic
fun newPullParser() = factory.newPullParser()!!
@JvmStatic
fun newSerializer() = factory.newSerializer()!!
......
......@@ -8,4 +8,7 @@
package at.bitfire.dav4android.exception
open class DavException @JvmOverloads constructor(message: String, ex: Throwable? = null): Exception(message, ex)
\ No newline at end of file
open class DavException @JvmOverloads constructor(
message: String,
ex: Throwable? = null
): Exception(message, ex)
\ No newline at end of file
......@@ -15,8 +15,10 @@ import java.io.*
open class HttpException: Exception, Serializable {
// don't dump more than 20 kB
private val MAX_DUMP_SIZE = 20*1024
companion object {
// don't dump more than 20 kB
private const val MAX_DUMP_SIZE = 20*1024
}
val status: Int
val request: String?
......
......@@ -8,4 +8,7 @@
package at.bitfire.dav4android.exception
class InvalidDavResponseException(message: String, ex: Throwable? = null): DavException(message, ex)
\ No newline at end of file
class InvalidDavResponseException @JvmOverloads constructor(
message: String,
ex: Throwable? = null
): DavException(message, ex)
\ No newline at end of file
......@@ -8,4 +8,4 @@
package at.bitfire.dav4android.exception
class UnsupportedDavException(message: String) : DavException(message)
\ No newline at end of file
class UnsupportedDavException(message: String): DavException(message)
\ No newline at end of file
......@@ -37,7 +37,7 @@ class GetETag(
// entity tag is weak (doesn't matter for us)
tag = it.substring(2)
tag = QuotedStringUtils.decodeQuotedString(tag)
tag?.let { tag = QuotedStringUtils.decodeQuotedString(it) }
}
eTag = tag
......
......@@ -19,11 +19,11 @@ class ResourceType: Property {
@JvmField
val NAME = Property.Name(XmlUtils.NS_WEBDAV, "resourcetype")
@JvmField val COLLECTION = Property.Name(XmlUtils.NS_WEBDAV, "collection") // WebDAV
@JvmField val PRINCIPAL = Property.Name(XmlUtils.NS_WEBDAV, "principal") // WebDAV ACL
@JvmField val ADDRESSBOOK = Property.Name(XmlUtils.NS_CARDDAV, "addressbook") // CardDAV
@JvmField val CALENDAR = Property.Name(XmlUtils.NS_CALDAV, "calendar") // CalDAV
@JvmField val SUBSCRIBED = Property.Name(XmlUtils.NS_CALENDARSERVER, "subscribed")
val COLLECTION = Property.Name(XmlUtils.NS_WEBDAV, "collection") // WebDAV
val PRINCIPAL = Property.Name(XmlUtils.NS_WEBDAV, "principal") // WebDAV ACL
val ADDRESSBOOK = Property.Name(XmlUtils.NS_CARDDAV, "addressbook") // CardDAV
val CALENDAR = Property.Name(XmlUtils.NS_CALDAV, "calendar") // CalDAV
val SUBSCRIBED = Property.Name(XmlUtils.NS_CALENDARSERVER, "subscribed")
}
val types = mutableSetOf<Property.Name>()
......
......@@ -16,9 +16,9 @@ import org.xmlpull.v1.XmlPullParser
class SupportedReportSet: Property {
companion object {
@JvmField val NAME = Property.Name(XmlUtils.NS_WEBDAV, "supported-report-set")
val NAME = Property.Name(XmlUtils.NS_WEBDAV, "supported-report-set")
@JvmField val SYNC_COLLECTION = "DAV:sync-collection" // collection synchronization (RFC 6578)
val SYNC_COLLECTION = "DAV:sync-collection" // collection synchronization (RFC 6578)
}
val reports = mutableSetOf<String>()
......
/*
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class HttpUtilsTest {
@Test
public void testParseWwwAuthenticate() {
// two schemes: one without param (illegal!), second with two params
List<HttpUtils.AuthScheme> schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ " UnknownWithoutParam, Unknown WithParam1=\"a\", Param2 " }));
assertEquals(2, schemes.size());
assertEquals("UnknownWithoutParam", schemes.get(0).getName());
assertEquals(0, schemes.get(0).getParams().size());
assertEquals(0, schemes.get(0).getUnnamedParams().size());
assertEquals("Unknown", schemes.get(1).getName());
assertEquals(1, schemes.get(1).getParams().size());
assertEquals("a", schemes.get(1).getParams().get("withparam1"));
assertEquals(1, schemes.get(1).getParams().size());
assertEquals(1, schemes.get(1).getUnnamedParams().size());
assertEquals("Param2", schemes.get(1).getUnnamedParams().get(0));
// parameters with quoted strings with commas
schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "X-MyScheme param1, param2=\"a,\\\"b\\\",c\", MyOtherScheme paramA" }));
assertEquals(2, schemes.size());
assertEquals("X-MyScheme", schemes.get(0).getName());
assertEquals(1, schemes.get(0).getParams().size());
assertEquals("a,\"b\",c", schemes.get(0).getParams().get("param2"));
assertEquals(1, schemes.get(0).getUnnamedParams().size());
assertEquals("param1", schemes.get(0).getUnnamedParams().get(0));
assertEquals("MyOtherScheme", schemes.get(1).getName());
assertEquals(0, schemes.get(1).getParams().size());
assertEquals(1, schemes.get(1).getUnnamedParams().size());
assertEquals("paramA", schemes.get(1).getUnnamedParams().get(0));
/*** REAL WORLD EXAMPLES ***/
// Basic auth
schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "Basic realm=\"test\"" }));
assertEquals(1, schemes.size());
HttpUtils.AuthScheme scheme = schemes.get(0);
assertEquals("Basic", scheme.getName());
assertEquals(1, scheme.getParams().size());
assertEquals("test", scheme.getParams().get("realm"));
assertEquals(0, scheme.getUnnamedParams().size());
// Basic and Digest auth in one line
schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "Basic realm=\"testrealm@host.com\", Digest realm=\"testrealm@host.com\", qop=\"auth,auth-int\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"" }));
assertEquals(2, schemes.size());
scheme = schemes.get(0);
assertEquals("Basic", scheme.getName());
assertEquals(1, scheme.getParams().size());
assertEquals("testrealm@host.com", scheme.getParams().get("realm"));
assertEquals(0, scheme.getUnnamedParams().size());
scheme = schemes.get(1);
assertEquals("Digest", scheme.getName());
assertEquals(4, scheme.getParams().size());
assertEquals("testrealm@host.com", scheme.getParams().get("realm"));
assertEquals("auth,auth-int", scheme.getParams().get("qop"));
assertEquals("dcd98b7102dd2f0e8b11d0f600bfb0c093", scheme.getParams().get("nonce"));
assertEquals("5ccc069c403ebaf9f0171e9517f40e41", scheme.getParams().get("opaque"));
assertEquals(0, scheme.getUnnamedParams().size());
// Negotiate (RFC 4559)
schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "Negotiate" }));
assertEquals(1, schemes.size());
scheme = schemes.get(0);
assertEquals("Negotiate", scheme.getName());
assertEquals(0, scheme.getParams().size());
assertEquals(0, scheme.getUnnamedParams().size());
schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "Negotiate a87421000492aa874209af8bc028" }));
assertEquals(1, schemes.size());
scheme = schemes.get(0);
assertEquals("Negotiate", scheme.getName());
assertEquals(0, scheme.getParams().size());
assertEquals(1, scheme.getUnnamedParams().size());
assertEquals("a87421000492aa874209af8bc028", scheme.getUnnamedParams().get(0));
// NTLM, see https://msdn.microsoft.com/en-us/library/dd944123%28v=office.12%29.aspx
schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{
"NTLM realm=\"SIP Communications Service\", targetname=\"server.contoso.com\", version=3",
"Kerberos realm=\"SIP Communications Service\", targetname=\"sip/server.contoso.com\", version=3"
}));
assertEquals(2, schemes.size());
scheme = schemes.get(0);
assertEquals("NTLM", scheme.getName());
assertEquals(3, scheme.getParams().size());
assertEquals("SIP Communications Service", scheme.getParams().get("realm"));
assertEquals("server.contoso.com", scheme.getParams().get("targetname"));
assertEquals("3", scheme.getParams().get("version"));
assertEquals(0, scheme.getUnnamedParams().size());
scheme = schemes.get(1);
assertEquals("Kerberos", scheme.getName());
assertEquals(3, scheme.getParams().size());
assertEquals("SIP Communications Service", scheme.getParams().get("realm"));
assertEquals("sip/server.contoso.com", scheme.getParams().get("targetname"));
assertEquals("3", scheme.getParams().get("version"));
assertEquals(0, scheme.getUnnamedParams().size());
// https://issues.apache.org/jira/browse/HTTPCLIENT-1489
schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "X-MobileMe-AuthToken realm=\"Newcastle\", Basic realm=\"Newcastle\"" }));
assertEquals(2, schemes.size());
scheme = schemes.get(0);
assertEquals("X-MobileMe-AuthToken", scheme.getName());
assertEquals(1, scheme.getParams().size());
assertEquals("Newcastle", scheme.getParams().get("realm"));
assertEquals(0, scheme.getUnnamedParams().size());
scheme = schemes.get(1);
assertEquals("Basic", scheme.getName());
assertEquals(1, scheme.getParams().size());
assertEquals("Newcastle", scheme.getParams().get("realm"));
assertEquals(0, scheme.getUnnamedParams().size());
// Contacts and Calendar Server example; space in second token!
schemes = HttpUtils.parseWwwAuthenticate(Arrays.asList(new String[]{ "digest nonce=\"785592012006934833760823299624355448128925071071026584347\", realm=\"Test Realm\", algorithm=\"md5\"" }));
assertEquals(1, schemes.size());
scheme = schemes.get(0);
assertEquals("digest", scheme.getName());
assertEquals(3, scheme.getParams().size());
assertEquals("785592012006934833760823299624355448128925071071026584347", scheme.getParams().get("nonce"));
assertEquals("Test Realm", scheme.getParams().get("realm"));
assertEquals("md5", scheme.getParams().get("algorithm"));
}
}
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android
import org.junit.Assert.assertEquals
import org.junit.Test
class HttpUtilsTest {
@Test
fun testParseWwwAuthenticate() {
// two schemes: one without param (illegal!), second with two params
var schemes = HttpUtils.parseWwwAuthenticate(listOf(" UnknownWithoutParam, Unknown WithParam1=\"a\", Param2 "))
assertEquals(2, schemes.size)
assertEquals("UnknownWithoutParam", schemes.first().name)
assertEquals(0, schemes.first().params.size)
assertEquals(0, schemes.first().unnamedParams.size)
assertEquals("Unknown", schemes[1].name)
assertEquals(1, schemes[1].params.size)
assertEquals("a", schemes[1].params["withparam1"])
assertEquals(1, schemes[1].params.size)
assertEquals(1, schemes[1].unnamedParams.size)
assertEquals("Param2", schemes[1].unnamedParams.first)
// parameters with quoted strings with commas
schemes = HttpUtils.parseWwwAuthenticate(listOf("X-MyScheme param1, param2=\"a,\\\"b\\\",c\", MyOtherScheme paramA"))
assertEquals(2, schemes.size)
assertEquals("X-MyScheme", schemes.first().name)
assertEquals(1, schemes.first().params.size)
assertEquals("a,\"b\",c", schemes.first().params["param2"])
assertEquals(1, schemes.first().unnamedParams.size)
assertEquals("param1", schemes.first().unnamedParams.first)
assertEquals("MyOtherScheme", schemes[1].name)
assertEquals(0, schemes[1].params.size)
assertEquals(1, schemes[1].unnamedParams.size)
assertEquals("paramA", schemes[1].unnamedParams.first)
/*** REAL WORLD EXAMPLES ***/
// Basic auth
schemes = HttpUtils.parseWwwAuthenticate(listOf("Basic realm=\"test\""))
assertEquals(1, schemes.size)
var scheme = schemes.first()
assertEquals("Basic", scheme.name)
assertEquals(1, scheme.params.size)
assertEquals("test", scheme.params["realm"])
assertEquals(0, scheme.unnamedParams.size)
// Basic and Digest auth in one line
schemes = HttpUtils.parseWwwAuthenticate(listOf("Basic realm=\"testrealm@host.com\", Digest realm=\"testrealm@host.com\", qop=\"auth,auth-int\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""))
assertEquals(2, schemes.size)
scheme = schemes.first()
assertEquals("Basic", scheme.name)
assertEquals(1, scheme.params.size)
assertEquals("testrealm@host.com", scheme.params["realm"])
assertEquals(0, scheme.unnamedParams.size)
scheme = schemes[1]
assertEquals("Digest", scheme.name)
assertEquals(4, scheme.params.size)
assertEquals("testrealm@host.com", scheme.params["realm"])
assertEquals("auth,auth-int", scheme.params["qop"])
assertEquals("dcd98b7102dd2f0e8b11d0f600bfb0c093", scheme.params["nonce"])
assertEquals("5ccc069c403ebaf9f0171e9517f40e41", scheme.params["opaque"])
assertEquals(0, scheme.unnamedParams.size)
// Negotiate (RFC 4559)
schemes = HttpUtils.parseWwwAuthenticate(listOf("Negotiate"))
assertEquals(1, schemes.size)
scheme = schemes.first()
assertEquals("Negotiate", scheme.name)
assertEquals(0, scheme.params.size)
assertEquals(0, scheme.unnamedParams.size)
schemes = HttpUtils.parseWwwAuthenticate(listOf("Negotiate a87421000492aa874209af8bc028"))
assertEquals(1, schemes.size)
scheme = schemes.first()
assertEquals("Negotiate", scheme.name)
assertEquals(0, scheme.params.size)
assertEquals(1, scheme.unnamedParams.size)
assertEquals("a87421000492aa874209af8bc028", scheme.unnamedParams.first)
// NTLM, see https://msdn.microsoft.com/en-us/library/dd944123%28v=office.12%29.aspx
schemes = HttpUtils.parseWwwAuthenticate(listOf(
"NTLM realm=\"SIP Communications Service\", targetname=\"server.contoso.com\", version=3",
"Kerberos realm=\"SIP Communications Service\", targetname=\"sip/server.contoso.com\", version=3"
))
assertEquals(2, schemes.size)
scheme = schemes.first()
assertEquals("NTLM", scheme.name)
assertEquals(3, scheme.params.size)
assertEquals("SIP Communications Service", scheme.params["realm"])
assertEquals("server.contoso.com", scheme.params["targetname"])
assertEquals("3", scheme.params["version"])
assertEquals(0, scheme.unnamedParams.size)
scheme = schemes[1]
assertEquals("Kerberos", scheme.name)
assertEquals(3, scheme.params.size)
assertEquals("SIP Communications Service", scheme.params["realm"])
assertEquals("sip/server.contoso.com", scheme.params["targetname"])
assertEquals("3", scheme.params["version"])
assertEquals(0, scheme.unnamedParams.size)
// https://issues.apache.org/jira/browse/HTTPCLIENT-1489
schemes = HttpUtils.parseWwwAuthenticate(listOf("X-MobileMe-AuthToken realm=\"Newcastle\", Basic realm=\"Newcastle\""))
assertEquals(2, schemes.size)
scheme = schemes.first()
assertEquals("X-MobileMe-AuthToken", scheme.name)
assertEquals(1, scheme.params.size)
assertEquals("Newcastle", scheme.params["realm"])
assertEquals(0, scheme.unnamedParams.size)
scheme = schemes[1]
assertEquals("Basic", scheme.name)
assertEquals(1, scheme.params.size)
assertEquals("Newcastle", scheme.params["realm"])
assertEquals(0, scheme.unnamedParams.size)
// Contacts and Calendar Server example; space in second token!
schemes = HttpUtils.parseWwwAuthenticate(listOf("digest nonce=\"785592012006934833760823299624355448128925071071026584347\", realm=\"Test Realm\", algorithm=\"md5\""))
assertEquals(1, schemes.size)
scheme = schemes.first()
assertEquals("digest", scheme.name)
assertEquals(3, scheme.params.size)
assertEquals("785592012006934833760823299624355448128925071071026584347", scheme.params["nonce"])
assertEquals("Test Realm", scheme.params["realm"])
assertEquals("md5", scheme.params["algorithm"])
}
}
/*
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class QuotedStringUtilsTest {
@Test
public void testAsQuotedString() {
assertEquals(null, QuotedStringUtils.asQuotedString(null));
assertEquals("\"\"", QuotedStringUtils.asQuotedString(""));
assertEquals("\"\\\"\"", QuotedStringUtils.asQuotedString("\""));
assertEquals("\"\\\\\"", QuotedStringUtils.asQuotedString("\\"));
}
public void testDecodeQuotedString() {
assertEquals(null, QuotedStringUtils.decodeQuotedString(null));
assertEquals("\"", QuotedStringUtils.decodeQuotedString("\""));
assertEquals("\\", QuotedStringUtils.decodeQuotedString("\"\\\""));
assertEquals("\"test", QuotedStringUtils.decodeQuotedString("\"test"));
assertEquals("test", QuotedStringUtils.decodeQuotedString("test"));
assertEquals("", QuotedStringUtils.decodeQuotedString("\"\""));
assertEquals("test", QuotedStringUtils.decodeQuotedString("\"test\""));
assertEquals("test\\", QuotedStringUtils.decodeQuotedString("\"test\\\""));
assertEquals("test", QuotedStringUtils.decodeQuotedString("\"t\\e\\st\""));
assertEquals("12\"34", QuotedStringUtils.decodeQuotedString("\"12\\\"34\""));
assertEquals("1234\"", QuotedStringUtils.decodeQuotedString("\"1234\\\"\""));
}
}
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android
import org.junit.Assert.assertEquals
import org.junit.Test
class QuotedStringUtilsTest {
@Test
fun testAsQuotedString() {
assertEquals("\"\"", QuotedStringUtils.asQuotedString(""))
assertEquals("\"\\\"\"", QuotedStringUtils.asQuotedString("\""))
assertEquals("\"\\\\\"", QuotedStringUtils.asQuotedString("\\"))
}
fun testDecodeQuotedString() {
assertEquals("\"", QuotedStringUtils.decodeQuotedString("\""))
assertEquals("\\", QuotedStringUtils.decodeQuotedString("\"\\\""))
assertEquals("\"test", QuotedStringUtils.decodeQuotedString("\"test"))
assertEquals("test", QuotedStringUtils.decodeQuotedString("test"))
assertEquals("", QuotedStringUtils.decodeQuotedString("\"\""))
assertEquals("test", QuotedStringUtils.decodeQuotedString("\"test\""))
assertEquals("test\\", QuotedStringUtils.decodeQuotedString("\"test\\\""))
assertEquals("test", QuotedStringUtils.decodeQuotedString("\"t\\e\\st\""))
assertEquals("12\"34", QuotedStringUtils.decodeQuotedString("\"12\\\"34\""))
assertEquals("1234\"", QuotedStringUtils.decodeQuotedString("\"1234\\\"\""))
}
}
package at.bitfire.dav4android;
import org.junit.Test;
import okhttp3.HttpUrl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class UrlUtilsTest {
@Test
public void testEquals() {
assertTrue(UrlUtils.equals(HttpUrl.parse("http://host/resource"), HttpUrl.parse("http://host/resource")));
assertTrue(UrlUtils.equals(HttpUrl.parse("http://host:80/resource"), HttpUrl.parse("http://host/resource")));
assertTrue(UrlUtils.equals(HttpUrl.parse("https://HOST:443/resource"), HttpUrl.parse("https://host/resource")));
assertTrue(UrlUtils.equals(HttpUrl.parse("https://host:443/my@dav/"), HttpUrl.parse("https://host/my%40dav/")));
assertFalse(UrlUtils.equals(HttpUrl.parse("http://host/resource"), HttpUrl.parse("http://host/resource/")));
assertFalse(UrlUtils.equals(HttpUrl.parse("http://host/resource"), HttpUrl.parse("http://host:81/resource")));
}
@Test
public void testHostToDomain() {
assertNull(UrlUtils.hostToDomain(null));
assertEquals("", UrlUtils.hostToDomain("."));
assertEquals("com", UrlUtils.hostToDomain("com"));
assertEquals("com", UrlUtils.hostToDomain("com."));
assertEquals("example.com", UrlUtils.hostToDomain("example.com"));
assertEquals("example.com", UrlUtils.hostToDomain("example.com."));
assertEquals("example.com", UrlUtils.hostToDomain(".example.com"));
assertEquals("example.com", UrlUtils.hostToDomain(".example.com."));
assertEquals("example.com", UrlUtils.hostToDomain("host.example.com"));
assertEquals("example.com", UrlUtils.hostToDomain("host.example.com."));
assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com"));
assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com."));
}
@Test
public void testOmitTrailingSlash() {
assertEquals(HttpUrl.parse("http://host/resource"), UrlUtils.omitTrailingSlash(HttpUrl.parse("http://host/resource")));
assertEquals(HttpUrl.parse("http://host/resource"), UrlUtils.omitTrailingSlash(HttpUrl.parse("http://host/resource/")));
}
@Test
public void testWithTrailingSlash() {
assertEquals(HttpUrl.parse("http://host/resource/"), UrlUtils.withTrailingSlash(HttpUrl.parse("http://host/resource")));
assertEquals(HttpUrl.parse("http://host/resource/"), UrlUtils.withTrailingSlash(HttpUrl.parse("http://host/resource/")));
}
}
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android
import okhttp3.HttpUrl
import org.junit.Assert.*
import org.junit.Test
class UrlUtilsTest {
@Test
fun testEquals() {
assertTrue(UrlUtils.equals(HttpUrl.parse("http://host/resource")!!, HttpUrl.parse("http://host/resource")!!))
assertTrue(UrlUtils.equals(HttpUrl.parse("http://host:80/resource")!!, HttpUrl.parse("http://host/resource")!!))
assertTrue(UrlUtils.equals(HttpUrl.parse("https://HOST:443/resource")!!, HttpUrl.parse("https://host/resource")!!))
assertTrue(UrlUtils.equals(HttpUrl.parse("https://host:443/my@dav/")!!, HttpUrl.parse("https://host/my%40dav/")!!))
assertFalse(UrlUtils.equals(HttpUrl.parse("http://host/resource")!!, HttpUrl.parse("http://host/resource/")!!))
assertFalse(UrlUtils.equals(HttpUrl.parse("http://host/resource")!!, HttpUrl.parse("http://host:81/resource")!!))
}
@Test
fun testHostToDomain() {
assertNull(UrlUtils.hostToDomain(null))
assertEquals("", UrlUtils.hostToDomain("."))
assertEquals("com", UrlUtils.hostToDomain("com"))
assertEquals("com", UrlUtils.hostToDomain("com."))
assertEquals("example.com", UrlUtils.hostToDomain("example.com"))
assertEquals("example.com", UrlUtils.hostToDomain("example.com."))
assertEquals("example.com", UrlUtils.hostToDomain(".example.com"))
assertEquals("example.com", UrlUtils.hostToDomain(".example.com."))
assertEquals("example.com", UrlUtils.hostToDomain("host.example.com"))
assertEquals("example.com", UrlUtils.hostToDomain("host.example.com."))
assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com"))
assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com."))
}
@Test
fun testOmitTrailingSlash() {
assertEquals(HttpUrl.parse("http://host/resource")!!, UrlUtils.omitTrailingSlash(HttpUrl.parse("http://host/resource")!!))
assertEquals(HttpUrl.parse("http://host/resource")!!, UrlUtils.omitTrailingSlash(HttpUrl.parse("http://host/resource/")!!))
}
@Test
fun testWithTrailingSlash() {
assertEquals(HttpUrl.parse("http://host/resource/")!!, UrlUtils.withTrailingSlash(HttpUrl.parse("http://host/resource")!!))
assertEquals(HttpUrl.parse("http://host/resource/")!!, UrlUtils.withTrailingSlash(HttpUrl.parse("http://host/resource/")!!))
}
}
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android.exception;
import org.junit.Test;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import static junit.framework.Assert.assertTrue;
@SuppressWarnings("ThrowableInstanceNeverThrown")
public class HttpExceptionTest {
final static String responseMessage = "Unknown error";
@Test
public void testHttpFormatting() {
Request request = new Request.Builder()
.post(RequestBody.create(null, "REQUEST\nBODY" + (char)5))
.url("http://example.com")
.build();
Response response = new Response.Builder()
.request(request)
.protocol(Protocol.HTTP_2)
.code(500)
.message(responseMessage)
.body(ResponseBody.create(null, (char)0x99 + "SERVER\r\nRESPONSE"))
.build();
HttpException e = new HttpException(response);
assertTrue(e.getMessage().contains("500"));
assertTrue(e.getMessage().contains(responseMessage));
assertTrue(e.getRequest().contains("REQUEST\nBODY[05]"));
assertTrue(e.getResponse().contains("[99]SERVER↵\nRESPONSE"));
}
}
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android.exception
import okhttp3.*
import org.junit.Assert.assertTrue
import org.junit.Test
class HttpExceptionTest {
private val responseMessage = "Unknown error"
@Test
fun testHttpFormatting() {
val request = Request.Builder()
.post(RequestBody.create(null, "REQUEST\nBODY" + 5.toChar()))
.url("http://example.com")
.build()
val response = Response.Builder()
.request(request)
.protocol(Protocol.HTTP_2)
.code(500)
.message(responseMessage)
.body(ResponseBody.create(null, 0x99.toChar() + "SERVER\r\nRESPONSE"))
.build()
val e = HttpException(response)
assertTrue(e.message!!.contains("500"))
assertTrue(e.message!!.contains(responseMessage))
assertTrue(e.request!!.contains("REQUEST\nBODY[05]"))
assertTrue(e.response!!.contains("[99]SERVER↵\nRESPONSE"))
}
}
/*
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android.exception;
import org.junit.Test;
import java.util.Calendar;
import java.util.Date;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.internal.http.HttpDate;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class ServiceUnavailableExceptionTest {
@Test
public void testRetryAfter() {
Response response = new Response.Builder()
.request(new Request.Builder()
.url("http://www.example.com")
.get()
.build())
.protocol(Protocol.HTTP_1_1)
.code(503).message("Try later")
.build();
ServiceUnavailableException e = new ServiceUnavailableException(response);
assertNull(e.getRetryAfter());
response = response.newBuilder()
.header("Retry-After", "120")
.build();
e = new ServiceUnavailableException(response);
assertNotNull(e.getRetryAfter());
assertTrue(withinTimeRange(e.getRetryAfter(), 120));
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MINUTE, 30);
response = response.newBuilder()
.header("Retry-After", HttpDate.format(cal.getTime()))
.build();
e = new ServiceUnavailableException(response);
assertNotNull(e.getRetryAfter());
assertTrue(withinTimeRange(e.getRetryAfter(), 30*60));
}
private boolean withinTimeRange(Date d, int seconds) {
final long msCheck = d.getTime(), msShouldBe = new Date().getTime() + seconds*1000;
// assume max. 5 seconds difference for test running
return Math.abs(msCheck - msShouldBe) < 5000;
}
}
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android.exception
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import okhttp3.internal.http.HttpDate
import org.junit.Assert.*
import org.junit.Test
import java.util.*
class ServiceUnavailableExceptionTest {
@Test
fun testRetryAfter() {
var response = Response.Builder()
.request(Request.Builder()
.url("http://www.example.com")
.get()
.build())
.protocol(Protocol.HTTP_1_1)
.code(503).message("Try later")
.build()
var e = ServiceUnavailableException(response)
assertNull(e.retryAfter)
response = response.newBuilder()
.header("Retry-After", "120")
.build()
e = ServiceUnavailableException(response)
assertNotNull(e.retryAfter)
assertTrue(withinTimeRange(e.retryAfter!!, 120))
val cal = Calendar.getInstance()
cal.add(Calendar.MINUTE, 30)
response = response.newBuilder()
.header("Retry-After", HttpDate.format(cal.time))
.build()
e = ServiceUnavailableException(response)
assertNotNull(e.retryAfter)
assertTrue(withinTimeRange(e.retryAfter!!, 30*60))
}
private fun withinTimeRange(d: Date, seconds: Int): Boolean {
val msCheck = d.time
val msShouldBe = Date().time + seconds*1000
// assume max. 5 seconds difference for test running
return Math.abs(msCheck - msShouldBe) < 5000
}
}
\ No newline at end of file