nextcloud/core/src/OC/backbone-webdav.js
Louis Chemineau 14e57bf70a Replace license information to SPDX expression
Signed-off-by: Louis Chemineau <louis@chmn.me>
2022-01-11 17:26:40 +01:00

327 lines
8.4 KiB
JavaScript

/**
* Copyright (c) 2015
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* 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
}