aboutsummaryrefslogtreecommitdiffstats
path: root/core/src/OC/backbone-webdav.js
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/OC/backbone-webdav.js')
-rw-r--r--core/src/OC/backbone-webdav.js308
1 files changed, 308 insertions, 0 deletions
diff --git a/core/src/OC/backbone-webdav.js b/core/src/OC/backbone-webdav.js
new file mode 100644
index 00000000000..318c50e8ee5
--- /dev/null
+++ b/core/src/OC/backbone-webdav.js
@@ -0,0 +1,308 @@
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+/* eslint-disable */
+import _ from 'underscore'
+import { dav } from 'davclient.js'
+
+const methodMap = {
+ create: 'POST',
+ update: 'PROPPATCH',
+ patch: 'PROPPATCH',
+ delete: 'DELETE',
+ read: 'PROPFIND'
+}
+
+// Throw an error when a URL is needed, and none is supplied.
+function urlError() {
+ throw new Error('A "url" property or function must be specified')
+}
+
+/**
+ * Convert a single propfind result to JSON
+ *
+ * @param {Object} result
+ * @param {Object} davProperties properties mapping
+ */
+function parsePropFindResult(result, davProperties) {
+ if (_.isArray(result)) {
+ return _.map(result, function(subResult) {
+ return parsePropFindResult(subResult, davProperties)
+ })
+ }
+ var props = {
+ href: result.href
+ }
+
+ _.each(result.propStat, function(propStat) {
+ if (propStat.status !== 'HTTP/1.1 200 OK') {
+ return
+ }
+
+ for (var key in propStat.properties) {
+ var propKey = key
+ if (key in davProperties) {
+ propKey = davProperties[key]
+ }
+ props[propKey] = propStat.properties[key]
+ }
+ })
+
+ if (!props.id) {
+ // parse id from href
+ props.id = parseIdFromLocation(props.href)
+ }
+
+ return props
+}
+
+/**
+ * Parse ID from location
+ *
+ * @param {string} url url
+ * @returns {string} id
+ */
+function parseIdFromLocation(url) {
+ var queryPos = url.indexOf('?')
+ if (queryPos > 0) {
+ url = url.substr(0, queryPos)
+ }
+
+ var parts = url.split('/')
+ var result
+ do {
+ result = parts[parts.length - 1]
+ parts.pop()
+ // note: first result can be empty when there is a trailing slash,
+ // so we take the part before that
+ } while (!result && parts.length > 0)
+
+ return result
+}
+
+function isSuccessStatus(status) {
+ return status >= 200 && status <= 299
+}
+
+function convertModelAttributesToDavProperties(attrs, davProperties) {
+ var props = {}
+ var key
+ for (key in attrs) {
+ var changedProp = davProperties[key]
+ var value = attrs[key]
+ if (!changedProp) {
+ console.warn('No matching DAV property for property "' + key)
+ changedProp = key
+ }
+ if (_.isBoolean(value) || _.isNumber(value)) {
+ // convert to string
+ value = '' + value
+ }
+ props[changedProp] = value
+ }
+ return props
+}
+
+function callPropFind(client, options, model, headers) {
+ return client.propFind(
+ options.url,
+ _.values(options.davProperties) || [],
+ options.depth,
+ headers
+ ).then(function(response) {
+ if (isSuccessStatus(response.status)) {
+ if (_.isFunction(options.success)) {
+ var propsMapping = _.invert(options.davProperties)
+ var results = parsePropFindResult(response.body, propsMapping)
+ if (options.depth > 0) {
+ // discard root entry
+ results.shift()
+ }
+
+ options.success(results)
+
+ }
+ } else if (_.isFunction(options.error)) {
+ options.error(response)
+ }
+ })
+}
+
+function callPropPatch(client, options, model, headers) {
+ return client.propPatch(
+ options.url,
+ convertModelAttributesToDavProperties(model.changed, options.davProperties),
+ headers
+ ).then(function(result) {
+ if (isSuccessStatus(result.status)) {
+ if (_.isFunction(options.success)) {
+ // pass the object's own values because the server
+ // does not return the updated model
+ options.success(model.toJSON())
+ }
+ } else if (_.isFunction(options.error)) {
+ options.error(result)
+ }
+ })
+
+}
+
+function callMkCol(client, options, model, headers) {
+ // call MKCOL without data, followed by PROPPATCH
+ return client.request(
+ options.type,
+ options.url,
+ headers,
+ null
+ ).then(function(result) {
+ if (!isSuccessStatus(result.status)) {
+ if (_.isFunction(options.error)) {
+ options.error(result)
+ }
+ return
+ }
+
+ callPropPatch(client, options, model, headers)
+ })
+}
+
+function callMethod(client, options, model, headers) {
+ headers['Content-Type'] = 'application/json'
+ return client.request(
+ options.type,
+ options.url,
+ headers,
+ options.data
+ ).then(function(result) {
+ if (!isSuccessStatus(result.status)) {
+ if (_.isFunction(options.error)) {
+ options.error(result)
+ }
+ return
+ }
+
+ if (_.isFunction(options.success)) {
+ if (options.type === 'PUT' || options.type === 'POST' || options.type === 'MKCOL') {
+ // pass the object's own values because the server
+ // does not return anything
+ var responseJson = result.body || model.toJSON()
+ var locationHeader = result.xhr.getResponseHeader('Content-Location')
+ if (options.type === 'POST' && locationHeader) {
+ responseJson.id = parseIdFromLocation(locationHeader)
+ }
+ options.success(responseJson)
+ return
+ }
+ // if multi-status, parse
+ if (result.status === 207) {
+ var propsMapping = _.invert(options.davProperties)
+ options.success(parsePropFindResult(result.body, propsMapping))
+ } else {
+ options.success(result.body)
+ }
+ }
+ })
+}
+
+export const davCall = (options, model) => {
+ var client = new dav.Client({
+ baseUrl: options.url,
+ xmlNamespaces: _.extend({
+ 'DAV:': 'd',
+ 'http://owncloud.org/ns': 'oc'
+ }, options.xmlNamespaces || {})
+ })
+ client.resolveUrl = function() {
+ return options.url
+ }
+ var headers = _.extend({
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'requesttoken': OC.requestToken
+ }, options.headers)
+ if (options.type === 'PROPFIND') {
+ return callPropFind(client, options, model, headers)
+ } else if (options.type === 'PROPPATCH') {
+ return callPropPatch(client, options, model, headers)
+ } else if (options.type === 'MKCOL') {
+ return callMkCol(client, options, model, headers)
+ } else {
+ return callMethod(client, options, model, headers)
+ }
+}
+
+/**
+ * DAV transport
+ */
+export const davSync = Backbone => (method, model, options) => {
+ var params = { type: methodMap[method] || method }
+ var isCollection = (model instanceof Backbone.Collection)
+
+ if (method === 'update') {
+ // if a model has an inner collection, it must define an
+ // attribute "hasInnerCollection" that evaluates to true
+ if (model.hasInnerCollection) {
+ // if the model itself is a Webdav collection, use MKCOL
+ params.type = 'MKCOL'
+ } else if (model.usePUT || (model.collection && model.collection.usePUT)) {
+ // use PUT instead of PROPPATCH
+ params.type = 'PUT'
+ }
+ }
+
+ // Ensure that we have a URL.
+ if (!options.url) {
+ params.url = _.result(model, 'url') || urlError()
+ }
+
+ // Ensure that we have the appropriate request data.
+ if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
+ params.data = JSON.stringify(options.attrs || model.toJSON(options))
+ }
+
+ // Don't process data on a non-GET request.
+ if (params.type !== 'PROPFIND') {
+ params.processData = false
+ }
+
+ if (params.type === 'PROPFIND' || params.type === 'PROPPATCH') {
+ var davProperties = model.davProperties
+ if (!davProperties && model.model) {
+ // use dav properties from model in case of collection
+ davProperties = model.model.prototype.davProperties
+ }
+ if (davProperties) {
+ if (_.isFunction(davProperties)) {
+ params.davProperties = davProperties.call(model)
+ } else {
+ params.davProperties = davProperties
+ }
+ }
+
+ params.davProperties = _.extend(params.davProperties || {}, options.davProperties)
+
+ if (_.isUndefined(options.depth)) {
+ if (isCollection) {
+ options.depth = 1
+ } else {
+ options.depth = 0
+ }
+ }
+ }
+
+ // Pass along `textStatus` and `errorThrown` from jQuery.
+ var error = options.error
+ options.error = function(xhr, textStatus, errorThrown) {
+ options.textStatus = textStatus
+ options.errorThrown = errorThrown
+ if (error) {
+ error.call(options.context, xhr, textStatus, errorThrown)
+ }
+ }
+
+ // Make the request, allowing the user to override any Ajax options.
+ var xhr = options.xhr = Backbone.davCall(_.extend(params, options), model)
+ model.trigger('request', model, xhr, options)
+ return xhr
+}