You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-13 22:12:50 +02:00
All: Fixes #209: Items with non-ASCII characters end up truncated on Nextcloud
This commit is contained in:
@@ -60,6 +60,7 @@ async function allSyncTargetItemsEncrypted() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function localItemsSameAsRemote(locals, expect) {
|
async function localItemsSameAsRemote(locals, expect) {
|
||||||
|
let error = null;
|
||||||
try {
|
try {
|
||||||
let files = await fileApi().list();
|
let files = await fileApi().list();
|
||||||
files = files.items;
|
files = files.items;
|
||||||
@@ -81,12 +82,15 @@ async function localItemsSameAsRemote(locals, expect) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
let remoteContent = await fileApi().get(path);
|
let remoteContent = await fileApi().get(path);
|
||||||
|
|
||||||
remoteContent = dbItem.type_ == BaseModel.TYPE_NOTE ? await Note.unserialize(remoteContent) : await Folder.unserialize(remoteContent);
|
remoteContent = dbItem.type_ == BaseModel.TYPE_NOTE ? await Note.unserialize(remoteContent) : await Folder.unserialize(remoteContent);
|
||||||
expect(remoteContent.title).toBe(dbItem.title);
|
expect(remoteContent.title).toBe(dbItem.title);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
console.error(error);
|
error = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect(error).toBe(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
let insideBeforeEach = false;
|
let insideBeforeEach = false;
|
||||||
@@ -985,4 +989,15 @@ describe('Synchronizer', function() {
|
|||||||
expect(resource1.encryption_blob_encrypted).toBe(0);
|
expect(resource1.encryption_blob_encrypted).toBe(0);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should create remote items with UTF-8 content', asyncTest(async () => {
|
||||||
|
let folder = await Folder.save({ title: "Fahrräder" });
|
||||||
|
await Note.save({ title: "Fahrräder", body: "Fahrräder", parent_id: folder.id });
|
||||||
|
|
||||||
|
let all = await allItems();
|
||||||
|
|
||||||
|
await synchronizer().start();
|
||||||
|
|
||||||
|
await localItemsSameAsRemote(all, expect);
|
||||||
|
}));
|
||||||
|
|
||||||
});
|
});
|
@@ -158,6 +158,22 @@ class WebDavApi {
|
|||||||
return this.exec('PROPFIND', path, body, { 'Depth': depth }, options);
|
return this.exec('PROPFIND', path, body, { 'Depth': depth }, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestToCurl_(url, options) {
|
||||||
|
let output = [];
|
||||||
|
output.push('curl');
|
||||||
|
if (options.method) output.push('-X ' + options.method);
|
||||||
|
if (options.headers) {
|
||||||
|
for (let n in options.headers) {
|
||||||
|
if (!options.headers.hasOwnProperty(n)) continue;
|
||||||
|
output.push('-H ' + '"' + n + ': ' + options.headers[n] + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.body) output.push('--data ' + "'" + options.body + "'");
|
||||||
|
output.push(url);
|
||||||
|
|
||||||
|
return output.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
// curl -u admin:123456 'http://nextcloud.local/remote.php/dav/files/admin/' -X PROPFIND --data '<?xml version="1.0" encoding="UTF-8"?>
|
// 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:propfind xmlns:d="DAV:">
|
||||||
// <d:prop xmlns:oc="http://owncloud.org/ns">
|
// <d:prop xmlns:oc="http://owncloud.org/ns">
|
||||||
@@ -175,7 +191,10 @@ class WebDavApi {
|
|||||||
|
|
||||||
if (authToken) headers['Authorization'] = 'Basic ' + authToken;
|
if (authToken) headers['Authorization'] = 'Basic ' + authToken;
|
||||||
|
|
||||||
if (typeof body === 'string') headers['Content-Length'] = body.length;
|
// /!\ Doesn't work with UTF-8 strings as it results in truncated content. Content-Length
|
||||||
|
// /!\ should not be needed anyway, but was required by one service. If re-implementing this
|
||||||
|
// /!\ test with various content, including binary blobs.
|
||||||
|
// if (typeof body === 'string') headers['Content-Length'] = body.length;
|
||||||
|
|
||||||
const fetchOptions = {};
|
const fetchOptions = {};
|
||||||
fetchOptions.headers = headers;
|
fetchOptions.headers = headers;
|
||||||
@@ -188,6 +207,7 @@ class WebDavApi {
|
|||||||
let response = null;
|
let response = null;
|
||||||
|
|
||||||
// console.info('WebDAV Call', method + ' ' + url, headers, options);
|
// console.info('WebDAV Call', method + ' ' + url, headers, options);
|
||||||
|
// console.info(this.requestToCurl_(url, fetchOptions));
|
||||||
|
|
||||||
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
|
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
|
||||||
response = await shim.uploadBlob(url, fetchOptions);
|
response = await shim.uploadBlob(url, fetchOptions);
|
||||||
|
@@ -15,7 +15,6 @@ shared.checkSyncConfig = async function(comp, settings) {
|
|||||||
const options = Setting.subValues('sync.' + syncTargetId, settings);
|
const options = Setting.subValues('sync.' + syncTargetId, settings);
|
||||||
comp.setState({ checkSyncConfigResult: 'checking' });
|
comp.setState({ checkSyncConfigResult: 'checking' });
|
||||||
const result = await SyncTargetClass.checkConfig(options);
|
const result = await SyncTargetClass.checkConfig(options);
|
||||||
console.info(result);
|
|
||||||
comp.setState({ checkSyncConfigResult: result });
|
comp.setState({ checkSyncConfigResult: result });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -42,8 +42,8 @@ class FileApiDriverWebDav {
|
|||||||
const isCollection = this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:resourcetype', 0, 'd:collection', 0]);
|
const isCollection = this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:resourcetype', 0, 'd:collection', 0]);
|
||||||
const lastModifiedString = this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:getlastmodified', 0]);
|
const lastModifiedString = this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:getlastmodified', 0]);
|
||||||
|
|
||||||
const sizeDONOTUSE = Number(this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:getcontentlength', 0]));
|
// const sizeDONOTUSE = Number(this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:getcontentlength', 0]));
|
||||||
if (isNaN(sizeDONOTUSE)) throw new Error('Cannot get content size: ' + JSON.stringify(resource));
|
// if (isNaN(sizeDONOTUSE)) throw new Error('Cannot get content size: ' + JSON.stringify(resource));
|
||||||
|
|
||||||
if (!lastModifiedString) throw new Error('Could not get lastModified date: ' + JSON.stringify(resource));
|
if (!lastModifiedString) throw new Error('Could not get lastModified date: ' + JSON.stringify(resource));
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class FileApiDriverWebDav {
|
|||||||
// created_time: lastModifiedDate.getTime(),
|
// created_time: lastModifiedDate.getTime(),
|
||||||
updated_time: lastModifiedDate.getTime(),
|
updated_time: lastModifiedDate.getTime(),
|
||||||
isDir: isCollection === '',
|
isDir: isCollection === '',
|
||||||
sizeDONOTUSE: sizeDONOTUSE, // This property is used only for the WebDAV PUT hack (see below) so mark it as such so that it can be removed with the hack later on.
|
// sizeDONOTUSE: sizeDONOTUSE, // This property is used only for the WebDAV PUT hack (see below) so mark it as such so that it can be removed with the hack later on.
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,32 +304,7 @@ class FileApiDriverWebDav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async put(path, content, options = null) {
|
async put(path, content, options = null) {
|
||||||
// In theory, if a client doesn't complete an upload, the file will not appear in the Nextcloud app. Likewise if
|
return await this.api().exec('PUT', path, content, null, options);
|
||||||
// the server interrupts the upload midway, the client should receive some kind of error and try uploading the
|
|
||||||
// file again next time. At the very least the file should not appear half-uploaded on the server. In practice
|
|
||||||
// however it seems some files might end up half uploaded on the server (at least on ocloud.de) so, for now,
|
|
||||||
// instead of doing a simple PUT, we do it to a temp file on Nextcloud, then check the file size and, if it
|
|
||||||
// matches, move it its actual place (hoping the server won't mess up and only copy half of the file).
|
|
||||||
// This is innefficient so once the bug is better understood it should hopefully be possible to go back to
|
|
||||||
// using a single PUT call.
|
|
||||||
|
|
||||||
let contentSize = 0;
|
|
||||||
if (content) contentSize = content.length;
|
|
||||||
if (options && options.path) {
|
|
||||||
const stat = await shim.fsDriver().stat(options.path);
|
|
||||||
contentSize = stat.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tempPath = this.fileApi_.tempDirName() + '/' + basename(path) + '_' + Date.now();
|
|
||||||
await this.api().exec('PUT', tempPath, content, null, options);
|
|
||||||
|
|
||||||
const stat = await this.stat(tempPath);
|
|
||||||
if (stat.sizeDONOTUSE != contentSize) {
|
|
||||||
// await this.delete(tempPath);
|
|
||||||
throw new Error('WebDAV PUT - Size check failed for ' + tempPath + ' Expected: ' + contentSize + '. Found: ' + stat.sizeDONOTUSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.move(tempPath, path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(path) {
|
async delete(path) {
|
||||||
|
Reference in New Issue
Block a user