1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-30 10:36:35 +02:00
joplin/packages/lib/WebDavApi.js

477 lines
18 KiB
JavaScript
Raw Normal View History

const Logger = require('@joplin/utils/Logger').default;
2020-11-05 18:58:23 +02:00
const shim = require('./shim').default;
2018-01-21 19:01:37 +02:00
const parseXmlString = require('xml2js').parseString;
const JoplinError = require('./JoplinError').default;
const URL = require('url-parse');
const { _ } = require('./locale');
2020-11-05 18:58:23 +02:00
const { rtrimSlashes } = require('./path-utils');
const base64 = require('base-64');
const { ltrimSlashes } = require('./path-utils');
2018-01-21 19:01:37 +02:00
// Note that the d: namespace (the DAV namespace) is specific to Nextcloud. The RFC for example uses "D:" however
// we make all the tags and attributes lowercase so we handle both the Nextcloud style and RFC. Hopefully other
// implementations use the same namespaces. If not, extra processing can be done in `nameProcessor`, for
// example to convert a custom namespace to "d:" so that it can be used by the rest of the code.
// In general, we should only deal with things in "d:", which is the standard DAV namespace.
class WebDavApi {
2018-01-25 21:01:14 +02:00
constructor(options) {
2018-01-21 19:01:37 +02:00
this.logger_ = new Logger();
this.options_ = options;
this.lastRequests_ = [];
}
logRequest_(request, responseText) {
if (this.lastRequests_.length > 10) this.lastRequests_.splice(0, 1);
const serializeRequest = (r) => {
const options = { ...r.options };
if (typeof options.body === 'string') options.body = options.body.substr(0, 4096);
const output = [];
output.push(options.method ? options.method : 'GET');
output.push(r.url);
options.headers = { ...options.headers };
if (options.headers['Authorization']) options.headers['Authorization'] = '********';
delete options.method;
delete options.agent;
output.push(JSON.stringify(options));
return output.join(' ');
};
this.lastRequests_.push({
timestamp: Date.now(),
request: serializeRequest(request),
response: responseText ? responseText.substr(0, 4096) : '',
});
}
lastRequests() {
return this.lastRequests_;
}
clearLastRequests() {
this.lastRequests_ = [];
2018-01-21 19:01:37 +02:00
}
setLogger(l) {
this.logger_ = l;
}
logger() {
return this.logger_;
}
authToken() {
2018-01-25 21:01:14 +02:00
if (!this.options_.username() || !this.options_.password()) return null;
try {
// Note: Non-ASCII passwords will throw an error about Latin1 characters - https://github.com/laurent22/joplin/issues/246
// Tried various things like the below, but it didn't work on React Native:
2019-10-09 21:35:13 +02:00
// return base64.encode(utf8.encode(this.options_.username() + ':' + this.options_.password()));
2019-09-19 23:51:18 +02:00
return base64.encode(`${this.options_.username()}:${this.options_.password()}`);
} catch (error) {
2019-09-19 23:51:18 +02:00
error.message = `Cannot encode username/password: ${error.message}`;
throw error;
}
2018-01-21 19:01:37 +02:00
}
baseUrl() {
return rtrimSlashes(this.options_.baseUrl());
2018-01-25 21:01:14 +02:00
}
relativeBaseUrl() {
const url = new URL(this.baseUrl());
return url.pathname + url.query;
2018-01-21 19:01:37 +02:00
}
async xmlToJson(xml) {
const davNamespaces = []; // Yes, there can be more than one... xmlns:a="DAV:" xmlns:D="DAV:"
2018-01-21 19:01:37 +02:00
const nameProcessor = name => {
if (name.indexOf('xmlns') !== 0) {
// Check if the current name is within the DAV namespace. If it is, normalise it
// by moving it to the "d:" namespace, which is what all the functions are using.
const p = name.split(':');
if (p.length === 2) {
const ns = p[0];
if (davNamespaces.indexOf(ns) >= 0) {
2019-09-19 23:51:18 +02:00
name = `d:${p[1]}`;
}
} else if (p.length === 1 && davNamespaces.indexOf('') >= 0) {
// Also handle the case where the namespace alias is empty.
// https://github.com/laurent22/joplin/issues/2002
name = `d:${name}`;
}
}
2018-01-21 19:01:37 +02:00
return name.toLowerCase();
};
const attrValueProcessor = (value, name) => {
// The namespace is ususally specified like so: xmlns:D="DAV:" ("D" being the alias used in the tag names)
// In some cases, the namespace can also be empty like so: "xmlns=DAV". In this case, the tags will have
// no namespace so instead of <d:prop> will have just <prop>. This is handled above in nameProcessor()
if (value.toLowerCase() === 'dav:') {
const p = name.split(':');
davNamespaces.push(p.length === 2 ? p[p.length - 1] : '');
}
2019-07-29 15:43:53 +02:00
};
2018-01-21 19:01:37 +02:00
const options = {
tagNameProcessors: [nameProcessor],
attrNameProcessors: [nameProcessor],
2019-07-29 15:43:53 +02:00
attrValueProcessors: [attrValueProcessor],
};
2018-01-21 19:01:37 +02:00
return new Promise((resolve) => {
2018-01-21 19:01:37 +02:00
parseXmlString(xml, options, (error, result) => {
if (error) {
2018-01-23 22:10:20 +02:00
resolve(null); // Error handled by caller which will display the XML text (or plain text) if null is returned from this function
2018-01-21 19:01:37 +02:00
return;
}
resolve(result);
});
});
}
2019-07-29 15:43:53 +02:00
valueFromJson(json, keys, type) {
2018-01-21 19:01:37 +02:00
let output = json;
2018-01-25 21:01:14 +02:00
2018-01-21 19:01:37 +02:00
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
2018-01-25 21:01:14 +02:00
// console.info(key, typeof key, typeof output, typeof output === 'object' && (key in output), Array.isArray(output));
if (typeof key === 'number' && !Array.isArray(output)) return null;
if (typeof key === 'string' && (typeof output !== 'object' || !(key in output))) return null;
2018-01-21 19:01:37 +02:00
output = output[key];
}
if (type === 'string') {
// If the XML has not attribute the value is directly a string
// If the XML node has attributes, the value is under "_".
// Eg for this XML, the string will be under {"_":"Thu, 01 Feb 2018 17:24:05 GMT"}:
// <a:getlastmodified b:dt="dateTime.rfc1123">Thu, 01 Feb 2018 17:24:05 GMT</a:getlastmodified>
// For this XML, the value will be "Thu, 01 Feb 2018 17:24:05 GMT"
// <a:getlastmodified>Thu, 01 Feb 2018 17:24:05 GMT</a:getlastmodified>
if (typeof output === 'object' && '_' in output) output = output['_'];
2018-01-21 19:01:37 +02:00
if (typeof output !== 'string') return null;
return output;
}
if (type === 'object') {
if (!Array.isArray(output) && typeof output === 'object') return output;
return null;
}
2018-01-25 21:01:14 +02:00
if (type === 'array') {
return Array.isArray(output) ? output : null;
}
2018-01-21 19:01:37 +02:00
return null;
}
stringFromJson(json, keys) {
return this.valueFromJson(json, keys, 'string');
}
objectFromJson(json, keys) {
return this.valueFromJson(json, keys, 'object');
}
2018-01-25 21:01:14 +02:00
arrayFromJson(json, keys) {
return this.valueFromJson(json, keys, 'array');
}
resourcePropByName(resource, outputType, propName) {
const propStats = resource['d:propstat'];
let output = null;
if (!Array.isArray(propStats)) throw new Error('Missing d:propstat property');
for (let i = 0; i < propStats.length; i++) {
const props = propStats[i]['d:prop'];
if (!Array.isArray(props) || !props.length) continue;
const prop = props[0];
if (Array.isArray(prop[propName])) {
output = prop[propName];
break;
}
}
if (outputType === 'string') {
if (!output) throw new JoplinError(`String property not found: ${propName}: ${JSON.stringify(resource)}`, 'stringNotFound');
// If the XML has not attribute the value is directly a string
// If the XML node has attributes, the value is under "_".
// Eg for this XML, the string will be under {"_":"Thu, 01 Feb 2018 17:24:05 GMT"}:
// <a:getlastmodified b:dt="dateTime.rfc1123">Thu, 01 Feb 2018 17:24:05 GMT</a:getlastmodified>
// For this XML, the value will be "Thu, 01 Feb 2018 17:24:05 GMT"
// <a:getlastmodified>Thu, 01 Feb 2018 17:24:05 GMT</a:getlastmodified>
output = output[0];
if (typeof output === 'object' && '_' in output) output = output['_'];
if (typeof output !== 'string') return null;
return output;
}
if (outputType === 'array') {
return output;
}
2019-09-19 23:51:18 +02:00
throw new Error(`Invalid output type: ${outputType}`);
}
async execPropFind(path, depth, fields = null, options = null) {
2018-01-21 19:01:37 +02:00
if (fields === null) fields = ['d:getlastmodified'];
let fieldsXml = '';
for (let i = 0; i < fields.length; i++) {
2019-09-19 23:51:18 +02:00
fieldsXml += `<${fields[i]}/>`;
2018-01-21 19:01:37 +02:00
}
// To find all available properties:
//
2018-01-23 22:10:20 +02:00
// const body=`<?xml version="1.0" encoding="utf-8" ?>
// <propfind xmlns="DAV:">
// <propname/>
// </propfind>`;
2019-07-29 15:43:53 +02:00
const body =
`<?xml version="1.0" encoding="UTF-8"?>
2018-01-23 22:10:20 +02:00
<d:propfind xmlns:d="DAV:">
<d:prop xmlns:oc="http://owncloud.org/ns">
2019-09-20 00:02:29 +02:00
${fieldsXml}
2018-01-23 22:10:20 +02:00
</d:prop>
</d:propfind>`;
2018-01-21 19:01:37 +02:00
2019-07-29 15:43:53 +02:00
return this.exec('PROPFIND', path, body, { Depth: depth }, options);
2018-01-21 19:01:37 +02:00
}
requestToCurl_(url, options) {
const output = [];
output.push('curl');
2019-02-24 12:17:37 +02:00
output.push('-v');
2019-09-19 23:51:18 +02:00
if (options.method) output.push(`-X ${options.method}`);
if (options.headers) {
for (const n in options.headers) {
if (!options.headers.hasOwnProperty(n)) continue;
2019-09-19 23:51:18 +02:00
output.push(`${'-H ' + '"'}${n}: ${options.headers[n]}"`);
}
}
2019-09-19 23:51:18 +02:00
if (options.body) output.push(`${'--data ' + '\''}${options.body}'`);
output.push(url);
2019-07-29 15:43:53 +02:00
return output.join(' ');
}
handleNginxHack_(jsonResponse, newErrorHandler) {
// Trying to fix 404 error issue with Nginx WebDAV server.
// https://github.com/laurent22/joplin/issues/624
// https://github.com/laurent22/joplin/issues/808
// Not tested but someone confirmed it worked - https://github.com/laurent22/joplin/issues/808#issuecomment-443552858
// and fix is narrowly scoped so shouldn't affect anything outside this particular edge case.
//
// The issue is that instead of an HTTP 404 status code, Nginx returns 200 but with this response:
//
// <?xml version="1.0" encoding="utf-8" ?>
// <D:multistatus xmlns:D="DAV:">
// <D:response>
// <D:href>/notes/ecd4027a5271483984b00317433e2c66.md</D:href>
// <D:propstat>
// <D:prop/>
// <D:status>HTTP/1.1 404 Not Found</D:status>
// </D:propstat>
// </D:response>
// </D:multistatus>
//
// So we need to parse this and find that it is in fact a 404 error.
//
// HOWEVER, some implementations also return 404 for missing props, for example SeaFile:
// (indicates that the props "getlastmodified" is not present, but this call is only
// used when checking the conf, so we don't really need it)
// https://github.com/laurent22/joplin/issues/1137
//
// <?xml version='1.0' encoding='UTF-8'?>
// <ns0:multistatus xmlns:ns0="DAV:">
// <ns0:response>
// <ns0:href>/seafdav/joplin/</ns0:href>
// <ns0:propstat>
// <ns0:prop>
// <ns0:getlastmodified/>
// </ns0:prop>
// <ns0:status>HTTP/1.1 404 Not Found</ns0:status>
// </ns0:propstat>
// <ns0:propstat>
// <ns0:prop>
// <ns0:resourcetype>
// <ns0:collection/>
// </ns0:resourcetype>
// </ns0:prop>
// <ns0:status>HTTP/1.1 200 OK</ns0:status>
// </ns0:propstat>
// </ns0:response>
// </ns0:multistatus>
//
// As a simple fix for now it's enough to check if ALL the statuses are 404 - in that case
// it really means that the file doesn't exist. Otherwise we can proceed as usual.
const responseArray = this.arrayFromJson(jsonResponse, ['d:multistatus', 'd:response']);
if (responseArray && responseArray.length === 1) {
const propStats = this.arrayFromJson(jsonResponse, ['d:multistatus', 'd:response', 0, 'd:propstat']);
if (!propStats.length) return;
let count404 = 0;
for (let i = 0; i < propStats.length; i++) {
const status = this.arrayFromJson(jsonResponse, ['d:multistatus', 'd:response', 0, 'd:propstat', i, 'd:status']);
if (status && status.length && status[0].indexOf('404') >= 0) count404++;
}
if (count404 === propStats.length) throw newErrorHandler('Not found', 404);
}
}
2018-01-21 19:01:37 +02:00
// curl -u admin:123456 'http://nextcloud.local/remote.php/dav/files/admin/' -X PROPFIND --data '<?xml version="1.0" encoding="UTF-8"?>
// <d:propfind xmlns:d="DAV:">
// <d:prop xmlns:oc="http://owncloud.org/ns">
// <d:getlastmodified/>
// </d:prop>
// </d:propfind>'
2018-01-23 22:10:20 +02:00
async exec(method, path = '', body = null, headers = null, options = null) {
headers = { ...headers };
options = { ...options };
Mobile: Upgraded React Native to v0.63 commit 2fb6cee90174bfcc02f77ba1606bfd8c4e2c8fc8 Merge: 4e303be85f db509955f6 Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 16:24:07 2020 +0100 Merge branch 'dev' into rn_63 commit 4e303be85f7b3162b7e5b96e18557da13acfc988 Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 16:14:39 2020 +0100 Clean up commit e3a37ec2d6f3e6cc07c018b11b3f80ca8256063e Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 15:57:55 2020 +0100 Use different script for pre-commit and manual start commit bd236648fcd92a812cd16369dfa2238d38c6638f Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 15:56:45 2020 +0100 Removed RN eslint config commit e7feda41c9b473cd18768f2ce7686611fa2b3d08 Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 15:27:08 2020 +0100 Revert "Disable git hook for now" This reverts commit 89263ac7425bae5b03b60742ab186441217b37dc. commit cfd63fe46fbc714c065f13dd9add5a0b8e18bf1f Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 13:02:32 2020 +0100 Ask permission to use geo-location commit 66059939a38460ba05c09eed7f3b19fc4ead924c Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 12:26:20 2020 +0100 Fixed WebView race condition commit 1e0d2b7b86d88629f19ae6574f73c945d422d0b5 Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 11:56:21 2020 +0100 Fixed webview issues commit f537d22d7fc4bcf6ddb54a4faadf72f585ae271c Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 11:08:29 2020 +0100 Improve resource file watching commit eec32cf70aaf69b04a703ce49c3ede48a6ac1067 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 18:40:13 2020 +0100 Removed cache package dependency and implemented one more suitable for React Native commit efa346fea48414c98c1e577bf0d74a6e90a78044 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 14:57:21 2020 +0100 iOS: Added fonts to Info.plist although it was working without it commit 572b647bc0ff5b12ddd555ad7ca2bb18ccaeb512 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 14:56:49 2020 +0100 Specify content-type header for OneDrive to prevent network error commit bcedf6c7f0c35a428fd1c0800d4f17de662a49ff Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 12:45:01 2020 +0100 iOS: Disable long press menu since it is already built-in commit 7359dd61d1a609dbfce87b0deb169b8c5e2ace14 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 12:37:40 2020 +0100 Removed unused react-native-device-info commit 2d63ab36d32775f07236dae62f6cc7792dac435a Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 12:35:54 2020 +0100 iOS: Fixed taking a picture commit 8e2875a91c87b48ba3e230e9296d032cd05a267c Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 12:11:13 2020 +0100 iOS: Restored camera roll functionality commit 75f5edf2addfe3590d1a37bdac99680cb1a5c84c Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 11:40:13 2020 +0100 iOS: Fixed build settings commit b220c984198e78a2401387f4385bfdd331852d78 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 11:40:03 2020 +0100 iOS: Got images to work with WebKit commit c34b43e841b768104f19e86900133a1986b53af9 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 10:24:52 2020 +0100 iOS: Restore more settings commit 32997611e625f1775df05af966782ae763c1aa17 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 10:15:14 2020 +0100 iOS: Added back icons and other properties commit b5811d7f7cff227a30bb10ecad76f52cb212170a Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 23:53:14 2020 +0100 Got iOS build to work commit dc6d7c00e0048088cca653d5214d1cc4679ca005 Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 18:40:06 2020 +0100 Imported old settings in gradle build commit dff59f560317d260b8540a9324bcdd5e749a0c0b Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 18:20:00 2020 +0100 Restored sharing commit 0bdb449e72ef1766bd5aac878f44106c36e662c2 Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 17:25:40 2020 +0100 Updated NoteBodyViewer commit 0c0d228815251cfaf66ba25a276852d32192f106 Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 16:54:42 2020 +0100 Fixed networking commit 6ff45ce485d59e3e0fe66a9658a678499c887058 Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 13:11:00 2020 +0100 Fixed document picker commit cc889182b66052b8dfad03b46121e6a14763a51a Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 12:56:27 2020 +0100 Added back support for alarms commit 040261abfad89e5a58617d4c2d4f811d324ea488 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 22:04:49 2020 +0100 Fixed Clipboard and remove image-picker package commit 1077ad8f16481afcc63d92020e91cd37f077f207 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 21:54:52 2020 +0100 Fixed Select Alarm dialog and PoorManIntervals class commit 8296676fd52878b2f1cc2028a099f31909e6f286 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 21:32:52 2020 +0100 Fixed icons and warnings commit 3b0e3f6f43c83bb103132e8296d3887fccd386b5 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 17:02:59 2020 +0100 Got app to build again commit 89263ac7425bae5b03b60742ab186441217b37dc Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 15:41:17 2020 +0100 Disable git hook for now commit d6da162f674f94ba2c462268e39c161fa6126220 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 15:39:12 2020 +0100 Restored back all RN packages commit 7f8ce3732cf4c8ff6dcbcc0a8918c680adadd3f4 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 15:13:12 2020 +0100 Restored base packages commit ea59726eb3e0414afcdbe8af30a7765875239225 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 15:05:17 2020 +0100 Started over from scratch
2020-10-16 17:26:19 +02:00
2018-01-23 22:10:20 +02:00
if (!options.responseFormat) options.responseFormat = 'json';
if (!options.target) options.target = 'string';
2018-01-21 19:01:37 +02:00
const authToken = this.authToken();
2019-09-19 23:51:18 +02:00
if (authToken) headers['Authorization'] = `Basic ${authToken}`;
2018-01-21 19:01:37 +02:00
Mobile: Upgraded React Native to v0.63 commit 2fb6cee90174bfcc02f77ba1606bfd8c4e2c8fc8 Merge: 4e303be85f db509955f6 Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 16:24:07 2020 +0100 Merge branch 'dev' into rn_63 commit 4e303be85f7b3162b7e5b96e18557da13acfc988 Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 16:14:39 2020 +0100 Clean up commit e3a37ec2d6f3e6cc07c018b11b3f80ca8256063e Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 15:57:55 2020 +0100 Use different script for pre-commit and manual start commit bd236648fcd92a812cd16369dfa2238d38c6638f Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 15:56:45 2020 +0100 Removed RN eslint config commit e7feda41c9b473cd18768f2ce7686611fa2b3d08 Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 15:27:08 2020 +0100 Revert "Disable git hook for now" This reverts commit 89263ac7425bae5b03b60742ab186441217b37dc. commit cfd63fe46fbc714c065f13dd9add5a0b8e18bf1f Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 13:02:32 2020 +0100 Ask permission to use geo-location commit 66059939a38460ba05c09eed7f3b19fc4ead924c Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 12:26:20 2020 +0100 Fixed WebView race condition commit 1e0d2b7b86d88629f19ae6574f73c945d422d0b5 Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 11:56:21 2020 +0100 Fixed webview issues commit f537d22d7fc4bcf6ddb54a4faadf72f585ae271c Author: Laurent Cozic <laurent@cozic.net> Date: Fri Oct 16 11:08:29 2020 +0100 Improve resource file watching commit eec32cf70aaf69b04a703ce49c3ede48a6ac1067 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 18:40:13 2020 +0100 Removed cache package dependency and implemented one more suitable for React Native commit efa346fea48414c98c1e577bf0d74a6e90a78044 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 14:57:21 2020 +0100 iOS: Added fonts to Info.plist although it was working without it commit 572b647bc0ff5b12ddd555ad7ca2bb18ccaeb512 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 14:56:49 2020 +0100 Specify content-type header for OneDrive to prevent network error commit bcedf6c7f0c35a428fd1c0800d4f17de662a49ff Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 12:45:01 2020 +0100 iOS: Disable long press menu since it is already built-in commit 7359dd61d1a609dbfce87b0deb169b8c5e2ace14 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 12:37:40 2020 +0100 Removed unused react-native-device-info commit 2d63ab36d32775f07236dae62f6cc7792dac435a Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 12:35:54 2020 +0100 iOS: Fixed taking a picture commit 8e2875a91c87b48ba3e230e9296d032cd05a267c Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 12:11:13 2020 +0100 iOS: Restored camera roll functionality commit 75f5edf2addfe3590d1a37bdac99680cb1a5c84c Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 11:40:13 2020 +0100 iOS: Fixed build settings commit b220c984198e78a2401387f4385bfdd331852d78 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 11:40:03 2020 +0100 iOS: Got images to work with WebKit commit c34b43e841b768104f19e86900133a1986b53af9 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 10:24:52 2020 +0100 iOS: Restore more settings commit 32997611e625f1775df05af966782ae763c1aa17 Author: Laurent Cozic <laurent@cozic.net> Date: Thu Oct 15 10:15:14 2020 +0100 iOS: Added back icons and other properties commit b5811d7f7cff227a30bb10ecad76f52cb212170a Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 23:53:14 2020 +0100 Got iOS build to work commit dc6d7c00e0048088cca653d5214d1cc4679ca005 Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 18:40:06 2020 +0100 Imported old settings in gradle build commit dff59f560317d260b8540a9324bcdd5e749a0c0b Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 18:20:00 2020 +0100 Restored sharing commit 0bdb449e72ef1766bd5aac878f44106c36e662c2 Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 17:25:40 2020 +0100 Updated NoteBodyViewer commit 0c0d228815251cfaf66ba25a276852d32192f106 Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 16:54:42 2020 +0100 Fixed networking commit 6ff45ce485d59e3e0fe66a9658a678499c887058 Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 13:11:00 2020 +0100 Fixed document picker commit cc889182b66052b8dfad03b46121e6a14763a51a Author: Laurent Cozic <laurent@cozic.net> Date: Wed Oct 14 12:56:27 2020 +0100 Added back support for alarms commit 040261abfad89e5a58617d4c2d4f811d324ea488 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 22:04:49 2020 +0100 Fixed Clipboard and remove image-picker package commit 1077ad8f16481afcc63d92020e91cd37f077f207 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 21:54:52 2020 +0100 Fixed Select Alarm dialog and PoorManIntervals class commit 8296676fd52878b2f1cc2028a099f31909e6f286 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 21:32:52 2020 +0100 Fixed icons and warnings commit 3b0e3f6f43c83bb103132e8296d3887fccd386b5 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 17:02:59 2020 +0100 Got app to build again commit 89263ac7425bae5b03b60742ab186441217b37dc Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 15:41:17 2020 +0100 Disable git hook for now commit d6da162f674f94ba2c462268e39c161fa6126220 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 15:39:12 2020 +0100 Restored back all RN packages commit 7f8ce3732cf4c8ff6dcbcc0a8918c680adadd3f4 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 15:13:12 2020 +0100 Restored base packages commit ea59726eb3e0414afcdbe8af30a7765875239225 Author: Laurent Cozic <laurent@cozic.net> Date: Tue Oct 13 15:05:17 2020 +0100 Started over from scratch
2020-10-16 17:26:19 +02:00
// That should not be needed, but it is required for React Native 0.63+
// https://github.com/facebook/react-native/issues/30176
if (!headers['Content-Type']) {
if (method === 'PROPFIND') headers['Content-Type'] = 'text/xml';
if (method === 'PUT') headers['Content-Type'] = 'text/plain';
}
// React-native has caching enabled by at least on Android (see https://github.com/laurent22/joplin/issues/4706 and the related PR).
// The below header disables caching for all versions, including desktop.
// This can potentially also help with misconfigured caching on WebDAV server.
if (!headers['Cache-Control']) {
headers['Cache-Control'] = 'no-store';
}
// On iOS, the network lib appends a If-None-Match header to PROPFIND calls, which is kind of correct because
// the call is idempotent and thus could be cached. According to RFC-7232 though only GET and HEAD should have
// this header for caching purposes. It makes no mention of PROPFIND.
// So possibly because of this, Seafile (and maybe other WebDAV implementations) responds with a "412 Precondition Failed"
// error when this header is present for PROPFIND call on existing resources. This is also kind of correct because there is a resource
// with this eTag and since this is neither a GET nor HEAD call, it is supposed to respond with 412 if the resource is present.
// The "solution", an ugly one, is to send a purposely invalid string as eTag, which will bypass the If-None-Match check - Seafile
// finds out that no resource has this ID and simply sends the requested data.
// Also add a random value to make sure the eTag is unique for each call.
2019-09-19 23:51:18 +02:00
if (['GET', 'HEAD'].indexOf(method) < 0) headers['If-None-Match'] = `JoplinIgnore-${Math.floor(Math.random() * 100000)}`;
if (!headers['User-Agent']) headers['User-Agent'] = 'Joplin/1.0';
2018-01-21 19:01:37 +02:00
const fetchOptions = {};
fetchOptions.headers = headers;
fetchOptions.method = method;
2018-01-23 22:10:20 +02:00
if (options.path) fetchOptions.path = options.path;
2018-01-21 19:01:37 +02:00
if (body) fetchOptions.body = body;
fetchOptions.ignoreTlsErrors = this.options_.ignoreTlsErrors();
const url = `${this.baseUrl()}/${ltrimSlashes(path)}`;
2018-01-21 19:01:37 +02:00
if (shim.httpAgent(url)) fetchOptions.agent = shim.httpAgent(url);
2018-01-23 22:10:20 +02:00
let response = null;
2018-01-21 19:01:37 +02:00
// console.info('WebDAV Call', `${method} ${url}`, headers, options);
// console.info(this.requestToCurl_(url, fetchOptions));
if (options.source === 'file' && (method === 'POST' || method === 'PUT')) {
if (fetchOptions.path) {
const fileStat = await shim.fsDriver().stat(fetchOptions.path);
2019-09-19 23:51:18 +02:00
if (fileStat) fetchOptions.headers['Content-Length'] = `${fileStat.size}`;
}
2018-01-23 22:10:20 +02:00
response = await shim.uploadBlob(url, fetchOptions);
} else if (options.target === 'string') {
2019-09-19 23:51:18 +02:00
if (typeof body === 'string') fetchOptions.headers['Content-Length'] = `${shim.stringByteLength(body)}`;
2018-01-23 22:10:20 +02:00
response = await shim.fetch(url, fetchOptions);
2019-07-29 15:43:53 +02:00
} else {
// file
2018-01-23 22:10:20 +02:00
response = await shim.fetchBlob(url, fetchOptions);
2018-01-21 19:01:37 +02:00
}
2018-01-23 22:10:20 +02:00
const responseText = await response.text();
2018-01-21 19:01:37 +02:00
this.logRequest_({ url: url, options: fetchOptions }, responseText);
// console.info('WebDAV Response', responseText);
// Creates an error object with as much data as possible as it will appear in the log, which will make debugging easier
const newError = (message, code = 0) => {
// Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of
// JSON. That way the error message will still show there's a problem but without filling up the log or screen.
2019-09-19 23:51:18 +02:00
const shortResponseText = (`${responseText}`).substr(0, 1024);
return new JoplinError(`${method} ${path}: ${message} (${code}): ${shortResponseText}`, code);
2019-07-29 15:43:53 +02:00
};
2018-01-25 21:01:14 +02:00
2018-01-23 22:10:20 +02:00
let responseJson_ = null;
const loadResponseJson = async () => {
if (!responseText) return null;
if (responseJson_) return responseJson_;
// eslint-disable-next-line require-atomic-updates
2018-01-23 22:10:20 +02:00
responseJson_ = await this.xmlToJson(responseText);
if (!responseJson_) throw newError('Cannot parse XML response', response.status);
2018-01-23 22:10:20 +02:00
return responseJson_;
2019-07-29 15:43:53 +02:00
};
2018-01-21 19:01:37 +02:00
2018-01-23 22:10:20 +02:00
if (!response.ok) {
// When using fetchBlob we only get a string (not xml or json) back
if (options.target === 'file') throw newError('fetchBlob error', response.status);
2018-01-21 19:01:37 +02:00
let json = null;
try {
json = await loadResponseJson();
} catch (error) {
// Just send back the plain text in newErro()
}
2018-01-21 19:01:37 +02:00
2018-01-28 19:36:11 +02:00
if (json && json['d:error']) {
2018-01-23 22:10:20 +02:00
const code = json['d:error']['s:exception'] ? json['d:error']['s:exception'].join(' ') : response.status;
2019-07-29 15:43:53 +02:00
const message = json['d:error']['s:message'] ? json['d:error']['s:message'].join('\n') : 'Unknown error 1';
2019-09-19 23:51:18 +02:00
throw newError(`${message} (Exception ${code})`, response.status);
2018-01-23 22:10:20 +02:00
}
2018-01-21 19:01:37 +02:00
let message = 'Unknown error 2';
if (response.status === 401 || response.status === 403) {
// No auth token means an empty username or password
if (!authToken) {
message = _('Access denied: Please re-enter your password and/or username');
} else {
message = _('Access denied: Please check your username and password');
}
}
throw newError(message, response.status);
2018-01-23 22:10:20 +02:00
}
2019-07-29 15:43:53 +02:00
2018-01-23 22:10:20 +02:00
if (options.responseFormat === 'text') return responseText;
2018-01-21 19:01:37 +02:00
// The following methods may have a response depending on the server but it's not
// standard (some return a plain string, other XML, etc.) and we don't check the
// response anyway since we rely on the HTTP status code so return null.
if (['MKCOL', 'DELETE', 'PUT', 'MOVE'].indexOf(method) >= 0) return null;
2018-01-25 21:01:14 +02:00
const output = await loadResponseJson();
if (output) this.handleNginxHack_(output, newError);
2018-01-25 21:01:14 +02:00
// Check that we didn't get for example an HTML page (as an error) instead of the JSON response
// null responses are possible, for example for DELETE calls
if (output !== null && typeof output === 'object' && !('d:multistatus' in output)) throw newError('Not a valid WebDAV response');
2018-01-25 21:01:14 +02:00
return output;
2018-01-21 19:01:37 +02:00
}
}
2019-07-29 15:43:53 +02:00
module.exports = WebDavApi;