mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-27 03:57:21 +00:00
WebDAV file overwrite protection (#2614)
* putSaver: detect edit conflicts to prevent clobbering, if possible if the server supplies an ETag, we send it back when saving, allowing the server to detect edit conflicts and respond with 412 (cf. https://www.w3.org/1999/04/Editing/) caveats: * this only kicks in after the first save, as we don't have access to the ETag when first loading the document * there's no recovery mechanism (e.g. resetting `this.etag` in order to force clobbering), other than manually reloading the document * putSaver: retrieve ETag upon initialization for clobbering protection this addresses one of the caveats from the previous commit (2d75cb83af
) - while theoretically prone to a race condition, it seems unlikely that saving will be triggered before the server responds * putSaver: simplify URI extraction this simplifies the approach introduced inf51f6bf774
, with the purpose of removing the fragment identifier * putSaver: localize error message * putSaver: switch to built-in HTTP helper in the process, fixed ETag assignment in `#save` method (was `this.etag`, now `self.etag`) as well as a syntax error due to a missing closing brace * putSaver: consolidate URI handling
This commit is contained in:
parent
a6b538b308
commit
6505e6f448
@ -22,6 +22,7 @@ Encryption/RepeatPassword: Repeat password
|
||||
Encryption/PasswordNoMatch: Passwords do not match
|
||||
Encryption/SetPassword: Set password
|
||||
Error/Caption: Error
|
||||
Error/EditConflict: File changed on server
|
||||
Error/Filter: Filter error
|
||||
Error/FilterSyntax: Syntax error in filter expression
|
||||
Error/IsFilterOperator: Filter Error: Unknown operand for the 'is' filter operator
|
||||
|
@ -21,36 +21,62 @@ Select the appropriate saver module and set it up
|
||||
var PutSaver = function(wiki) {
|
||||
this.wiki = wiki;
|
||||
var self = this;
|
||||
var uri = this.uri();
|
||||
// Async server probe. Until probe finishes, save will fail fast
|
||||
// See also https://github.com/Jermolene/TiddlyWiki5/issues/2276
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("OPTIONS",encodeURI(document.location.protocol + "//" + document.location.hostname + ":" + document.location.port + document.location.pathname));
|
||||
req.onload = function() {
|
||||
// Check DAV header http://www.webdav.org/specs/rfc2518.html#rfc.section.9.1
|
||||
self.serverAcceptsPuts = (this.status === 200 && !!this.getResponseHeader('dav'));
|
||||
};
|
||||
req.send();
|
||||
$tw.utils.httpRequest({
|
||||
url: uri,
|
||||
type: "OPTIONS",
|
||||
callback: function(err, data, xhr) {
|
||||
// Check DAV header http://www.webdav.org/specs/rfc2518.html#rfc.section.9.1
|
||||
self.serverAcceptsPuts = xhr.status === 200 && !!xhr.getResponseHeader("dav");
|
||||
}
|
||||
});
|
||||
// Retrieve ETag if available
|
||||
$tw.utils.httpRequest({
|
||||
url: uri,
|
||||
type: "HEAD",
|
||||
callback: function(err, data, xhr) {
|
||||
self.etag = xhr.getResponseHeader("ETag");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
PutSaver.prototype.save = function(text,method,callback) {
|
||||
PutSaver.prototype.uri = function() {
|
||||
return encodeURI(document.location.toString().split("#")[0]);
|
||||
};
|
||||
|
||||
// TODO: in case of edit conflict
|
||||
// Prompt: Do you want to save over this? Y/N
|
||||
// Merging would be ideal, and may be possible using future generic merge flow
|
||||
PutSaver.prototype.save = function(text, method, callback) {
|
||||
if (!this.serverAcceptsPuts) {
|
||||
return false;
|
||||
}
|
||||
var req = new XMLHttpRequest();
|
||||
// TODO: store/check ETags if supported by server, to protect against overwrites
|
||||
// Prompt: Do you want to save over this? Y/N
|
||||
// Merging would be ideal, and may be possible using future generic merge flow
|
||||
req.onload = function() {
|
||||
if (this.status === 200 || this.status === 201) {
|
||||
callback(null); // success
|
||||
var self = this;
|
||||
var headers = { "Content-Type": "text/html;charset=UTF-8" };
|
||||
if (this.etag) {
|
||||
headers["If-Match"] = this.etag;
|
||||
}
|
||||
$tw.utils.httpRequest({
|
||||
url: this.uri(),
|
||||
type: "PUT",
|
||||
headers: headers,
|
||||
data: text,
|
||||
callback: function(err, data, xhr) {
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
self.etag = xhr.getResponseHeader("ETag");
|
||||
callback(null); // success
|
||||
}
|
||||
else if (xhr.status === 412) { // edit conflict
|
||||
var message = $tw.language.getString("Error/EditConflict");
|
||||
callback(message);
|
||||
}
|
||||
else {
|
||||
callback(xhr.responseText); // fail
|
||||
}
|
||||
}
|
||||
else {
|
||||
callback(this.responseText); // fail
|
||||
}
|
||||
};
|
||||
req.open("PUT", encodeURI(window.location.href));
|
||||
req.setRequestHeader("Content-Type", "text/html;charset=UTF-8");
|
||||
req.send(text);
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user