Commit 1169e150 authored by Ricki Hirner's avatar Ricki Hirner 🐑

Update okhttp, tests in Kotlin, refactoring

parent 568f0285
Pipeline #18748357 passed with stages
in 3 minutes and 50 seconds
buildscript { buildscript {
ext.kotlin_version = '1.2.21' ext.kotlin_version = '1.2.30'
ext.dokka_version = '0.9.15' ext.dokka_version = '0.9.15'
repositories { repositories {
...@@ -52,12 +52,12 @@ android { ...@@ -52,12 +52,12 @@ android {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 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' androidTestCompile 'com.android.support.test:runner:1.0.1'
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
testCompile 'junit:junit:4.12' 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( ...@@ -40,8 +40,8 @@ class BasicDigestAuthHandler(
private val HEADER_AUTHORIZATION = "Authorization" private val HEADER_AUTHORIZATION = "Authorization"
// cached digest parameters // cached digest parameters
@JvmField var clientNonce = h(UUID.randomUUID().toString()) var clientNonce = h(UUID.randomUUID().toString())
@JvmField var nonceCount = AtomicInteger(1) var nonceCount = AtomicInteger(1)
fun quotedString(s: String) = "\"" + s.replace("\"", "\\\"") + "\"" fun quotedString(s: String) = "\"" + s.replace("\"", "\\\"") + "\""
fun h(data: String) = ByteString.of(ByteBuffer.wrap(data.toByteArray())).md5().hex()!! fun h(data: String) = ByteString.of(ByteBuffer.wrap(data.toByteArray())).md5().hex()!!
......
...@@ -12,7 +12,6 @@ import java.util.logging.Logger ...@@ -12,7 +12,6 @@ import java.util.logging.Logger
object Constants { object Constants {
@JvmField
var log = Logger.getLogger("dav4android")!! var log = Logger.getLogger("dav4android")!!
} }
\ No newline at end of file
...@@ -22,10 +22,7 @@ class DavAddressBook @JvmOverloads constructor( ...@@ -22,10 +22,7 @@ class DavAddressBook @JvmOverloads constructor(
): DavCollection(httpClient, location, log) { ): DavCollection(httpClient, location, log) {
companion object { companion object {
@JvmField
val MIME_VCARD3_UTF8 = MediaType.parse("text/vcard;charset=utf-8") val MIME_VCARD3_UTF8 = MediaType.parse("text/vcard;charset=utf-8")
@JvmField
val MIME_VCARD4 = MediaType.parse("text/vcard;version=4.0") val MIME_VCARD4 = MediaType.parse("text/vcard;version=4.0")
} }
...@@ -36,7 +33,6 @@ class DavAddressBook @JvmOverloads constructor( ...@@ -36,7 +33,6 @@ class DavAddressBook @JvmOverloads constructor(
* @throws HttpException on HTTP error * @throws HttpException on HTTP error
* @throws DavException on DAV error * @throws DavException on DAV error
*/ */
@Throws(IOException::class, HttpException::class, DavException::class)
fun addressbookQuery() { fun addressbookQuery() {
/* <!ELEMENT addressbook-query ((DAV:allprop | /* <!ELEMENT addressbook-query ((DAV:allprop |
DAV:propname | DAV:propname |
...@@ -78,7 +74,6 @@ class DavAddressBook @JvmOverloads constructor( ...@@ -78,7 +74,6 @@ class DavAddressBook @JvmOverloads constructor(
* @throws HttpException on HTTP error * @throws HttpException on HTTP error
* @throws DavException on DAV error * @throws DavException on DAV error
*/ */
@Throws(IOException::class, HttpException::class, DavException::class)
fun multiget(urls: List<HttpUrl>, vCard4: Boolean) { fun multiget(urls: List<HttpUrl>, vCard4: Boolean) {
/* <!ELEMENT addressbook-multiget ((DAV:allprop | /* <!ELEMENT addressbook-multiget ((DAV:allprop |
DAV:propname | DAV:propname |
......
...@@ -24,7 +24,6 @@ class DavCalendar @JvmOverloads constructor( ...@@ -24,7 +24,6 @@ class DavCalendar @JvmOverloads constructor(
): DavCollection(httpClient, location, log) { ): DavCollection(httpClient, location, log) {
companion object { companion object {
@JvmField
val MIME_ICALENDAR = MediaType.parse("text/calendar") val MIME_ICALENDAR = MediaType.parse("text/calendar")
val MIME_ICALENDAR_UTF8 = MediaType.parse("text/calendar;charset=utf-8") val MIME_ICALENDAR_UTF8 = MediaType.parse("text/calendar;charset=utf-8")
} }
...@@ -38,7 +37,6 @@ class DavCalendar @JvmOverloads constructor( ...@@ -38,7 +37,6 @@ class DavCalendar @JvmOverloads constructor(
* @throws HttpException on HTTP error * @throws HttpException on HTTP error
* @throws DavException on DAV error * @throws DavException on DAV error
*/ */
@Throws(IOException::class, HttpException::class, DavException::class)
fun calendarQuery(component: String, start: Date?, end: Date?) { fun calendarQuery(component: String, start: Date?, end: Date?) {
/* <!ELEMENT calendar-query ((DAV:allprop | /* <!ELEMENT calendar-query ((DAV:allprop |
DAV:propname | DAV:propname |
...@@ -100,7 +98,6 @@ class DavCalendar @JvmOverloads constructor( ...@@ -100,7 +98,6 @@ class DavCalendar @JvmOverloads constructor(
* @throws HttpException on HTTP error * @throws HttpException on HTTP error
* @throws DavException on DAV error * @throws DavException on DAV error
*/ */
@Throws(IOException::class, HttpException::class, DavException::class)
fun multiget(urls: List<HttpUrl>) { fun multiget(urls: List<HttpUrl>) {
/* <!ELEMENT calendar-multiget ((DAV:allprop | /* <!ELEMENT calendar-multiget ((DAV:allprop |
DAV:propname | DAV:propname |
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
package at.bitfire.dav4android package at.bitfire.dav4android
import at.bitfire.dav4android.exception.DavException
import at.bitfire.dav4android.exception.HttpException
import at.bitfire.dav4android.property.SyncToken import at.bitfire.dav4android.property.SyncToken
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
...@@ -16,13 +18,13 @@ import okhttp3.RequestBody ...@@ -16,13 +18,13 @@ import okhttp3.RequestBody
import java.io.StringWriter import java.io.StringWriter
import java.util.logging.Logger import java.util.logging.Logger
open class DavCollection( open class DavCollection @JvmOverloads constructor(
httpClient: OkHttpClient, httpClient: OkHttpClient,
location: HttpUrl, location: HttpUrl,
log: Logger = Constants.log log: Logger = Constants.log
): DavResource(httpClient, location, log) { ): DavResource(httpClient, location, log) {
/** /**
* Sends a REPORT sync-collection request. If a sync-token is returned, it will be made * Sends a REPORT sync-collection request. If a sync-token is returned, it will be made
* available in [properties]. * available in [properties].
* *
...@@ -30,6 +32,10 @@ open class DavCollection( ...@@ -30,6 +32,10 @@ open class DavCollection(
* @param infiniteDepth sync-level to be sent with the request: false = "1", true = "infinite" * @param infiniteDepth sync-level to be sent with the request: false = "1", true = "infinite"
* @param limit maximum number of results (may cause truncation) * @param limit maximum number of results (may cause truncation)
* @param properties WebDAV properties to be requested * @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) { fun reportChanges(syncToken: String?, infiniteDepth: Boolean, limit: Int?, vararg properties: Property.Name) {
/* <!ELEMENT sync-collection (sync-token, sync-level, limit?, prop)> /* <!ELEMENT sync-collection (sync-token, sync-level, limit?, prop)>
......
...@@ -429,11 +429,11 @@ open class DavResource @JvmOverloads constructor( ...@@ -429,11 +429,11 @@ open class DavResource @JvmOverloads constructor(
"prop" -> "prop" ->
prop = parseMultiStatus_Prop() prop = parseMultiStatus_Prop()
"status" -> "status" ->
try { status = try {
status = StatusLine.parse(parser.nextText()) StatusLine.parse(parser.nextText())
} catch(e: ProtocolException) { } catch(e: ProtocolException) {
log.warning("Invalid status line, treating as 500 Server Error") 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() eventType = parser.next()
...@@ -485,11 +485,11 @@ open class DavResource @JvmOverloads constructor( ...@@ -485,11 +485,11 @@ open class DavResource @JvmOverloads constructor(
href = location.resolve(sHref) href = location.resolve(sHref)
} }
"status" -> "status" ->
try { status = try {
status = StatusLine.parse(parser.nextText()) StatusLine.parse(parser.nextText())
} catch(e: ProtocolException) { } catch(e: ProtocolException) {
log.warning("Invalid status line, treating as 500 Server Error") 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" -> "propstat" ->
parseMultiStatus_PropStat()?.let { properties.merge(it, false) } parseMultiStatus_PropStat()?.let { properties.merge(it, false) }
......
...@@ -17,13 +17,11 @@ object HttpUtils { ...@@ -17,13 +17,11 @@ object HttpUtils {
private val authSchemeWithParam = Pattern.compile("^([^ \"]+) +(.*)$") private val authSchemeWithParam = Pattern.compile("^([^ \"]+) +(.*)$")
@JvmStatic
fun listHeader(response: Response, name: String): Array<String> { fun listHeader(response: Response, name: String): Array<String> {
val value = response.headers(name).joinToString(",") val value = response.headers(name).joinToString(",")
return value.split(',').filter { it.isNotEmpty() }.toTypedArray() return value.split(',').filter { it.isNotEmpty() }.toTypedArray()
} }
@JvmStatic
fun parseWwwAuthenticate(wwwAuths: List<String>): List<AuthScheme> { fun parseWwwAuthenticate(wwwAuths: List<String>): List<AuthScheme> {
/* WWW-Authenticate = "WWW-Authenticate" ":" 1#challenge /* WWW-Authenticate = "WWW-Authenticate" ":" 1#challenge
......
...@@ -11,7 +11,7 @@ package at.bitfire.dav4android ...@@ -11,7 +11,7 @@ package at.bitfire.dav4android
/** /**
* A WebDAV property. * 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. * which will be accessed by reflection.
*/ */
interface Property { interface Property {
...@@ -21,11 +21,11 @@ interface Property { ...@@ -21,11 +21,11 @@ interface Property {
val name: String val name: String
) { ) {
override fun equals(o: Any?): Boolean { override fun equals(other: Any?): Boolean {
return if (o is Name) return if (other is Name)
namespace == o.namespace && name == o.name namespace == other.namespace && name == other.name
else else
super.equals(o) super.equals(other)
} }
override fun hashCode() = namespace.hashCode() xor name.hashCode() override fun hashCode() = namespace.hashCode() xor name.hashCode()
......
...@@ -33,12 +33,12 @@ class PropertyCollection { ...@@ -33,12 +33,12 @@ class PropertyCollection {
if (!properties.isInitialized()) if (!properties.isInitialized())
return null return null
try { return try {
val name = clazz.getDeclaredField("NAME").get(null) as Property.Name val name = clazz.getDeclaredField("NAME").get(null) as Property.Name
return properties.value[name] as? T properties.value[name] as? T
} catch (e: NoSuchFieldException) { } catch (e: NoSuchFieldException) {
Constants.log.severe("$clazz does not have a static NAME field") Constants.log.severe("$clazz does not have a static NAME field")
return null null
} }
} }
...@@ -66,15 +66,14 @@ class PropertyCollection { ...@@ -66,15 +66,14 @@ class PropertyCollection {
/** /**
* Merges another #{@link PropertyCollection} into #{@link #properties}. * Merges another [PropertyCollection] into [properties].
* Existing properties will be overwritten. * Existing properties will be overwritten.
*
* @param another property collection to take the properties from * @param another property collection to take the properties from
* @param removeNullValues Indicates how "another" properties with null values should be treated. * @param removeNullValues Indicates how "another" properties with null values should be treated.
* <ul> * - true: If the "another" property value is null, the property will be removed in [properties].
* <li>#{@code true}: If the "another" property value is #{@code null}, the property will be removed in #{@link #properties}.</li> * - false: If the "another" property value is null, the property in [properties] will be set to null, too,
* <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 [properties] will never be overwritten by null.
but only if it doesn't exist yet. This means values in #{@link #properties} will never be overwritten by #{@code null}.</li>
* </ul>
*/ */
fun merge(another: PropertyCollection, removeNullValues: Boolean) { fun merge(another: PropertyCollection, removeNullValues: Boolean) {
val properties = another.getMap() val properties = another.getMap()
......
...@@ -20,7 +20,10 @@ interface PropertyFactory { ...@@ -20,7 +20,10 @@ interface PropertyFactory {
*/ */
fun getName(): Property.Name 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? fun create(parser: XmlPullParser): Property?
} }
...@@ -10,19 +10,10 @@ package at.bitfire.dav4android ...@@ -10,19 +10,10 @@ package at.bitfire.dav4android
object QuotedStringUtils { object QuotedStringUtils {
@JvmStatic fun asQuotedString(raw: String) =
fun asQuotedString(raw: String?): String? {
return if (raw == null)
null
else
"\"" + raw.replace("\\" ,"\\\\").replace("\"", "\\\"") + "\"" "\"" + raw.replace("\\" ,"\\\\").replace("\"", "\\\"") + "\""
}
@JvmStatic
fun decodeQuotedString(quoted: String?): String? {
if (quoted == null)
return null
fun decodeQuotedString(quoted: String): String {
/* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) /* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
qdtext = <any TEXT except <">> qdtext = <any TEXT except <">>
quoted-pair = "\" CHAR quoted-pair = "\" CHAR
...@@ -44,4 +35,4 @@ object QuotedStringUtils { ...@@ -44,4 +35,4 @@ object QuotedStringUtils {
return quoted return quoted
} }
} }
\ No newline at end of file
...@@ -14,7 +14,6 @@ import java.net.URISyntaxException ...@@ -14,7 +14,6 @@ import java.net.URISyntaxException
object UrlUtils { object UrlUtils {
@JvmStatic
fun equals(url1: HttpUrl, url2: HttpUrl): Boolean { fun equals(url1: HttpUrl, url2: HttpUrl): Boolean {
// if okhttp thinks the two URLs are equal, they're in any case // if okhttp thinks the two URLs are equal, they're in any case
// (and it's a simple String comparison) // (and it's a simple String comparison)
...@@ -32,7 +31,6 @@ object UrlUtils { ...@@ -32,7 +31,6 @@ object UrlUtils {
} }
} }
@JvmStatic
fun hostToDomain(host: String?): String? { fun hostToDomain(host: String?): String? {
if (host == null) if (host == null)
return null return null
...@@ -49,7 +47,6 @@ object UrlUtils { ...@@ -49,7 +47,6 @@ object UrlUtils {
host host
} }
@JvmStatic
fun omitTrailingSlash(url: HttpUrl): HttpUrl { fun omitTrailingSlash(url: HttpUrl): HttpUrl {
val idxLast = url.pathSize () - 1 val idxLast = url.pathSize () - 1
val hasTrailingSlash = url.pathSegments()[idxLast] == "" val hasTrailingSlash = url.pathSegments()[idxLast] == ""
...@@ -60,7 +57,6 @@ object UrlUtils { ...@@ -60,7 +57,6 @@ object UrlUtils {
url url
} }
@JvmStatic
fun withTrailingSlash(url: HttpUrl): HttpUrl { fun withTrailingSlash(url: HttpUrl): HttpUrl {
val idxLast = url.pathSize() - 1 val idxLast = url.pathSize() - 1
val hasTrailingSlash = url.pathSegments()[idxLast] == "" val hasTrailingSlash = url.pathSegments()[idxLast] == ""
......
...@@ -15,11 +15,11 @@ import java.io.IOException ...@@ -15,11 +15,11 @@ import java.io.IOException
object XmlUtils { object XmlUtils {
@JvmField val NS_WEBDAV = "DAV:" const val NS_WEBDAV = "DAV:"
@JvmField val NS_CALDAV = "urn:ietf:params:xml:ns:caldav" const val NS_CALDAV = "urn:ietf:params:xml:ns:caldav"
@JvmField val NS_CARDDAV = "urn:ietf:params:xml:ns:carddav" const val NS_CARDDAV = "urn:ietf:params:xml:ns:carddav"
@JvmField val NS_APPLE_ICAL = "http://apple.com/ns/ical/" const val NS_APPLE_ICAL = "http://apple.com/ns/ical/"
@JvmField val NS_CALENDARSERVER = "http://calendarserver.org/ns/" const val NS_CALENDARSERVER = "http://calendarserver.org/ns/"
private val factory: XmlPullParserFactory private val factory: XmlPullParserFactory
init { init {
...@@ -31,10 +31,7 @@ object XmlUtils { ...@@ -31,10 +31,7 @@ object XmlUtils {
} }
} }
@JvmStatic
fun newPullParser() = factory.newPullParser()!! fun newPullParser() = factory.newPullParser()!!
@JvmStatic
fun newSerializer() = factory.newSerializer()!! fun newSerializer() = factory.newSerializer()!!
......
...@@ -8,4 +8,7 @@ ...@@ -8,4 +8,7 @@
package at.bitfire.dav4android.exception package at.bitfire.dav4android.exception
open class DavException @JvmOverloads constructor(message: String, ex: Throwable? = null): Exception(message, ex) open class DavException @JvmOverloads constructor(
\ No newline at end of file message: String,
ex: Throwable? = null
): Exception(message, ex)
\ No newline at end of file
...@@ -15,8 +15,10 @@ import java.io.* ...@@ -15,8 +15,10 @@ import java.io.*
open class HttpException: Exception, Serializable { open class HttpException: Exception, Serializable {
// don't dump more than 20 kB companion object {
private val MAX_DUMP_SIZE = 20*1024 // don't dump more than 20 kB
private const val MAX_DUMP_SIZE = 20*1024
}
val status: Int val status: Int
val request: String? val request: String?
......
...@@ -8,4 +8,7 @@ ...@@ -8,4 +8,7 @@
package at.bitfire.dav4android.exception package at.bitfire.dav4android.exception
class InvalidDavResponseException(message: String, ex: Throwable? = null): DavException(message, ex) class InvalidDavResponseException @JvmOverloads constructor(
\ No newline at end of file message: String,
ex: Throwable? = null
): DavException(message, ex)
\ No newline at end of file
...@@ -8,4 +8,4 @@ ...@@ -8,4 +8,4 @@
package at.bitfire.dav4android.exception package at.bitfire.dav4android.exception
class UnsupportedDavException(message: String) : DavException(message) class UnsupportedDavException(message: String): DavException(message)
\ No newline at end of file \ No newline at end of file
...@@ -37,7 +37,7 @@ class GetETag( ...@@ -37,7 +37,7 @@ class GetETag(
// entity tag is weak (doesn't matter for us) // entity tag is weak (doesn't matter for us)
tag = it.substring(2) tag = it.substring(2)
tag = QuotedStringUtils.decodeQuotedString(tag) tag?.let { tag = QuotedStringUtils.decodeQuotedString(it) }
} }
eTag = tag eTag = tag
......
...@@ -19,11 +19,11 @@ class ResourceType: Property { ...@@ -19,11 +19,11 @@ class ResourceType: Property {
@JvmField @JvmField
val NAME = Property.Name(XmlUtils.NS_WEBDAV, "resourcetype") val NAME = Property.Name(XmlUtils.NS_WEBDAV, "resourcetype")
@JvmField val COLLECTION = Property.Name(XmlUtils.NS_WEBDAV, "collection") // WebDAV val COLLECTION = Property.Name(XmlUtils.NS_WEBDAV, "collection") // WebDAV
@JvmField val PRINCIPAL = Property.Name(XmlUtils.NS_WEBDAV, "principal") // WebDAV ACL val PRINCIPAL = Property.Name(XmlUtils.NS_WEBDAV, "principal") // WebDAV ACL
@JvmField val ADDRESSBOOK = Property.Name(XmlUtils.NS_CARDDAV, "addressbook") // CardDAV val