Commit 1587530e authored by Ricki Hirner's avatar Ricki Hirner

Rewrite to Kotlin

parent 90a31736
buildscript {
ext.kotlin_version = '1.1.2-4'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.android.tools.build:gradle:2.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
jcenter()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
}
repositories {
jcenter()
mavenCentral()
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 25
......@@ -48,10 +47,10 @@ android {
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile 'com.android.support:support-annotations:25.+'
compile 'org.apache.commons:commons-lang3:3.5'
compile 'com.squareup.okhttp3:okhttp:3.8.0'
provided 'org.projectlombok:lombok:1.16.16'
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.8.0'
......
This diff is collapsed.
package at.bitfire.dav4android;
import java.util.logging.Logger;
public class Constants {
public static Logger log = Logger.getLogger("dav4android");
}
package at.bitfire.dav4android
import java.util.logging.Logger
class Constants {
companion object {
@JvmField
var log = Logger.getLogger("dav4android")!!
}
}
......@@ -6,117 +6,123 @@
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import at.bitfire.dav4android.exception.DavException;
import at.bitfire.dav4android.exception.HttpException;
import lombok.Cleanup;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class DavAddressBook extends DavResource {
public static final MediaType
MIME_VCARD3_UTF8 = MediaType.parse("text/vcard;charset=utf-8"),
MIME_VCARD4 = MediaType.parse("text/vcard;version=4.0");
public DavAddressBook(OkHttpClient httpClient, HttpUrl location) {
super(httpClient, location);
package at.bitfire.dav4android
import at.bitfire.dav4android.exception.DavException
import at.bitfire.dav4android.exception.HttpException
import okhttp3.*
import java.io.IOException
import java.io.StringWriter
import java.util.logging.Logger
class DavAddressBook @JvmOverloads constructor(
httpClient: OkHttpClient,
location: HttpUrl,
log: Logger = Constants.log
): 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")
}
public void addressbookQuery() throws IOException, HttpException, DavException {
/**
* Sends an addressbook-query REPORT request to the resource.
* @throws IOException on I/O error
* @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 |
DAV:prop)?, filter, limit?)>
<!ELEMENT filter (prop-filter*)>
*/
XmlSerializer serializer = XmlUtils.newSerializer();
StringWriter writer = new StringWriter();
serializer.setOutput(writer);
serializer.startDocument("UTF-8", null);
serializer.setPrefix("", XmlUtils.NS_WEBDAV);
serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV);
serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook-query");
serializer.startTag(XmlUtils.NS_WEBDAV, "prop");
serializer.startTag(XmlUtils.NS_WEBDAV, "getetag");
serializer.endTag(XmlUtils.NS_WEBDAV, "getetag");
serializer.endTag(XmlUtils.NS_WEBDAV, "prop");
serializer.startTag(XmlUtils.NS_CARDDAV, "filter");
serializer.endTag(XmlUtils.NS_CARDDAV, "filter");
serializer.endTag(XmlUtils.NS_CARDDAV, "addressbook-query");
serializer.endDocument();
Response response = httpClient.newCall(new Request.Builder()
val serializer = XmlUtils.newSerializer()
val writer = StringWriter()
serializer.setOutput(writer)
serializer.startDocument("UTF-8", null)
serializer.setPrefix("", XmlUtils.NS_WEBDAV)
serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV)
serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook-query")
serializer.startTag(XmlUtils.NS_WEBDAV, "prop")
serializer.startTag(XmlUtils.NS_WEBDAV, "getetag")
serializer.endTag(XmlUtils.NS_WEBDAV, "getetag")
serializer.endTag(XmlUtils.NS_WEBDAV, "prop")
serializer.startTag(XmlUtils.NS_CARDDAV, "filter")
serializer.endTag(XmlUtils.NS_CARDDAV, "filter")
serializer.endTag(XmlUtils.NS_CARDDAV, "addressbook-query")
serializer.endDocument()
val response = httpClient.newCall(Request.Builder()
.url(location)
.method("REPORT", RequestBody.create(MIME_XML, writer.toString()))
.header("Depth", "1")
.build()).execute();
.build()).execute()
checkStatus(response, false);
assertMultiStatus(response);
checkStatus(response, false)
assertMultiStatus(response)
members.clear();
@Cleanup Reader reader = response.body().charStream();
processMultiStatus(reader);
members.clear()
response.body()?.charStream()?.use { processMultiStatus(it) }
}
public void multiget(HttpUrl[] urls, boolean vCard4) throws IOException, HttpException, DavException {
/**
* Sends an addressbook-multiget REPORT request to the resource.
* @throws IOException on I/O error
* @throws HttpException on HTTP error
* @throws DavException on DAV error
*/
@Throws(IOException::class, HttpException::class, DavException::class)
fun multiget(urls: Array<HttpUrl>, vCard4: Boolean) {
/* <!ELEMENT addressbook-multiget ((DAV:allprop |
DAV:propname |
DAV:prop)?,
DAV:href+)>
*/
XmlSerializer serializer = XmlUtils.newSerializer();
StringWriter writer = new StringWriter();
serializer.setOutput(writer);
serializer.startDocument("UTF-8", null);
serializer.setPrefix("", XmlUtils.NS_WEBDAV);
serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV);
serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook-multiget");
serializer.startTag(XmlUtils.NS_WEBDAV, "prop");
serializer.startTag(XmlUtils.NS_WEBDAV, "getcontenttype"); // to determine the character set
serializer.endTag(XmlUtils.NS_WEBDAV, "getcontenttype");
serializer.startTag(XmlUtils.NS_WEBDAV, "getetag");
serializer.endTag(XmlUtils.NS_WEBDAV, "getetag");
serializer.startTag(XmlUtils.NS_CARDDAV, "address-data");
val serializer = XmlUtils.newSerializer()
val writer = StringWriter()
serializer.setOutput(writer)
serializer.startDocument("UTF-8", null)
serializer.setPrefix("", XmlUtils.NS_WEBDAV)
serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV)
serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook-multiget")
serializer.startTag(XmlUtils.NS_WEBDAV, "prop")
serializer.startTag(XmlUtils.NS_WEBDAV, "getcontenttype") // to determine the character set
serializer.endTag(XmlUtils.NS_WEBDAV, "getcontenttype")
serializer.startTag(XmlUtils.NS_WEBDAV, "getetag")
serializer.endTag(XmlUtils.NS_WEBDAV, "getetag")
serializer.startTag(XmlUtils.NS_CARDDAV, "address-data")
if (vCard4) {
serializer.attribute(null, "content-type", "text/vcard");
serializer.attribute(null, "version", "4.0");
serializer.attribute(null, "content-type", "text/vcard")
serializer.attribute(null, "version", "4.0")
}
serializer.endTag(XmlUtils.NS_CARDDAV, "address-data");
serializer.endTag(XmlUtils.NS_WEBDAV, "prop");
for (HttpUrl url : urls) {
serializer.startTag(XmlUtils.NS_WEBDAV, "href");
serializer.text(url.encodedPath());
serializer.endTag(XmlUtils.NS_WEBDAV, "href");
serializer.endTag(XmlUtils.NS_CARDDAV, "address-data")
serializer.endTag(XmlUtils.NS_WEBDAV, "prop")
for (url in urls) {
serializer.startTag(XmlUtils.NS_WEBDAV, "href")
serializer.text(url.encodedPath())
serializer.endTag(XmlUtils.NS_WEBDAV, "href")
}
serializer.endTag(XmlUtils.NS_CARDDAV, "addressbook-multiget");
serializer.endDocument();
serializer.endTag(XmlUtils.NS_CARDDAV, "addressbook-multiget")
serializer.endDocument()
Response response = httpClient.newCall(new Request.Builder()
val response = httpClient.newCall(Request.Builder()
.url(location)
.method("REPORT", RequestBody.create(MIME_XML, writer.toString()))
.header("Depth", "0") // "The request MUST include a Depth: 0 header [...]"
.build()).execute();
checkStatus(response, false);
assertMultiStatus(response);
.build()).execute()
members.clear();
checkStatus(response, false)
assertMultiStatus(response)
@Cleanup Reader reader = response.body().charStream();
processMultiStatus(reader);
members.clear()
response.body()?.charStream()?.use { processMultiStatus(it) }
}
}
package at.bitfire.dav4android;
import java.util.logging.Logger;
import lombok.NonNull;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
public class DavCollection extends DavResource {
public DavCollection(@NonNull OkHttpClient httpClient, @NonNull HttpUrl location) {
super(httpClient, location);
}
}
package at.bitfire.dav4android;
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import java.util.logging.Logger
open class DavCollection(
httpClient: OkHttpClient,
location: HttpUrl,
log: Logger = Constants.log
): DavResource(httpClient, location, log) {
}
This diff is collapsed.
This diff is collapsed.
/*
* 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.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
import okhttp3.Response;
public class HttpUtils {
private static final Pattern authSchemeWithParam = Pattern.compile("^([^ \"]+) +(.*)$");
public static String[] listHeader(Response response, String name) {
String value = StringUtils.join(response.headers(name), ",");
return StringUtils.splitByWholeSeparator(value, ",");
}
public static List<AuthScheme> parseWwwAuthenticate(String[] wwwAuths) {
/* WWW-Authenticate = "WWW-Authenticate" ":" 1#challenge
challenge = auth-scheme 1*SP 1#auth-param
auth-scheme = token
auth-param = token "=" ( token | quoted-string )
We call the auth-param tokens: <name>=<value>
token = 1*<any CHAR except CTLs or separators>
separators = "(" | ")" | "<" | ">" | "@"
| "," | ";" | ":" | "\" | <">
| "/" | "[" | "]" | "?" | "="
| "{" | "}" | SP | HT
quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
qdtext = <any TEXT except <">>
quoted-pair = "\" CHAR
*/
List<AuthScheme> schemes = new LinkedList<>();
for (String wwwAuth : wwwAuths) {
// Step 1: tokenize by ',', but take into account that auth-param values may contain quoted-pair values with ',' in it (these ',' have to be ignored)
// Auth-scheme and auth-param names are tokens and thus must not contain the '"' separator.
List<String> tokens = new LinkedList<>();
StringBuilder token = new StringBuilder();
boolean inQuotes = false;
int len = wwwAuth.length();
for (int i = 0; i < len; i++) {
char c = wwwAuth.charAt(i);
boolean literal = false;
if (c == '"')
inQuotes = !inQuotes;
else if (inQuotes && c == '\\' && i+1 < len) {
token.append(c);
c = wwwAuth.charAt(++i);
literal = true;
}
if (c == ',' && !inQuotes && !literal) {
tokens.add(token.toString());
token = new StringBuilder();
} else
token.append(c);
}
if (token.length() != 0)
tokens.add(token.toString());
/* Step 2: determine token type after trimming:
"<authSchemes> <auth-param>" new auth scheme + 1 param
"<auth-param>" add param to previous auth scheme
Take into account that the second type may contain quoted spaces.
The auth scheme name must not contain separators (including quotes).
*/
List<AuthScheme> authSchemes = new LinkedList<>();
List<String> authParams = new LinkedList<>();
AuthScheme scheme = null;
for (String s : tokens) {
s = s.trim();
Matcher matcher = authSchemeWithParam.matcher(s);
if (matcher.matches()) {
// auth-scheme with auth-param
schemes.add(scheme = new AuthScheme(matcher.group(1)));
scheme.addRawParam(matcher.group(2));
} else if (scheme != null) {
// if there was an auth-scheme before, this must be an auth-param
scheme.addRawParam(s);
} else {
// there was not auth-scheme before, so this must be an auth-scheme
schemes.add(scheme = new AuthScheme(s));
}
}
}
Constants.log.finer("Server authentication schemes: ");
for (AuthScheme scheme : schemes)
Constants.log.finer(" - " + scheme);
return schemes;
}
@RequiredArgsConstructor
public static class AuthScheme {
Pattern nameValue = Pattern.compile("^([^=]+)=(.*)$");
public final String name;
/** Map (name -> value) authentication parameters. Names are always lower-case. */
public final Map<String, String> params = new HashMap<>();
public final List<String> unnamedParams = new LinkedList<>();
public void addRawParam(String authParam) {
Matcher m = nameValue.matcher(authParam);
if (m.matches()) {
String name = m.group(1),
value = m.group(2);
int len = value.length();
if (value.charAt(0) == '"' && value.charAt(len-1) == '"') {
// quoted-string
value = value
.substring(1, len-1)
.replace("\\\"", "\"");
}
params.put(name.toLowerCase(), value);
} else
unnamedParams.add(authParam);
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append(name + "(");
for (String name : params.keySet())
s.append(name + "=[" + params.get(name) + "],");
s.append(")");
return s.toString();
}
}
}
/*
* 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 okhttp3.Response
import org.apache.commons.lang3.StringUtils
import java.util.LinkedList
import java.util.regex.Pattern
import kotlin.collections.HashMap
class HttpUtils {
companion object {
private val authSchemeWithParam = Pattern.compile("^([^ \"]+) +(.*)$")
@JvmStatic
fun listHeader(response: Response, name: String): Array<String> {
val value = StringUtils.join(response.headers(name), ",")
return StringUtils.splitByWholeSeparator(value, ",")
}
@JvmStatic
fun parseWwwAuthenticate(wwwAuths: List<String>): List<AuthScheme> {
/* WWW-Authenticate = "WWW-Authenticate" ":" 1#challenge
challenge = auth-scheme 1*SP 1#auth-param
auth-scheme = token
auth-param = token "=" ( token | quoted-string )
We call the auth-param tokens: <name>=<value>
token = 1*<any CHAR except CTLs or separators>
separators = "(" | ")" | "<" | ">" | "@"
| "," | ";" | ":" | "\" | <">
| "/" | "[" | "]" | "?" | "="
| "{" | "}" | SP | HT
quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
qdtext = <any TEXT except <">>
quoted-pair = "\" CHAR
*/
val schemes = LinkedList<AuthScheme>()
for (wwwAuth in wwwAuths) {
// Step 1: tokenize by ',', but take into account that auth-param values may contain quoted-pair values with ',' in it (these ',' have to be ignored)
// Auth-scheme and auth-param names are tokens and thus must not contain the '"' separator.
val tokens = LinkedList<String>()
var token = StringBuilder()
var inQuotes = false
val len = wwwAuth.length
var i = 0
while (i < len) {
var c = wwwAuth[i]
var literal = false
if (c == '"')
inQuotes = !inQuotes
else if (inQuotes && c == '\\' && i + 1 < len) {
token.append(c)
c = wwwAuth[++i]
literal = true
}
if (c == ',' && !inQuotes && !literal) {
tokens.add(token.toString())
token = StringBuilder()
} else
token.append(c)
i++
}
if (token.isNotEmpty())
tokens.add(token.toString())
/* Step 2: determine token type after trimming:
"<authSchemes> <auth-param>" new auth scheme + 1 param
"<auth-param>" add param to previous auth scheme
Take into account that the second type may contain quoted spaces.
The auth scheme name must not contain separators (including quotes).
*/
var scheme: AuthScheme? = null
for (s in tokens) {
@Suppress("NAME_SHADOWING")
val s: String = s.trim()
val matcher = authSchemeWithParam.matcher(s)
when {
matcher.matches() -> {
// auth-scheme with auth-param
scheme = AuthScheme(matcher.group(1))
schemes.add(scheme)
scheme.addRawParam(matcher.group(2))
}
scheme != null ->
// if there was an auth-scheme before, this must be an auth-param
scheme.addRawParam(s)
else -> {
// there was not auth-scheme before, so this must be an auth-scheme
scheme = AuthScheme(s)
schemes.add(scheme)
}
}
}
}
Constants.log.finer("Server authentication schemes: ")
for (scheme in schemes)
Constants.log.finer(" - $scheme")
return schemes
}
}
class AuthScheme(
val name: String
) {
val nameValue = Pattern.compile("^([^=]+)=(.*)$")!!
/** Map (name -> value) authentication parameters. Names are always lower-case. */
val params = HashMap<String, String>()
val unnamedParams = LinkedList<String>()
fun addRawParam(authParam: String) {
val m = nameValue.matcher(authParam)
if (m.matches()) {
val name = m.group(1)
var value = m.group(2)
val len = value.length
if (value[0] == '"' && value[len - 1] == '"') {
// quoted-string
value = value
.substring(1, len - 1)
.replace("\\\"", "\"")
}
params.put(name.toLowerCase(), value)
} else
unnamedParams.add(authParam)
}
override fun toString(): String {
val s = StringBuilder()
s.append(name + "(")
for ((name, value) in params)
s.append("$name=[$value],")
s.append(")")
return s.toString()
}
}
}
/*
* 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 android.text.TextUtils;
import lombok.RequiredArgsConstructor;
public interface Property {
@RequiredArgsConstructor
class Name {
public final String namespace;
public final String name;
@Override
public boolean equals(Object o) {
if (o instanceof Name) {
Name n = (Name)o;