Commit c1a23985 authored by Ricki Hirner's avatar Ricki Hirner 🐑
Browse files

Use Property.Name more consequently

parent 0e99ffde
Pipeline #161786785 passed with stages
in 2 minutes and 5 seconds
......@@ -6,8 +6,12 @@
package at.bitfire.dav4jvm
import at.bitfire.dav4jvm.XmlUtils.insertTag
import at.bitfire.dav4jvm.exception.DavException
import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.dav4jvm.property.AddressData
import at.bitfire.dav4jvm.property.GetContentType
import at.bitfire.dav4jvm.property.GetETag
import okhttp3.HttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
......@@ -26,6 +30,10 @@ class DavAddressBook @JvmOverloads constructor(
companion object {
val MIME_VCARD3_UTF8 = "text/vcard;charset=utf-8".toMediaType()
val MIME_VCARD4 = "text/vcard;version=4.0".toMediaType()
val ADDRESSBOOK_QUERY = Property.Name(XmlUtils.NS_CARDDAV, "addressbook-query")
val ADDRESSBOOK_MULTIGET = Property.Name(XmlUtils.NS_CARDDAV, "addressbook-multiget")
val FILTER = Property.Name(XmlUtils.NS_CARDDAV, "filter")
}
/**
......@@ -52,14 +60,12 @@ class DavAddressBook @JvmOverloads constructor(
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.insertTag(ADDRESSBOOK_QUERY) {
insertTag(PROP) {
insertTag(GetETag.NAME)
}
insertTag(FILTER)
}
serializer.endDocument()
followRedirects {
......@@ -99,25 +105,22 @@ class DavAddressBook @JvmOverloads constructor(
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.insertTag(ADDRESSBOOK_MULTIGET) {
insertTag(PROP) {
insertTag(GetContentType.NAME)
insertTag(GetETag.NAME)
insertTag(AddressData.NAME) {
if (vCard4) {
attribute(null, AddressData.CONTENT_TYPE, "text/vcard")
attribute(null, AddressData.VERSION, "4.0")
}
}
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")
for (url in urls)
insertTag(HREF) {
text(url.encodedPath)
}
}
serializer.endDocument()
followRedirects {
......
......@@ -6,6 +6,7 @@
package at.bitfire.dav4jvm
import at.bitfire.dav4jvm.XmlUtils.insertTag
import at.bitfire.dav4jvm.exception.DavException
import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.dav4jvm.property.CalendarData
......@@ -17,7 +18,6 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.xmlpull.v1.XmlSerializer
import java.io.IOException
import java.io.StringWriter
import java.text.SimpleDateFormat
......@@ -34,9 +34,19 @@ class DavCalendar @JvmOverloads constructor(
val MIME_ICALENDAR = "text/calendar".toMediaType()
val MIME_ICALENDAR_UTF8 = "text/calendar;charset=utf-8".toMediaType()
val CALENDAR_QUERY = Property.Name(XmlUtils.NS_CALDAV, "calendar-query")
val CALENDAR_MULTIGET = Property.Name(XmlUtils.NS_CALDAV, "calendar-multiget")
val FILTER = Property.Name(XmlUtils.NS_CALDAV, "filter")
val COMP_FILTER = Property.Name(XmlUtils.NS_CALDAV, "comp-filter")
const val COMP_FILTER_NAME = "name"
val TIME_RANGE = Property.Name(XmlUtils.NS_CALDAV, "time-range")
const val TIME_RANGE_START = "start"
const val TIME_RANGE_END = "end"
private val timeFormatUTC = SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US)
init {
timeFormatUTC.timeZone = TimeZone.getTimeZone("UTC")
timeFormatUTC.timeZone = TimeZone.getTimeZone("Etc/UTC")
}
}
......@@ -74,28 +84,27 @@ class DavCalendar @JvmOverloads constructor(
serializer.startDocument("UTF-8", null)
serializer.setPrefix("", XmlUtils.NS_WEBDAV)
serializer.setPrefix("CAL", XmlUtils.NS_CALDAV)
serializer.startTag(XmlUtils.NS_CALDAV, "calendar-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_CALDAV, "filter")
serializer.startTag(XmlUtils.NS_CALDAV, "comp-filter")
serializer.attribute(null, "name", "VCALENDAR")
serializer.startTag(XmlUtils.NS_CALDAV, "comp-filter")
serializer.attribute(null, "name", component)
if (start != null || end != null) {
serializer.startTag(XmlUtils.NS_CALDAV, "time-range")
if (start != null)
serializer.attribute(null, "start", timeFormatUTC.format(start))
if (end != null)
serializer.attribute(null, "end", timeFormatUTC.format(end))
serializer.endTag(XmlUtils.NS_CALDAV, "time-range")
serializer.insertTag(CALENDAR_QUERY) {
insertTag(PROP) {
insertTag(GetETag.NAME)
}
insertTag(FILTER) {
insertTag(COMP_FILTER) {
attribute(null, COMP_FILTER_NAME, "VCALENDAR")
insertTag(COMP_FILTER) {
attribute(null, COMP_FILTER_NAME, component)
if (start != null || end != null) {
insertTag(TIME_RANGE) {
if (start != null)
attribute(null, TIME_RANGE_START, timeFormatUTC.format(start))
if (end != null)
attribute(null, TIME_RANGE_END, timeFormatUTC.format(end))
}
}
}
serializer.endTag(XmlUtils.NS_CALDAV, "comp-filter")
serializer.endTag(XmlUtils.NS_CALDAV, "comp-filter")
serializer.endTag(XmlUtils.NS_CALDAV, "filter")
serializer.endTag(XmlUtils.NS_CALDAV, "calendar-query")
}
}
}
serializer.endDocument()
followRedirects {
......@@ -124,11 +133,6 @@ class DavCalendar @JvmOverloads constructor(
* @throws DavException on WebDAV error
*/
fun multiget(urls: List<HttpUrl>, callback: DavResponseCallback): List<Property> {
fun XmlSerializer.emptyTag(propertyName: Property.Name) {
startTag(propertyName.namespace, propertyName.name)
endTag(propertyName.namespace, propertyName.name)
}
/* <!ELEMENT calendar-multiget ((DAV:allprop |
DAV:propname |
DAV:prop)?, DAV:href+)>
......@@ -139,19 +143,18 @@ class DavCalendar @JvmOverloads constructor(
serializer.startDocument("UTF-8", null)
serializer.setPrefix("", XmlUtils.NS_WEBDAV)
serializer.setPrefix("CAL", XmlUtils.NS_CALDAV)
serializer.startTag(XmlUtils.NS_CALDAV, "calendar-multiget")
serializer.startTag(XmlUtils.NS_WEBDAV, "prop")
serializer.emptyTag(GetContentType.NAME) // to determine the character set
serializer.emptyTag(GetETag.NAME)
serializer.emptyTag(ScheduleTag.NAME)
serializer.emptyTag(CalendarData.NAME)
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.insertTag(CALENDAR_MULTIGET) {
insertTag(PROP) {
insertTag(GetContentType.NAME) // to determine the character set
insertTag(GetETag.NAME)
insertTag(ScheduleTag.NAME)
insertTag(CalendarData.NAME)
}
serializer.endTag(XmlUtils.NS_CALDAV, "calendar-multiget")
for (url in urls)
insertTag(HREF) {
serializer.text(url.encodedPath)
}
}
serializer.endDocument()
followRedirects {
......
......@@ -6,6 +6,7 @@
package at.bitfire.dav4jvm
import at.bitfire.dav4jvm.XmlUtils.insertTag
import at.bitfire.dav4jvm.exception.DavException
import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.dav4jvm.property.SyncToken
......@@ -25,6 +26,13 @@ open class DavCollection @JvmOverloads constructor(
log: Logger = Dav4jvm.log
): DavResource(httpClient, location, log) {
companion object {
val SYNC_COLLECTION = Property.Name(XmlUtils.NS_WEBDAV, "sync-collection")
val SYNC_LEVEL = Property.Name(XmlUtils.NS_WEBDAV, "sync-level")
val LIMIT = Property.Name(XmlUtils.NS_WEBDAV, "limit")
val NRESULTS = Property.Name(XmlUtils.NS_WEBDAV, "nresults")
}
/**
* Sends a REPORT sync-collection request.
*
......@@ -57,27 +65,25 @@ open class DavCollection @JvmOverloads constructor(
serializer.setOutput(writer)
serializer.startDocument("UTF-8", null)
serializer.setPrefix("", XmlUtils.NS_WEBDAV)
serializer.startTag(XmlUtils.NS_WEBDAV, "sync-collection")
serializer.startTag(SyncToken.NAME.namespace, SyncToken.NAME.name)
syncToken?.let { serializer.text(it) }
serializer.endTag(SyncToken.NAME.namespace, SyncToken.NAME.name)
serializer.startTag(XmlUtils.NS_WEBDAV, "sync-level")
serializer.text(if (infiniteDepth) "infinite" else "1")
serializer.endTag(XmlUtils.NS_WEBDAV, "sync-level")
limit?.let { nresults ->
serializer.startTag(XmlUtils.NS_WEBDAV, "limit")
serializer.startTag(XmlUtils.NS_WEBDAV, "nresults")
serializer.text(nresults.toString())
serializer.endTag(XmlUtils.NS_WEBDAV, "nresults")
serializer.endTag(XmlUtils.NS_WEBDAV, "limit")
serializer.insertTag(SYNC_COLLECTION) {
insertTag(SyncToken.NAME) {
if (syncToken != null)
text(syncToken)
}
serializer.startTag(XmlUtils.NS_WEBDAV, "prop")
properties.forEach {
serializer.startTag(it.namespace, it.name)
serializer.endTag(it.namespace, it.name)
insertTag(SYNC_LEVEL) {
text(if (infiniteDepth) "infinite" else "1")
}
serializer.endTag(XmlUtils.NS_WEBDAV, "prop")
serializer.endTag(XmlUtils.NS_WEBDAV, "sync-collection")
if (limit != null)
insertTag(LIMIT) {
insertTag(NRESULTS) {
text(limit.toString())
}
}
insertTag(PROP) {
for (prop in properties)
insertTag(prop)
}
}
serializer.endDocument()
followRedirects {
......
......@@ -6,6 +6,8 @@
package at.bitfire.dav4jvm
import at.bitfire.dav4jvm.XmlUtils.insertTag
import at.bitfire.dav4jvm.XmlUtils.propertyName
import at.bitfire.dav4jvm.exception.*
import at.bitfire.dav4jvm.property.SyncToken
import okhttp3.*
......@@ -20,6 +22,7 @@ import java.io.Reader
import java.io.StringWriter
import java.net.HttpURLConnection
import java.util.logging.Logger
import at.bitfire.dav4jvm.Response as DavResponse
/**
* Represents a WebDAV resource at the given location and allows WebDAV
......@@ -41,7 +44,12 @@ open class DavResource @JvmOverloads constructor(
companion object {
const val MAX_REDIRECTS = 5
val MIME_XML = "application/xml; charset=utf-8".toMediaType()
val PROPFIND = Property.Name(XmlUtils.NS_WEBDAV, "propfind")
val PROP = Property.Name(XmlUtils.NS_WEBDAV, "prop")
val HREF = Property.Name(XmlUtils.NS_WEBDAV, "href")
}
/**
......@@ -314,14 +322,12 @@ open class DavResource @JvmOverloads constructor(
serializer.setPrefix("CAL", XmlUtils.NS_CALDAV)
serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV)
serializer.startDocument("UTF-8", null)
serializer.startTag(XmlUtils.NS_WEBDAV, "propfind")
serializer.startTag(XmlUtils.NS_WEBDAV, "prop")
for (prop in reqProp) {
serializer.startTag(prop.namespace, prop.name)
serializer.endTag(prop.namespace, prop.name)
serializer.insertTag(PROPFIND) {
insertTag(PROP) {
for (prop in reqProp)
insertTag(prop)
}
}
serializer.endTag(XmlUtils.NS_WEBDAV, "prop")
serializer.endTag(XmlUtils.NS_WEBDAV, "propfind")
serializer.endDocument()
followRedirects {
......@@ -386,7 +392,7 @@ open class DavResource @JvmOverloads constructor(
for (attempt in 1..MAX_REDIRECTS) {
response = sendRequest()
if (response.isRedirect)
// handle 3xx Redirection
// handle 3xx Redirection
response.use {
val target = it.header("Location")?.let { location.resolve(it) }
if (target != null) {
......@@ -468,11 +474,11 @@ open class DavResource @JvmOverloads constructor(
val depth = parser.depth
var eventType = parser.eventType
while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) {
if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1 && parser.namespace == XmlUtils.NS_WEBDAV)
when (parser.name) {
"response" ->
if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1)
when (parser.propertyName()) {
DavResponse.RESPONSE ->
at.bitfire.dav4jvm.Response.parse(parser, location, callback)
"sync-token" ->
SyncToken.NAME ->
XmlUtils.readText(parser)?.let {
responseProperties += SyncToken(it)
}
......@@ -489,7 +495,7 @@ open class DavResource @JvmOverloads constructor(
var eventType = parser.eventType
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG && parser.depth == 1)
if (parser.namespace == XmlUtils.NS_WEBDAV && parser.name == "multistatus")
if (parser.propertyName() == DavResponse.MULTISTATUS)
return parseMultiStatus()
// ignore further <multistatus> elements
eventType = parser.next()
......
......@@ -23,6 +23,8 @@ class Error(
companion object {
val NAME = Property.Name(XmlUtils.NS_WEBDAV, "error")
fun parseError(parser: XmlPullParser): List<Error> {
val names = mutableSetOf<Property.Name>()
......
......@@ -8,6 +8,8 @@
package at.bitfire.dav4jvm
import at.bitfire.dav4jvm.Response.Companion.STATUS
import at.bitfire.dav4jvm.XmlUtils.propertyName
import okhttp3.Protocol
import okhttp3.internal.http.StatusLine
import org.xmlpull.v1.XmlPullParser
......@@ -25,10 +27,11 @@ data class PropStat(
val error: List<Error>? = null
) {
fun isSuccess() = status.code/100 == 2
companion object {
@JvmField
val NAME = Property.Name(XmlUtils.NS_WEBDAV, "propstat")
private val ASSUMING_OK = StatusLine(Protocol.HTTP_1_1, 200, "Assuming OK")
private val INVALID_STATUS = StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line")
......@@ -41,18 +44,17 @@ data class PropStat(
var eventType = parser.eventType
while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) {
if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1)
if (parser.namespace == XmlUtils.NS_WEBDAV)
when (parser.name) {
"prop" ->
prop.addAll(Property.parse(parser))
"status" ->
status = try {
StatusLine.parse(parser.nextText())
} catch (e: ProtocolException) {
// invalid status line, treat as 500 Internal Server Error
INVALID_STATUS
}
}
when (parser.propertyName()) {
DavResource.PROP ->
prop.addAll(Property.parse(parser))
STATUS ->
status = try {
StatusLine.parse(parser.nextText())
} catch (e: ProtocolException) {
// invalid status line, treat as 500 Internal Server Error
INVALID_STATUS
}
}
eventType = parser.next()
}
......@@ -61,4 +63,7 @@ data class PropStat(
}
fun isSuccess() = status.code/100 == 2
}
\ No newline at end of file
......@@ -19,21 +19,13 @@ import java.util.*
*/
interface Property {
class Name(
data class Name(
val namespace: String,
val name: String
): Serializable {
override fun equals(other: Any?): Boolean {
return if (other is Name)
namespace == other.namespace && name == other.name
else
super.equals(other)
}
override fun hashCode() = namespace.hashCode() xor name.hashCode()
override fun toString() = "$namespace:$name"
override fun toString() = "$namespace$name"
}
companion object {
......
......@@ -9,6 +9,7 @@
package at.bitfire.dav4jvm
import at.bitfire.dav4jvm.Dav4jvm.log
import at.bitfire.dav4jvm.XmlUtils.propertyName
import at.bitfire.dav4jvm.property.ResourceType
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
......@@ -93,6 +94,11 @@ data class Response(
companion object {
val RESPONSE = Property.Name(XmlUtils.NS_WEBDAV, "response")
val MULTISTATUS = Property.Name(XmlUtils.NS_WEBDAV, "multistatus")
val STATUS = Property.Name(XmlUtils.NS_WEBDAV, "status")
val LOCATION = Property.Name(XmlUtils.NS_WEBDAV, "location")
/**
* Parses an XML response element.
*/
......@@ -108,45 +114,44 @@ data class Response(
var eventType = parser.eventType
while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) {
if (eventType == XmlPullParser.START_TAG && parser.depth == depth+1)
if (parser.namespace == XmlUtils.NS_WEBDAV)
when (parser.name) {
"href" -> {
var sHref = parser.nextText()
if (!sHref.startsWith("/")) {
/* According to RFC 4918 8.3 URL Handling, only absolute paths are allowed as relative
URLs. However, some servers reply with relative paths. */
val firstColon = sHref.indexOf(':')
if (firstColon != -1) {
/* There are some servers which return not only relative paths, but relative paths like "a:b.vcf",
which would be interpreted as scheme: "a", scheme-specific part: "b.vcf" normally.
For maximum compatibility, we prefix all relative paths which contain ":" (but not "://"),
with "./" to allow resolving by HttpUrl. */
var hierarchical = false
try {
if (sHref.substring(firstColon, firstColon + 3) == "://")
hierarchical = true
} catch (e: IndexOutOfBoundsException) {
// no "://"
}
if (!hierarchical)
sHref = "./$sHref"
when (parser.propertyName()) {
DavResource.HREF -> {
var sHref = parser.nextText()
if (!sHref.startsWith("/")) {
/* According to RFC 4918 8.3 URL Handling, only absolute paths are allowed as relative
URLs. However, some servers reply with relative paths. */
val firstColon = sHref.indexOf(':')
if (firstColon != -1) {
/* There are some servers which return not only relative paths, but relative paths like "a:b.vcf",
which would be interpreted as scheme: "a", scheme-specific part: "b.vcf" normally.
For maximum compatibility, we prefix all relative paths which contain ":" (but not "://"),
with "./" to allow resolving by HttpUrl. */
var hierarchical = false
try {
if (sHref.substring(firstColon, firstColon + 3) == "://")
hierarchical = true
} catch (e: IndexOutOfBoundsException) {
// no "://"
}
if (!hierarchical)
sHref = "./$sHref"
}
href = location.resolve(sHref)
}
"status" ->
status = try {
StatusLine.parse(parser.nextText())
} catch(e: ProtocolException) {
log.warning("Invalid status line, treating as HTTP error 500")
StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line")
}
"propstat" ->
PropStat.parse(parser).let { propStat += it }
"error" ->
error = Error.parseError(parser)
"location" ->
newLocation = parser.nextText().toHttpUrlOrNull()
href = location.resolve(sHref)
}
STATUS ->
status = try {
StatusLine.parse(parser.nextText())
} catch(e: ProtocolException) {
log.warning("Invalid status line, treating as HTTP error 500")
StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line")
}
PropStat.NAME ->
PropStat.parse(parser).let { propStat += it }
Error.NAME ->
error = Error.parseError(parser)
LOCATION ->
newLocation = parser.nextText().toHttpUrlOrNull()
}
eventType = parser.next()
}
......
......@@ -9,6 +9,7 @@ package at.bitfire.dav4jvm
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory
import org.xmlpull.v1.XmlSerializer
import java.io.IOException
object XmlUtils {
......@@ -34,12 +35,11 @@ object XmlUtils {
@Throws(IOException::class, XmlPullParserException::class)