2016-02-19 15:19:15 -06:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
|
|
/*
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Storage abstraction.
|
2020-04-18 03:39:50 -05:00
|
|
|
|
|
|
|
#pragma once
|
2016-02-19 15:19:15 -06:00
|
|
|
|
2016-04-16 17:12:10 -05:00
|
|
|
#include <set>
|
2016-10-29 20:15:00 -05:00
|
|
|
#include <string>
|
2019-11-27 02:43:05 -06:00
|
|
|
#include <chrono>
|
2016-02-19 15:19:15 -06:00
|
|
|
|
2016-03-31 01:48:34 -05:00
|
|
|
#include <Poco/URI.h>
|
2016-10-29 20:15:00 -05:00
|
|
|
#include <Poco/Util/Application.h>
|
2020-06-11 09:54:27 -05:00
|
|
|
#include <Poco/JSON/Object.h>
|
2016-03-03 20:05:30 -06:00
|
|
|
|
2016-02-19 15:19:15 -06:00
|
|
|
#include "Auth.hpp"
|
2021-01-10 11:20:23 -06:00
|
|
|
#include "HttpRequest.hpp"
|
2021-11-18 06:08:14 -06:00
|
|
|
#include "COOLWSD.hpp"
|
loolwsd: include cleanup and organization
A source file (.cpp) must include its own header first.
This insures that the header is self-contained and
doesn't depend on arbitrary (and accidental) includes
before it to compile.
Furthermore, system headers should go next, followed by
C then C++ headers, then libraries (Poco, etc) and, finally,
project headers come last.
This makes sure that headers and included in the same dependency
order to avoid side-effects. For example, Poco should never rely on
anything from our project in the same way that a C header should
never rely on anything in C++, Poco, or project headers.
Also, includes ought to be sorted where possible, to improve
readability and avoid accidental duplicates (of which there
were a few).
Change-Id: I62cc1343e4a091d69195e37ed659dba20cfcb1ef
Reviewed-on: https://gerrit.libreoffice.org/25262
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2016-05-21 09:23:07 -05:00
|
|
|
#include "Log.hpp"
|
2016-02-19 15:19:15 -06:00
|
|
|
#include "Util.hpp"
|
2018-11-22 05:03:42 -06:00
|
|
|
#include <common/Authorization.hpp>
|
2021-01-10 11:20:00 -06:00
|
|
|
#include <net/HttpRequest.hpp>
|
2016-02-19 15:19:15 -06:00
|
|
|
|
2021-06-28 07:04:22 -05:00
|
|
|
/// Limits number of HTTP redirections to prevent from redirection loops
|
|
|
|
static constexpr auto RedirectionLimit = 21;
|
|
|
|
|
2020-07-16 09:44:41 -05:00
|
|
|
namespace Poco
|
|
|
|
{
|
|
|
|
namespace Net
|
|
|
|
{
|
|
|
|
class HTTPClientSession;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Poco
|
|
|
|
|
2019-11-19 16:51:45 -06:00
|
|
|
/// Represents whether the underlying file is locked
|
|
|
|
/// and with what token.
|
|
|
|
struct LockContext
|
|
|
|
{
|
|
|
|
/// Do we have support for locking for a storage.
|
|
|
|
bool _supportsLocks;
|
|
|
|
/// Do we own the (leased) lock currently
|
|
|
|
bool _isLocked;
|
|
|
|
/// Name if we need it to use consistently for locking
|
|
|
|
std::string _lockToken;
|
2019-11-27 02:43:05 -06:00
|
|
|
/// Time of last successful lock (re-)acquisition
|
|
|
|
std::chrono::steady_clock::time_point _lastLockTime;
|
2020-07-01 03:34:08 -05:00
|
|
|
/// Reason for unsuccessful locking request
|
|
|
|
std::string _lockFailureReason;
|
2019-11-19 16:51:45 -06:00
|
|
|
|
2023-02-18 08:26:29 -06:00
|
|
|
LockContext()
|
|
|
|
: _supportsLocks(false)
|
|
|
|
, _isLocked(false)
|
|
|
|
, _refreshSeconds(COOLWSD::getConfigValue<int>("storage.wopi.locking.refresh", 900))
|
|
|
|
{
|
|
|
|
}
|
2019-11-19 16:51:45 -06:00
|
|
|
|
|
|
|
/// one-time setup for supporting locks & create token
|
|
|
|
void initSupportsLocks();
|
2019-11-27 02:43:05 -06:00
|
|
|
|
2023-03-10 14:03:05 -06:00
|
|
|
/// wait another refresh cycle
|
|
|
|
void bumpTimer()
|
|
|
|
{
|
|
|
|
_lastLockTime = std::chrono::steady_clock::now();
|
|
|
|
}
|
|
|
|
|
2019-11-27 02:43:05 -06:00
|
|
|
/// do we need to refresh our lock ?
|
|
|
|
bool needsRefresh(const std::chrono::steady_clock::time_point &now) const;
|
|
|
|
|
2019-12-16 01:33:16 -06:00
|
|
|
void dumpState(std::ostream& os) const;
|
2023-02-18 08:26:29 -06:00
|
|
|
|
|
|
|
private:
|
|
|
|
const std::chrono::seconds _refreshSeconds;
|
2019-11-19 16:51:45 -06:00
|
|
|
};
|
|
|
|
|
2016-02-19 15:19:15 -06:00
|
|
|
/// Base class of all Storage abstractions.
|
|
|
|
class StorageBase
|
|
|
|
{
|
|
|
|
public:
|
2016-10-26 06:15:28 -05:00
|
|
|
/// Represents basic file's attributes.
|
2016-08-13 23:01:13 -05:00
|
|
|
/// Used for local and network files.
|
2016-03-21 18:12:00 -05:00
|
|
|
class FileInfo
|
|
|
|
{
|
|
|
|
public:
|
2022-02-06 20:15:09 -06:00
|
|
|
FileInfo(std::string filename, std::string ownerId, std::string modifiedTime)
|
|
|
|
: _filename(std::move(filename))
|
|
|
|
, _ownerId(std::move(ownerId))
|
|
|
|
, _modifiedTime(std::move(modifiedTime))
|
2016-09-26 01:44:40 -05:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-05-20 11:17:31 -05:00
|
|
|
FileInfo(const FileInfo& fileInfo)
|
|
|
|
: _filename(fileInfo._filename)
|
|
|
|
, _ownerId(fileInfo._ownerId)
|
|
|
|
, _modifiedTime(fileInfo._modifiedTime)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
FileInfo& operator=(const FileInfo& rhs)
|
|
|
|
{
|
|
|
|
if (this != &rhs)
|
|
|
|
{
|
|
|
|
_filename = rhs._filename;
|
|
|
|
_ownerId = rhs._ownerId;
|
|
|
|
_modifiedTime = rhs._modifiedTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2016-04-06 23:32:28 -05:00
|
|
|
bool isValid() const
|
|
|
|
{
|
2017-01-15 22:06:27 -06:00
|
|
|
// 0-byte files are valid; LO will open them as new docs.
|
|
|
|
return !_filename.empty();
|
2016-04-06 23:32:28 -05:00
|
|
|
}
|
|
|
|
|
2018-11-29 01:42:43 -06:00
|
|
|
const std::string& getFilename() const { return _filename; }
|
|
|
|
|
|
|
|
const std::string& getOwnerId() const { return _ownerId; }
|
|
|
|
|
2022-02-06 20:15:09 -06:00
|
|
|
/// Set the last modified time as reported to the WOPI host.
|
|
|
|
void setLastModifiedTime(const std::string& modifiedTime) { _modifiedTime = modifiedTime; }
|
2018-11-29 01:42:43 -06:00
|
|
|
|
2022-02-06 20:15:09 -06:00
|
|
|
/// Get the last modified time as reported by the WOPI host.
|
|
|
|
const std::string& getLastModifiedTime() const { return _modifiedTime; }
|
2018-11-29 01:42:43 -06:00
|
|
|
|
|
|
|
private:
|
2016-04-19 02:10:07 -05:00
|
|
|
std::string _filename;
|
2016-11-08 02:14:14 -06:00
|
|
|
std::string _ownerId;
|
2022-02-06 20:15:09 -06:00
|
|
|
std::string _modifiedTime; //< Opaque modified timestamp as received from the server.
|
2016-03-21 18:12:00 -05:00
|
|
|
};
|
|
|
|
|
wsd: move storage attributes to DocBroker
There are a number of races with having Storage
track the attributes. To fix them, we move all
attributes to DocBroker and correct a number
of issues.
The idea of the design is based on the fact that
we want to capture the attributes between
uploads, but based on the saved document.
That is, when we upload a document version, we
want to pass to the storage whether from the
perspective of the *Storage* there has been
any user-modifications or not. Since saving
to disk may happen multiple times between
uploads (not least because of failures), and
since saving resets the modified flag, we need
to capture the modified flag at each save and
propagate it until we upload successfully.
Upon uploading successfully, we reset the
attributes.
For this reason we have two attribute instances;
one is the 'current' attributes as being uploaded
and the other the 'next' one. We capture the
current state at saving into 'next' and we merge
with 'current' when saving succeeds and we
aren't already uploading (otherwise, we update
it and then discard it when uploading succeeds,
losing the last attributes).
Furthermore, because the modified flag is
reset after each save, and because we might
save and upload immediately after a
modification, we may not have the modified flag.
This means that we need some heuristics to
decide if there has been user-issued
modifications. (It is better to be conservative
here.) We try to detect this by introspecting
the commands we receive from users.
In effect, we capture the attributes when issuing
an internal save, we transfer the captured
attributes only when saving succeeds and we aren't
uploading, and from then on, uploading has to
succeed to reset the 'current' attributes. In the
meantime, if we fail to upload and issue another
save, the new attributes will be captured and
merged with the 'current' and the next upload
will not have any lost attributes.
Change-Id: I8c5e75d25ac235c6232318343678bf5c0933c31e
Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
2022-08-04 21:02:11 -05:00
|
|
|
/// Represents attributes of interest to the storage.
|
|
|
|
/// These are typically set in the PUT headers.
|
|
|
|
/// They include flags to indicate auto-save, exit-save,
|
|
|
|
/// forced-uploading, and whether or not the document
|
|
|
|
/// had been modified, amongst others.
|
|
|
|
/// The reason for this class is to avoid clobbering
|
|
|
|
/// these attributes when uploading fails--or indeed
|
|
|
|
/// racing with uploading.
|
|
|
|
class Attributes
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Attributes()
|
|
|
|
: _forced(false)
|
|
|
|
, _isUserModified(false)
|
|
|
|
, _isAutosave(false)
|
|
|
|
, _isExitSave(false)
|
|
|
|
{}
|
|
|
|
|
|
|
|
/// Reset the attributes to clear them after using them.
|
|
|
|
void reset()
|
|
|
|
{
|
|
|
|
_forced = false;
|
|
|
|
_isUserModified = false;
|
|
|
|
_isAutosave = false;
|
|
|
|
_isExitSave = false;
|
|
|
|
_extendedData.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void merge(const Attributes& lhs)
|
|
|
|
{
|
|
|
|
// Whichever is true.
|
|
|
|
_forced = lhs._forced ? true : _forced;
|
|
|
|
_isUserModified = lhs._isUserModified ? true : _isUserModified;
|
|
|
|
_isAutosave = lhs._isAutosave ? true : _isAutosave;
|
|
|
|
_isExitSave = lhs._isExitSave ? true : _isExitSave;
|
|
|
|
|
|
|
|
// Clobber with the lhs, assuming it's newer.
|
|
|
|
if (!lhs._extendedData.empty())
|
|
|
|
_extendedData = lhs._extendedData;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Asks the storage object to force overwrite
|
|
|
|
/// even if document turned out to be changed in storage.
|
|
|
|
/// Used to resolve storage conflicts by clobbering.
|
|
|
|
void setForced(bool forced = true) { _forced = forced; }
|
|
|
|
bool isForced() const { return _forced; }
|
|
|
|
|
|
|
|
/// To be able to set the WOPI extension header appropriately.
|
|
|
|
void setUserModified(bool userModified) { _isUserModified = userModified; }
|
|
|
|
bool isUserModified() const { return _isUserModified; }
|
|
|
|
|
|
|
|
/// To be able to set the WOPI 'is autosave/is exitsave?' headers appropriately.
|
|
|
|
void setIsAutosave(bool newIsAutosave) { _isAutosave = newIsAutosave; }
|
|
|
|
bool isAutosave() const { return _isAutosave; }
|
|
|
|
|
|
|
|
/// Set only when saving on exit.
|
|
|
|
void setIsExitSave(bool exitSave) { _isExitSave = exitSave; }
|
|
|
|
bool isExitSave() const { return _isExitSave; }
|
|
|
|
|
|
|
|
/// Misc extended data.
|
|
|
|
void setExtendedData(const std::string& extendedData) { _extendedData = extendedData; }
|
|
|
|
const std::string& getExtendedData() const { return _extendedData; }
|
|
|
|
|
|
|
|
/// Dump the internals of this instance.
|
2023-04-04 19:58:37 -05:00
|
|
|
void dumpState(std::ostream& os, const std::string& indent = "\n ") const
|
wsd: move storage attributes to DocBroker
There are a number of races with having Storage
track the attributes. To fix them, we move all
attributes to DocBroker and correct a number
of issues.
The idea of the design is based on the fact that
we want to capture the attributes between
uploads, but based on the saved document.
That is, when we upload a document version, we
want to pass to the storage whether from the
perspective of the *Storage* there has been
any user-modifications or not. Since saving
to disk may happen multiple times between
uploads (not least because of failures), and
since saving resets the modified flag, we need
to capture the modified flag at each save and
propagate it until we upload successfully.
Upon uploading successfully, we reset the
attributes.
For this reason we have two attribute instances;
one is the 'current' attributes as being uploaded
and the other the 'next' one. We capture the
current state at saving into 'next' and we merge
with 'current' when saving succeeds and we
aren't already uploading (otherwise, we update
it and then discard it when uploading succeeds,
losing the last attributes).
Furthermore, because the modified flag is
reset after each save, and because we might
save and upload immediately after a
modification, we may not have the modified flag.
This means that we need some heuristics to
decide if there has been user-issued
modifications. (It is better to be conservative
here.) We try to detect this by introspecting
the commands we receive from users.
In effect, we capture the attributes when issuing
an internal save, we transfer the captured
attributes only when saving succeeds and we aren't
uploading, and from then on, uploading has to
succeed to reset the 'current' attributes. In the
meantime, if we fail to upload and issue another
save, the new attributes will be captured and
merged with the 'current' and the next upload
will not have any lost attributes.
Change-Id: I8c5e75d25ac235c6232318343678bf5c0933c31e
Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
2022-08-04 21:02:11 -05:00
|
|
|
{
|
|
|
|
os << indent << "forced: " << std::boolalpha << isForced();
|
|
|
|
os << indent << "user-modified: " << std::boolalpha << isUserModified();
|
|
|
|
os << indent << "auto-save: " << std::boolalpha << isAutosave();
|
|
|
|
os << indent << "exit-save: " << std::boolalpha << isExitSave();
|
|
|
|
os << indent << "extended-data: " << getExtendedData();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
/// Whether or not we want to force uploading.
|
|
|
|
bool _forced;
|
|
|
|
/// The document has been modified by the user.
|
|
|
|
bool _isUserModified;
|
|
|
|
/// This save operation is an autosave.
|
|
|
|
bool _isAutosave;
|
|
|
|
/// Saving on exit (when the document is cleaned up from memory)
|
|
|
|
bool _isExitSave;
|
|
|
|
/// The client-provided saving extended data to send to the WOPI host.
|
|
|
|
std::string _extendedData;
|
|
|
|
};
|
|
|
|
|
2020-12-28 12:05:50 -06:00
|
|
|
/// Represents the upload request result, with a Result code
|
|
|
|
/// and a reason message (typically for errors).
|
|
|
|
/// Note: the reason message may be displayed to the clients.
|
2020-11-29 18:11:59 -06:00
|
|
|
class UploadResult final
|
2016-11-23 06:09:54 -06:00
|
|
|
{
|
2017-10-25 07:09:27 -05:00
|
|
|
public:
|
2020-11-15 08:33:26 -06:00
|
|
|
enum class Result
|
2017-10-25 07:09:27 -05:00
|
|
|
{
|
2020-11-15 08:33:26 -06:00
|
|
|
OK = 0,
|
2017-10-25 07:09:27 -05:00
|
|
|
DISKFULL,
|
2022-05-21 08:03:43 -05:00
|
|
|
TOO_LARGE, //< 413
|
2022-11-26 07:26:33 -06:00
|
|
|
UNAUTHORIZED, //< 401, 403, 404
|
2017-10-25 07:09:27 -05:00
|
|
|
DOC_CHANGED, /**< Document changed in storage */
|
2022-05-21 08:03:43 -05:00
|
|
|
CONFLICT, //< 409
|
2017-10-25 07:09:27 -05:00
|
|
|
FAILED
|
|
|
|
};
|
|
|
|
|
2020-12-28 12:05:50 -06:00
|
|
|
explicit UploadResult(Result result)
|
|
|
|
: _result(result)
|
2017-10-25 07:09:27 -05:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-12-28 12:05:50 -06:00
|
|
|
UploadResult(Result result, std::string reason)
|
|
|
|
: _result(result)
|
|
|
|
, _reason(std::move(reason))
|
2017-10-25 07:09:27 -05:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-12-28 12:05:50 -06:00
|
|
|
void setResult(Result result) { _result = result; }
|
|
|
|
|
|
|
|
Result getResult() const { return _result; }
|
2017-10-25 07:09:27 -05:00
|
|
|
|
|
|
|
void setSaveAsResult(const std::string& name, const std::string& url)
|
|
|
|
{
|
|
|
|
_saveAsName = name;
|
|
|
|
_saveAsUrl = url;
|
|
|
|
}
|
|
|
|
|
2020-12-28 12:05:50 -06:00
|
|
|
const std::string& getSaveAsName() const { return _saveAsName; }
|
2017-10-25 07:09:27 -05:00
|
|
|
|
2020-12-28 12:05:50 -06:00
|
|
|
const std::string& getSaveAsUrl() const { return _saveAsUrl; }
|
2017-10-25 07:09:27 -05:00
|
|
|
|
2020-12-28 12:05:50 -06:00
|
|
|
void setReason(const std::string& msg) { _reason = msg; }
|
2020-07-01 05:46:58 -05:00
|
|
|
|
2020-12-28 12:05:50 -06:00
|
|
|
const std::string& getReason() const { return _reason; }
|
2020-07-01 05:46:58 -05:00
|
|
|
|
2017-10-25 07:09:27 -05:00
|
|
|
private:
|
|
|
|
Result _result;
|
|
|
|
std::string _saveAsName;
|
|
|
|
std::string _saveAsUrl;
|
2020-12-28 12:05:50 -06:00
|
|
|
std::string _reason;
|
2016-11-23 06:09:54 -06:00
|
|
|
};
|
|
|
|
|
2021-01-10 11:20:23 -06:00
|
|
|
/// The state of an asynchronous upload request.
|
|
|
|
class AsyncUpload final
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
enum class State
|
|
|
|
{
|
|
|
|
None, //< No async upload in progress or isn't supported.
|
|
|
|
Running, //< An async upload request is in progress.
|
|
|
|
Error, //< Failed to make an async upload request or timed out, no UploadResult.
|
|
|
|
Complete //< The last async upload request completed (regardless of the server's response).
|
|
|
|
};
|
|
|
|
|
2020-12-28 13:58:07 -06:00
|
|
|
AsyncUpload(State state, UploadResult result)
|
2021-01-10 11:20:23 -06:00
|
|
|
: _state(state)
|
|
|
|
, _result(std::move(result))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the state of the async upload.
|
|
|
|
State state() const { return _state; }
|
|
|
|
|
|
|
|
/// Returns the result of the async upload.
|
|
|
|
const UploadResult& result() const { return _result; }
|
|
|
|
|
|
|
|
private:
|
2020-12-28 13:58:07 -06:00
|
|
|
State _state;
|
2021-01-10 11:20:23 -06:00
|
|
|
UploadResult _result;
|
|
|
|
};
|
|
|
|
|
2021-11-18 06:08:14 -06:00
|
|
|
enum class COOLStatusCode
|
Inform all clients when document changed behind our back
Introduce a new header X-LOOL-WOPI-Timestamp
This is a WOPI header extension to detect any external document change. For
example, when the file that is already opened by LOOL is changed
in storage.
The WOPI host sends LastModifiedTime field (in WOPI specs) as part
of the CheckFileInfo response. It also expects wsd to send the
same timestamp in X-LOOL-WOPI-Timestamp header during WOPI::PutFile. If
this header is present, then WOPI host checks, before saving the
document, if the timestamp in the header is equal to the timestamp of
the file in its storage. Only upon meeting this condition, it saves the
file back to storage, otherwise it informs us about some change
to the document.
We are supposed to inform the user accordingly. If user is okay
with over-writing the document, then we can omit sending
X-LOOL-WOPI-Timestamp header, in which case, no check as mentioned above
would be performed while saving the file and document will be
overwritten.
Also, use a separate list of LOOL status codes to denote such a change.
It would be wrong to use HTTP_CONFLICT status code for denoting doc
changed in storage scenario. WOPI specs reserves that for WOPI locks
which are not yet implemented. Better to use a separate LOOL specific
status codes synced across WOPI hosts and us to denote scenario that we
expect and are not covered in WOPI specs.
Change-Id: I61539dfae672bc104b8008f030f96e90f9ff48a5
2017-05-31 12:48:33 -05:00
|
|
|
{
|
|
|
|
DOC_CHANGED = 1010 // Document changed externally in storage
|
|
|
|
};
|
|
|
|
|
2016-03-05 09:27:30 -06:00
|
|
|
/// localStorePath the absolute root path of the chroot.
|
|
|
|
/// jailPath the path within the jail that the child uses.
|
2016-10-14 05:09:43 -05:00
|
|
|
StorageBase(const Poco::URI& uri,
|
|
|
|
const std::string& localStorePath,
|
|
|
|
const std::string& jailPath) :
|
2016-03-05 09:27:30 -06:00
|
|
|
_localStorePath(localStorePath),
|
2016-03-09 17:36:25 -06:00
|
|
|
_jailPath(jailPath),
|
2022-02-06 20:15:09 -06:00
|
|
|
_fileInfo(std::string(), "cool", std::string()),
|
wsd: move storage attributes to DocBroker
There are a number of races with having Storage
track the attributes. To fix them, we move all
attributes to DocBroker and correct a number
of issues.
The idea of the design is based on the fact that
we want to capture the attributes between
uploads, but based on the saved document.
That is, when we upload a document version, we
want to pass to the storage whether from the
perspective of the *Storage* there has been
any user-modifications or not. Since saving
to disk may happen multiple times between
uploads (not least because of failures), and
since saving resets the modified flag, we need
to capture the modified flag at each save and
propagate it until we upload successfully.
Upon uploading successfully, we reset the
attributes.
For this reason we have two attribute instances;
one is the 'current' attributes as being uploaded
and the other the 'next' one. We capture the
current state at saving into 'next' and we merge
with 'current' when saving succeeds and we
aren't already uploading (otherwise, we update
it and then discard it when uploading succeeds,
losing the last attributes).
Furthermore, because the modified flag is
reset after each save, and because we might
save and upload immediately after a
modification, we may not have the modified flag.
This means that we need some heuristics to
decide if there has been user-issued
modifications. (It is better to be conservative
here.) We try to detect this by introspecting
the commands we receive from users.
In effect, we capture the attributes when issuing
an internal save, we transfer the captured
attributes only when saving succeeds and we aren't
uploading, and from then on, uploading has to
succeed to reset the 'current' attributes. In the
meantime, if we fail to upload and issue another
save, the new attributes will be captured and
merged with the 'current' and the next upload
will not have any lost attributes.
Change-Id: I8c5e75d25ac235c6232318343678bf5c0933c31e
Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
2022-08-04 21:02:11 -05:00
|
|
|
_isDownloaded(false)
|
2016-03-03 20:05:30 -06:00
|
|
|
{
|
2021-06-30 09:37:31 -05:00
|
|
|
setUri(uri);
|
2021-11-18 06:08:14 -06:00
|
|
|
LOG_DBG("Storage ctor: " << COOLWSD::anonymizeUrl(_uri.toString()));
|
2016-03-03 20:05:30 -06:00
|
|
|
}
|
|
|
|
|
2022-04-03 19:11:00 -05:00
|
|
|
virtual ~StorageBase() { LOG_TRC("~StorageBase " << _uri.toString()); }
|
2017-09-12 03:03:26 -05:00
|
|
|
|
2018-11-28 02:07:29 -06:00
|
|
|
const Poco::URI& getUri() const { return _uri; }
|
|
|
|
|
|
|
|
const std::string& getJailPath() const { return _jailPath; };
|
2016-03-09 17:36:25 -06:00
|
|
|
|
2017-03-21 21:56:16 -05:00
|
|
|
/// Returns the root path to the jailed file.
|
2017-02-05 18:35:54 -06:00
|
|
|
const std::string& getRootFilePath() const { return _jailedFilePath; };
|
|
|
|
|
2021-05-24 13:11:40 -05:00
|
|
|
/// Returns the root path to the jailed file to be uploaded.
|
|
|
|
std::string getRootFilePathToUpload() const { return _jailedFilePath + TO_UPLOAD_SUFFIX; };
|
|
|
|
|
|
|
|
/// Returns the root path to the jailed file being uploaded.
|
|
|
|
std::string getRootFilePathUploading() const
|
|
|
|
{
|
|
|
|
return _jailedFilePath + TO_UPLOAD_SUFFIX + UPLOADING_SUFFIX;
|
|
|
|
};
|
|
|
|
|
2017-11-07 07:15:36 -06:00
|
|
|
/// Set the root path of the jailed file, only for use in cases where we actually have converted
|
|
|
|
/// it to another format, in the same directory
|
|
|
|
void setRootFilePath(const std::string& newPath)
|
|
|
|
{
|
|
|
|
// Could assert here that it is in the same directory?
|
|
|
|
_jailedFilePath = newPath;
|
2018-01-14 17:59:11 -06:00
|
|
|
}
|
2017-11-07 07:15:36 -06:00
|
|
|
|
2018-11-28 02:07:29 -06:00
|
|
|
const std::string& getRootFilePathAnonym() const { return _jailedFilePathAnonym; };
|
|
|
|
|
|
|
|
void setRootFilePathAnonym(const std::string& newPath)
|
|
|
|
{
|
|
|
|
_jailedFilePathAnonym = newPath;
|
|
|
|
}
|
|
|
|
|
2021-02-15 09:03:04 -06:00
|
|
|
void setDownloaded(bool loaded) { _isDownloaded = loaded; }
|
2018-11-28 02:07:29 -06:00
|
|
|
|
2021-02-15 09:03:04 -06:00
|
|
|
bool isDownloaded() const { return _isDownloaded; }
|
2016-10-14 05:09:43 -05:00
|
|
|
|
2018-11-28 02:07:29 -06:00
|
|
|
void setFileInfo(const FileInfo& fileInfo) { _fileInfo = fileInfo; }
|
|
|
|
|
2016-10-26 06:15:28 -05:00
|
|
|
/// Returns the basic information about the file.
|
2021-01-23 08:11:31 -06:00
|
|
|
const FileInfo& getFileInfo() const { return _fileInfo; }
|
2017-06-06 22:43:48 -05:00
|
|
|
|
2023-05-20 10:34:08 -05:00
|
|
|
const std::string& getLastModifiedTime() const { return _fileInfo.getLastModifiedTime(); }
|
|
|
|
void setLastModifiedTime(const std::string& modifiedTime)
|
|
|
|
{
|
|
|
|
_fileInfo.setLastModifiedTime(modifiedTime);
|
|
|
|
}
|
|
|
|
|
2018-11-29 01:42:43 -06:00
|
|
|
std::string getFileExtension() const { return Poco::Path(_fileInfo.getFilename()).getExtension(); }
|
2016-03-21 18:12:00 -05:00
|
|
|
|
2023-02-19 14:17:39 -06:00
|
|
|
STATE_ENUM(LockUpdateResult,
|
|
|
|
UNSUPPORTED, //< Locking is not supported on this host.
|
|
|
|
OK, //< Succeeded to either lock or unlock (see LockContext).
|
|
|
|
UNAUTHORIZED, //< 401, 403, 404.
|
|
|
|
FAILED //< Other failures.
|
|
|
|
);
|
|
|
|
|
2019-11-19 16:51:45 -06:00
|
|
|
/// Update the locking state (check-in/out) of the associated file
|
2023-02-19 14:17:39 -06:00
|
|
|
virtual LockUpdateResult updateLockState(const Authorization& auth, LockContext& lockCtx,
|
|
|
|
bool lock, const Attributes& attribs) = 0;
|
2019-11-19 16:51:45 -06:00
|
|
|
|
2016-03-21 18:12:00 -05:00
|
|
|
/// Returns a local file path for the given URI.
|
2016-02-19 15:19:15 -06:00
|
|
|
/// If necessary copies the file locally first.
|
2021-10-31 18:39:11 -05:00
|
|
|
virtual std::string downloadStorageFileToLocal(const Authorization& auth, LockContext& lockCtx,
|
|
|
|
const std::string& templateUri) = 0;
|
2016-02-19 15:19:15 -06:00
|
|
|
|
2020-12-28 13:58:07 -06:00
|
|
|
/// The asynchronous upload completion callback function.
|
|
|
|
using AsyncUploadCallback = std::function<void(const AsyncUpload&)>;
|
|
|
|
|
2021-01-10 11:20:23 -06:00
|
|
|
/// Writes the contents of the file back to the source asynchronously, if possible.
|
|
|
|
/// @param savedFile When the operation was saveAs, this is the path to the file that was saved.
|
2020-12-28 13:58:07 -06:00
|
|
|
/// @param asyncUploadCallback Used to communicate the result back to the caller.
|
2021-10-31 18:39:11 -05:00
|
|
|
virtual void uploadLocalFileToStorageAsync(const Authorization& auth, LockContext& lockCtx,
|
2020-12-28 13:58:07 -06:00
|
|
|
const std::string& saveAsPath,
|
|
|
|
const std::string& saveAsFilename,
|
wsd: move storage attributes to DocBroker
There are a number of races with having Storage
track the attributes. To fix them, we move all
attributes to DocBroker and correct a number
of issues.
The idea of the design is based on the fact that
we want to capture the attributes between
uploads, but based on the saved document.
That is, when we upload a document version, we
want to pass to the storage whether from the
perspective of the *Storage* there has been
any user-modifications or not. Since saving
to disk may happen multiple times between
uploads (not least because of failures), and
since saving resets the modified flag, we need
to capture the modified flag at each save and
propagate it until we upload successfully.
Upon uploading successfully, we reset the
attributes.
For this reason we have two attribute instances;
one is the 'current' attributes as being uploaded
and the other the 'next' one. We capture the
current state at saving into 'next' and we merge
with 'current' when saving succeeds and we
aren't already uploading (otherwise, we update
it and then discard it when uploading succeeds,
losing the last attributes).
Furthermore, because the modified flag is
reset after each save, and because we might
save and upload immediately after a
modification, we may not have the modified flag.
This means that we need some heuristics to
decide if there has been user-issued
modifications. (It is better to be conservative
here.) We try to detect this by introspecting
the commands we receive from users.
In effect, we capture the attributes when issuing
an internal save, we transfer the captured
attributes only when saving succeeds and we aren't
uploading, and from then on, uploading has to
succeed to reset the 'current' attributes. In the
meantime, if we fail to upload and issue another
save, the new attributes will be captured and
merged with the 'current' and the next upload
will not have any lost attributes.
Change-Id: I8c5e75d25ac235c6232318343678bf5c0933c31e
Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
2022-08-04 21:02:11 -05:00
|
|
|
const bool isRename, const Attributes&, SocketPoll&,
|
2022-07-24 18:53:11 -05:00
|
|
|
const AsyncUploadCallback& asyncUploadCallback) = 0;
|
2021-01-10 11:20:23 -06:00
|
|
|
|
|
|
|
/// Get the progress state of an asynchronous LocalFileToStorage upload.
|
|
|
|
virtual AsyncUpload queryLocalFileToStorageAsyncUploadState()
|
|
|
|
{
|
|
|
|
// Unsupported.
|
2020-12-28 13:58:07 -06:00
|
|
|
return AsyncUpload(AsyncUpload::State::None, UploadResult(UploadResult::Result::OK));
|
2021-01-10 11:20:23 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Cancels an active asynchronous LocalFileToStorage upload.
|
|
|
|
virtual void cancelLocalFileToStorageAsyncUpload()
|
|
|
|
{
|
|
|
|
// By default, nothing to do.
|
|
|
|
}
|
|
|
|
|
2016-04-16 07:13:59 -05:00
|
|
|
/// Must be called at startup to configure.
|
|
|
|
static void initialize();
|
|
|
|
|
2023-03-05 07:15:07 -06:00
|
|
|
STATE_ENUM(StorageType,
|
|
|
|
Unsupported, //< An unsupported type.
|
|
|
|
Unauthorized, //< The host is not allowed by the admin.
|
|
|
|
FileSystem, //< File-System storage. Only for testing.
|
|
|
|
Wopi //< WOPI-like storage.
|
|
|
|
);
|
|
|
|
|
|
|
|
/// Validates the given URI.
|
|
|
|
static StorageType validate(const Poco::URI& uri, bool takeOwnership);
|
|
|
|
|
2016-04-16 07:13:59 -05:00
|
|
|
/// Storage object creation factory.
|
2020-11-22 16:36:44 -06:00
|
|
|
/// @takeOwnership is for local files that are temporary,
|
|
|
|
/// such as convert-to requests.
|
|
|
|
static std::unique_ptr<StorageBase> create(const Poco::URI& uri, const std::string& jailRoot,
|
|
|
|
const std::string& jailPath, bool takeOwnership);
|
2018-10-20 05:57:53 -05:00
|
|
|
|
2019-10-07 06:51:30 -05:00
|
|
|
static Poco::Net::HTTPClientSession* getHTTPClientSession(const Poco::URI& uri);
|
2021-01-10 11:20:00 -06:00
|
|
|
static std::shared_ptr<http::Session> getHttpSession(const Poco::URI& uri);
|
2019-10-07 06:51:30 -05:00
|
|
|
|
2017-03-21 21:56:16 -05:00
|
|
|
protected:
|
|
|
|
|
2021-06-30 09:37:31 -05:00
|
|
|
/// Sanitize a URI by removing authorization tokens.
|
|
|
|
Poco::URI sanitizeUri(Poco::URI uri)
|
|
|
|
{
|
|
|
|
static const std::string access_token("access_token");
|
|
|
|
|
|
|
|
Poco::URI::QueryParameters queryParams = uri.getQueryParameters();
|
|
|
|
for (auto& param : queryParams)
|
|
|
|
{
|
|
|
|
// Sanitize more params as needed.
|
|
|
|
if (param.first == access_token)
|
|
|
|
{
|
|
|
|
// If access_token exists, clear it. But don't add it if not provided.
|
|
|
|
param.second.clear();
|
|
|
|
uri.setQueryParameters(queryParams);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return uri;
|
|
|
|
}
|
|
|
|
|
2021-06-21 07:23:18 -05:00
|
|
|
/// Saves new URI when resource was moved
|
2021-06-30 09:37:31 -05:00
|
|
|
void setUri(const Poco::URI& uri) { _uri = sanitizeUri(uri); }
|
2021-06-21 07:23:18 -05:00
|
|
|
|
2017-03-21 21:56:16 -05:00
|
|
|
/// Returns the root path of the jail directory of docs.
|
|
|
|
std::string getLocalRootPath() const;
|
2016-04-07 15:59:27 -05:00
|
|
|
|
2018-11-28 02:07:29 -06:00
|
|
|
private:
|
2021-06-21 07:23:18 -05:00
|
|
|
Poco::URI _uri;
|
wsd: faster jail setup via bind-mount
loolmount now works and supports mounting and
unmounting, plus numerous improvements,
refactoring, logging, etc.. When enabled,
binding improves the jail setup time by anywhere
from 2x to orders of magnitude (in docker, f.e.).
A new config entry mount_jail_tree controls
whether mounting is used or the old method of
linking/copying of jail contents. It is set to
true by default and falls back to linking/copying.
A test mount is done when the setting is enabled,
and if mounting fails, it's disabled to avoid noise.
Temporarily disabled for unit-tests until we can
cleanup lingering mounts after Jenkins aborts our
build job. In a future patch we will have mount/jail
cleanup as part of make.
The network/system files in /etc that need frequent
refreshing are now updated in systemplate to make
their most recent version available in the jails.
These files can change during the course of loolwsd
lifetime, and are unlikely to be updated in
systemplate after installation at all. We link to
them in the systemplate/etc directory, and if that
fails, we copy them before forking each kit
instance to have the latest.
This reworks the approach used to bind-mount the
jails and the templates such that the total is
now down to only three mounts: systemplate, lo, tmp.
As now systemplate and lotemplate are shared, they
must be mounted as readonly, this means that user/
must now be moved into tmp/user/ which is writable.
The mount-points must be recursive, because we mount
lo/ within the mount-point of systemplate (which is
the root of the jail). But because we (re)bind
recursively, and because both systemplate and
lotemplate are mounted for each jails, we need to
make them unbindable, so they wouldn't multiply the
mount-points for each jails (an explosive growth!)
Contrarywise, we don't want the mount-points to
be shared, because we don't expect to add/remove
mounts after a jail is created.
The random temp directory is now created and set
correctly, plus many logging and other improvements.
Change-Id: Iae3fda5e876cf47d2cae6669a87b5b826a8748df
Reviewed-on: https://gerrit.libreoffice.org/c/online/+/92829
Tested-by: Jenkins
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
2020-04-09 08:02:58 -05:00
|
|
|
const std::string _localStorePath;
|
|
|
|
const std::string _jailPath;
|
2016-03-09 17:36:25 -06:00
|
|
|
std::string _jailedFilePath;
|
2018-06-10 19:24:04 -05:00
|
|
|
std::string _jailedFilePathAnonym;
|
2016-03-21 18:12:00 -05:00
|
|
|
FileInfo _fileInfo;
|
2021-02-15 09:03:04 -06:00
|
|
|
bool _isDownloaded;
|
2017-10-03 04:59:39 -05:00
|
|
|
|
2016-06-07 02:18:49 -05:00
|
|
|
static bool FilesystemEnabled;
|
2020-04-29 14:24:33 -05:00
|
|
|
/// If true, use only the WOPI URL for whether to use SSL to talk to storage server
|
|
|
|
static bool SSLAsScheme;
|
|
|
|
/// If true, force SSL communication with storage server
|
2019-10-07 06:51:30 -05:00
|
|
|
static bool SSLEnabled;
|
2016-02-19 15:19:15 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
/// Trivial implementation of local storage that does not need do anything.
|
|
|
|
class LocalStorage : public StorageBase
|
|
|
|
{
|
|
|
|
public:
|
2020-11-22 16:36:44 -06:00
|
|
|
LocalStorage(const Poco::URI& uri, const std::string& localStorePath,
|
|
|
|
const std::string& jailPath, bool isTemporaryFile)
|
|
|
|
: StorageBase(uri, localStorePath, jailPath)
|
2023-02-01 05:30:30 -06:00
|
|
|
#if !MOBILEAPP
|
2020-11-22 16:36:44 -06:00
|
|
|
, _isTemporaryFile(isTemporaryFile)
|
2023-02-01 05:30:30 -06:00
|
|
|
#endif
|
2020-11-22 16:36:44 -06:00
|
|
|
, _isCopy(false)
|
2016-03-05 09:27:30 -06:00
|
|
|
{
|
2016-12-22 15:41:05 -06:00
|
|
|
LOG_INF("LocalStorage ctor with localStorePath: [" << localStorePath <<
|
2021-11-18 06:08:14 -06:00
|
|
|
"], jailPath: [" << jailPath << "], uri: [" << COOLWSD::anonymizeUrl(uri.toString()) << "].");
|
2023-02-01 05:30:30 -06:00
|
|
|
#if MOBILEAPP
|
|
|
|
(void) isTemporaryFile;
|
|
|
|
#endif
|
2016-03-05 09:27:30 -06:00
|
|
|
}
|
2016-02-19 15:19:15 -06:00
|
|
|
|
2016-12-22 15:41:05 -06:00
|
|
|
class LocalFileInfo
|
|
|
|
{
|
2016-10-26 06:15:28 -05:00
|
|
|
public:
|
2018-07-08 21:50:09 -05:00
|
|
|
LocalFileInfo(const std::string& userId,
|
2016-10-26 06:15:28 -05:00
|
|
|
const std::string& username)
|
2018-07-08 21:50:09 -05:00
|
|
|
: _userId(userId),
|
2016-10-26 06:15:28 -05:00
|
|
|
_username(username)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2018-11-27 02:09:21 -06:00
|
|
|
const std::string& getUserId() const { return _userId; }
|
|
|
|
const std::string& getUsername() const { return _username; }
|
|
|
|
|
|
|
|
private:
|
2018-07-08 21:50:09 -05:00
|
|
|
std::string _userId;
|
2016-10-26 06:15:28 -05:00
|
|
|
std::string _username;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Returns the URI specific file data
|
|
|
|
/// Also stores the basic file information which can then be
|
|
|
|
/// obtained using getFileInfo method
|
2017-05-12 10:42:03 -05:00
|
|
|
std::unique_ptr<LocalFileInfo> getLocalFileInfo();
|
2016-03-05 09:27:30 -06:00
|
|
|
|
2023-02-19 14:17:39 -06:00
|
|
|
LockUpdateResult updateLockState(const Authorization&, LockContext&, bool,
|
|
|
|
const Attributes&) override
|
2020-01-18 15:04:26 -06:00
|
|
|
{
|
2023-02-19 14:17:39 -06:00
|
|
|
return LockUpdateResult::OK;
|
2020-01-18 15:04:26 -06:00
|
|
|
}
|
2019-11-19 16:51:45 -06:00
|
|
|
|
2021-10-31 18:39:11 -05:00
|
|
|
std::string downloadStorageFileToLocal(const Authorization& auth, LockContext& lockCtx,
|
2020-11-29 18:11:59 -06:00
|
|
|
const std::string& templateUri) override;
|
2016-03-05 09:27:30 -06:00
|
|
|
|
2022-07-24 18:53:11 -05:00
|
|
|
void uploadLocalFileToStorageAsync(const Authorization& auth, LockContext& lockCtx,
|
|
|
|
const std::string& saveAsPath,
|
|
|
|
const std::string& saveAsFilename, const bool isRename,
|
wsd: move storage attributes to DocBroker
There are a number of races with having Storage
track the attributes. To fix them, we move all
attributes to DocBroker and correct a number
of issues.
The idea of the design is based on the fact that
we want to capture the attributes between
uploads, but based on the saved document.
That is, when we upload a document version, we
want to pass to the storage whether from the
perspective of the *Storage* there has been
any user-modifications or not. Since saving
to disk may happen multiple times between
uploads (not least because of failures), and
since saving resets the modified flag, we need
to capture the modified flag at each save and
propagate it until we upload successfully.
Upon uploading successfully, we reset the
attributes.
For this reason we have two attribute instances;
one is the 'current' attributes as being uploaded
and the other the 'next' one. We capture the
current state at saving into 'next' and we merge
with 'current' when saving succeeds and we
aren't already uploading (otherwise, we update
it and then discard it when uploading succeeds,
losing the last attributes).
Furthermore, because the modified flag is
reset after each save, and because we might
save and upload immediately after a
modification, we may not have the modified flag.
This means that we need some heuristics to
decide if there has been user-issued
modifications. (It is better to be conservative
here.) We try to detect this by introspecting
the commands we receive from users.
In effect, we capture the attributes when issuing
an internal save, we transfer the captured
attributes only when saving succeeds and we aren't
uploading, and from then on, uploading has to
succeed to reset the 'current' attributes. In the
meantime, if we fail to upload and issue another
save, the new attributes will be captured and
merged with the 'current' and the next upload
will not have any lost attributes.
Change-Id: I8c5e75d25ac235c6232318343678bf5c0933c31e
Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
2022-08-04 21:02:11 -05:00
|
|
|
const Attributes&, SocketPoll&,
|
2022-07-24 18:53:11 -05:00
|
|
|
const AsyncUploadCallback& asyncUploadCallback) override;
|
2016-03-09 17:36:25 -06:00
|
|
|
|
|
|
|
private:
|
2023-02-01 05:30:30 -06:00
|
|
|
#if !MOBILEAPP
|
2020-11-22 16:36:44 -06:00
|
|
|
/// True if we the source file a temporary that we own.
|
|
|
|
/// Typically for convert-to requests.
|
|
|
|
const bool _isTemporaryFile;
|
2023-02-01 05:30:30 -06:00
|
|
|
#endif
|
2016-03-09 17:36:25 -06:00
|
|
|
/// True if the jailed file is not linked but copied.
|
|
|
|
bool _isCopy;
|
2016-10-26 07:47:42 -05:00
|
|
|
static std::atomic<unsigned> LastLocalStorageId;
|
2016-02-19 15:19:15 -06:00
|
|
|
};
|
|
|
|
|
2016-08-13 23:01:13 -05:00
|
|
|
/// WOPI protocol backed storage.
|
2016-03-03 20:05:30 -06:00
|
|
|
class WopiStorage : public StorageBase
|
|
|
|
{
|
|
|
|
public:
|
2020-12-14 13:00:55 -06:00
|
|
|
WopiStorage(const Poco::URI& uri, const std::string& localStorePath,
|
|
|
|
const std::string& jailPath)
|
|
|
|
: StorageBase(uri, localStorePath, jailPath)
|
|
|
|
, _wopiSaveDuration(std::chrono::milliseconds::zero())
|
2016-03-03 20:05:30 -06:00
|
|
|
{
|
2019-08-26 20:16:54 -05:00
|
|
|
LOG_INF("WopiStorage ctor with localStorePath: ["
|
|
|
|
<< localStorePath << "], jailPath: [" << jailPath << "], uri: ["
|
2021-11-18 06:08:14 -06:00
|
|
|
<< COOLWSD::anonymizeUrl(uri.toString()) << ']');
|
2016-03-03 20:05:30 -06:00
|
|
|
}
|
|
|
|
|
2023-05-20 11:17:31 -05:00
|
|
|
class WOPIFileInfo : public FileInfo
|
2016-10-29 20:15:00 -05:00
|
|
|
{
|
2020-06-11 09:54:27 -05:00
|
|
|
void init();
|
2016-10-26 06:15:28 -05:00
|
|
|
public:
|
2018-04-24 11:09:37 -05:00
|
|
|
enum class TriState
|
|
|
|
{
|
|
|
|
False,
|
|
|
|
True,
|
|
|
|
Unset
|
|
|
|
};
|
|
|
|
|
2020-06-11 09:54:27 -05:00
|
|
|
/// warning - removes items from object.
|
2023-05-20 08:34:25 -05:00
|
|
|
WOPIFileInfo(const FileInfo& fileInfo, const Poco::JSON::Object::Ptr& object,
|
2023-05-20 05:19:15 -05:00
|
|
|
const Poco::URI& uriObject);
|
2016-10-26 06:15:28 -05:00
|
|
|
|
2018-11-21 02:07:52 -06:00
|
|
|
const std::string& getUserId() const { return _userId; }
|
|
|
|
const std::string& getUsername() const { return _username; }
|
|
|
|
const std::string& getUserExtraInfo() const { return _userExtraInfo; }
|
2023-01-04 03:09:21 -06:00
|
|
|
const std::string& getUserPrivateInfo() const { return _userPrivateInfo; }
|
2018-11-21 02:07:52 -06:00
|
|
|
const std::string& getWatermarkText() const { return _watermarkText; }
|
2018-11-14 13:16:26 -06:00
|
|
|
const std::string& getTemplateSaveAs() const { return _templateSaveAs; }
|
2019-05-21 22:33:26 -05:00
|
|
|
const std::string& getTemplateSource() const { return _templateSource; }
|
2020-06-09 11:43:58 -05:00
|
|
|
const std::string& getBreadcrumbDocName() const { return _breadcrumbDocName; }
|
2021-02-06 16:11:06 -06:00
|
|
|
const std::string& getFileUrl() const { return _fileUrl; }
|
2023-05-20 09:21:14 -05:00
|
|
|
const std::string& getPostMessageOrigin() { return _postMessageOrigin; }
|
2023-05-20 11:22:19 -05:00
|
|
|
const std::string& getHideUserList() { return _hideUserList; }
|
2020-06-09 11:43:58 -05:00
|
|
|
|
2018-11-21 02:07:52 -06:00
|
|
|
bool getUserCanWrite() const { return _userCanWrite; }
|
|
|
|
void setHidePrintOption(bool hidePrintOption) { _hidePrintOption = hidePrintOption; }
|
|
|
|
bool getHidePrintOption() const { return _hidePrintOption; }
|
|
|
|
bool getHideSaveOption() const { return _hideSaveOption; }
|
|
|
|
void setHideExportOption(bool hideExportOption) { _hideExportOption = hideExportOption; }
|
|
|
|
bool getHideExportOption() const { return _hideExportOption; }
|
2022-10-13 11:50:20 -05:00
|
|
|
void setHideRepairOption(bool hideRepairOption) { _hideRepairOption = hideRepairOption; }
|
|
|
|
bool getHideRepairOption() const { return _hideRepairOption; }
|
2018-11-21 02:07:52 -06:00
|
|
|
bool getEnableOwnerTermination() const { return _enableOwnerTermination; }
|
|
|
|
bool getDisablePrint() const { return _disablePrint; }
|
|
|
|
bool getDisableExport() const { return _disableExport; }
|
|
|
|
bool getDisableCopy() const { return _disableCopy; }
|
|
|
|
bool getDisableInactiveMessages() const { return _disableInactiveMessages; }
|
2019-06-04 06:57:53 -05:00
|
|
|
bool getDownloadAsPostMessage() const { return _downloadAsPostMessage; }
|
2018-11-21 02:07:52 -06:00
|
|
|
bool getUserCanNotWriteRelative() const { return _userCanNotWriteRelative; }
|
|
|
|
bool getEnableInsertRemoteImage() const { return _enableInsertRemoteImage; }
|
2023-02-14 09:48:11 -06:00
|
|
|
bool getEnableRemoteLinkPicker() const { return _enableRemoteLinkPicker; }
|
2018-11-21 02:07:52 -06:00
|
|
|
bool getEnableShare() const { return _enableShare; }
|
2019-04-30 09:21:44 -05:00
|
|
|
bool getSupportsRename() const { return _supportsRename; }
|
2019-11-19 14:54:29 -06:00
|
|
|
bool getSupportsLocks() const { return _supportsLocks; }
|
2019-04-30 09:21:44 -05:00
|
|
|
bool getUserCanRename() const { return _userCanRename; }
|
2023-05-20 11:22:19 -05:00
|
|
|
|
2018-11-21 02:07:52 -06:00
|
|
|
TriState getDisableChangeTrackingShow() const { return _disableChangeTrackingShow; }
|
|
|
|
TriState getDisableChangeTrackingRecord() const { return _disableChangeTrackingRecord; }
|
|
|
|
TriState getHideChangeTrackingControls() const { return _hideChangeTrackingControls; }
|
2023-05-20 11:22:19 -05:00
|
|
|
|
2018-11-21 02:07:52 -06:00
|
|
|
private:
|
2016-10-26 06:15:28 -05:00
|
|
|
/// User id of the user accessing the file
|
2018-07-08 21:50:09 -05:00
|
|
|
std::string _userId;
|
|
|
|
/// Obfuscated User id used for logging the UserId.
|
|
|
|
std::string _obfuscatedUserId;
|
2016-10-26 06:15:28 -05:00
|
|
|
/// Display Name of user accessing the file
|
|
|
|
std::string _username;
|
2023-01-04 03:09:21 -06:00
|
|
|
/// Extra public info per user, typically mail and other links, as json, shared with everyone.
|
2017-05-28 11:20:49 -05:00
|
|
|
std::string _userExtraInfo;
|
2023-01-04 03:09:21 -06:00
|
|
|
/// Private info per user, for API keys and other non-public information.
|
|
|
|
std::string _userPrivateInfo;
|
2017-09-04 08:40:04 -05:00
|
|
|
/// In case a watermark has to be rendered on each tile.
|
|
|
|
std::string _watermarkText;
|
2018-11-14 13:16:26 -06:00
|
|
|
/// In case we want to use this file as a template, it should be first re-saved under this name (using PutRelativeFile).
|
|
|
|
std::string _templateSaveAs;
|
2019-05-21 22:33:26 -05:00
|
|
|
/// In case we want to use this file as a template.
|
|
|
|
std::string _templateSource;
|
2020-06-09 11:43:58 -05:00
|
|
|
/// User readable string of document name to show in UI, if present.
|
|
|
|
std::string _breadcrumbDocName;
|
2021-02-06 16:11:06 -06:00
|
|
|
/// The optional FileUrl, used to download the document if provided.
|
|
|
|
std::string _fileUrl;
|
2016-10-25 02:13:00 -05:00
|
|
|
/// WOPI Post message property
|
|
|
|
std::string _postMessageOrigin;
|
2023-05-20 11:22:19 -05:00
|
|
|
/// If set to "true", user list on the status bar will be hidden
|
|
|
|
/// If set to "mobile" | "tablet" | "desktop", will be hidden on a specified device
|
|
|
|
/// (may be joint, delimited by commas eg. "mobile,tablet")
|
|
|
|
std::string _hideUserList;
|
|
|
|
/// If we should disable change-tracking visibility by default (meaningful at loading).
|
|
|
|
TriState _disableChangeTrackingShow = WOPIFileInfo::TriState::Unset;
|
|
|
|
/// If we should disable change-tracking ability by default (meaningful at loading).
|
|
|
|
TriState _disableChangeTrackingRecord = WOPIFileInfo::TriState::Unset;
|
|
|
|
/// If we should hide change-tracking commands for this user.
|
|
|
|
TriState _hideChangeTrackingControls = WOPIFileInfo::TriState::Unset;
|
|
|
|
/// If user accessing the file has write permission
|
|
|
|
bool _userCanWrite = false;
|
2016-11-10 06:21:39 -06:00
|
|
|
/// Hide print button from UI
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _hidePrintOption = false;
|
2016-11-10 06:21:39 -06:00
|
|
|
/// Hide save button from UI
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _hideSaveOption = false;
|
2016-11-10 06:21:39 -06:00
|
|
|
/// Hide 'Download as' button/menubar item from UI
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _hideExportOption = false;
|
2022-10-13 11:50:20 -05:00
|
|
|
/// Hide the 'Repair' button/item from the UI
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _hideRepairOption = false;
|
2016-11-08 07:37:28 -06:00
|
|
|
/// If WOPI host has enabled owner termination feature on
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _enableOwnerTermination = false;
|
2016-12-13 05:30:43 -06:00
|
|
|
/// If WOPI host has allowed the user to print the document
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _disablePrint = false;
|
2016-12-13 05:30:43 -06:00
|
|
|
/// If WOPI host has allowed the user to export the document
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _disableExport = false;
|
2016-12-13 05:30:43 -06:00
|
|
|
/// If WOPI host has allowed the user to copy to/from the document
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _disableCopy = false;
|
|
|
|
/// If WOPI host has allowed the cool to show texts on the overlay informing about
|
|
|
|
/// inactivity, or if the integration is handling that.
|
|
|
|
bool _disableInactiveMessages = false;
|
|
|
|
/// For the (mobile) integrations, to indicate that the downloading for printing, exporting,
|
|
|
|
/// or slideshows should be intercepted and sent as a postMessage instead of handling directly.
|
|
|
|
bool _downloadAsPostMessage = false;
|
2017-10-03 10:06:02 -05:00
|
|
|
/// If set to false, users can access the save-as functionality
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _userCanNotWriteRelative = true;
|
2018-12-12 04:05:31 -06:00
|
|
|
/// If set to true, users can access the insert remote image functionality
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _enableInsertRemoteImage = false;
|
2023-02-14 09:48:11 -06:00
|
|
|
/// If set to true, users can access the remote link picker functionality
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _enableRemoteLinkPicker = false;
|
2018-12-12 04:05:31 -06:00
|
|
|
/// If set to true, users can access the file share functionality
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _enableShare = false;
|
2019-11-19 14:54:29 -06:00
|
|
|
/// If WOPI host supports locking
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _supportsLocks = false;
|
2019-04-30 09:21:44 -05:00
|
|
|
/// If WOPI host supports rename
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _supportsRename = false;
|
2019-04-30 09:21:44 -05:00
|
|
|
/// If user is allowed to rename the document
|
2023-05-20 11:22:19 -05:00
|
|
|
bool _userCanRename = false;
|
2016-10-26 06:15:28 -05:00
|
|
|
};
|
|
|
|
|
2017-05-12 10:42:03 -05:00
|
|
|
/// Returns the response of CheckFileInfo WOPI call for URI that was
|
|
|
|
/// provided during the initial creation of the WOPI storage.
|
2016-10-26 06:15:28 -05:00
|
|
|
/// Also extracts the basic file information from the response
|
|
|
|
/// which can then be obtained using getFileInfo()
|
2019-11-19 16:51:45 -06:00
|
|
|
/// Also sets up the locking context for future operations.
|
2021-10-31 18:39:11 -05:00
|
|
|
std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(const Authorization& auth, LockContext& lockCtx);
|
2021-06-18 09:50:40 -05:00
|
|
|
/// Implementation of getWOPIFileInfo for specific URI
|
2021-10-31 18:39:11 -05:00
|
|
|
std::unique_ptr<WOPIFileInfo> getWOPIFileInfoForUri(Poco::URI uriObject,
|
|
|
|
const Authorization& auth,
|
|
|
|
LockContext& lockCtx,
|
|
|
|
unsigned redirectLimit);
|
2019-11-19 16:51:45 -06:00
|
|
|
|
|
|
|
/// Update the locking state (check-in/out) of the associated file
|
2023-02-19 14:17:39 -06:00
|
|
|
LockUpdateResult updateLockState(const Authorization& auth, LockContext& lockCtx, bool lock,
|
|
|
|
const Attributes& attribs) override;
|
2016-03-21 18:12:00 -05:00
|
|
|
|
2016-03-03 20:05:30 -06:00
|
|
|
/// uri format: http://server/<...>/wopi*/files/<id>/content
|
2021-10-31 18:39:11 -05:00
|
|
|
std::string downloadStorageFileToLocal(const Authorization& auth, LockContext& lockCtx,
|
2020-11-29 18:11:59 -06:00
|
|
|
const std::string& templateUri) override;
|
2016-03-11 12:45:13 -06:00
|
|
|
|
2021-10-31 18:39:11 -05:00
|
|
|
void uploadLocalFileToStorageAsync(const Authorization& auth, LockContext& lockCtx,
|
|
|
|
const std::string& saveAsPath,
|
2020-12-28 13:58:07 -06:00
|
|
|
const std::string& saveAsFilename, const bool isRename,
|
wsd: move storage attributes to DocBroker
There are a number of races with having Storage
track the attributes. To fix them, we move all
attributes to DocBroker and correct a number
of issues.
The idea of the design is based on the fact that
we want to capture the attributes between
uploads, but based on the saved document.
That is, when we upload a document version, we
want to pass to the storage whether from the
perspective of the *Storage* there has been
any user-modifications or not. Since saving
to disk may happen multiple times between
uploads (not least because of failures), and
since saving resets the modified flag, we need
to capture the modified flag at each save and
propagate it until we upload successfully.
Upon uploading successfully, we reset the
attributes.
For this reason we have two attribute instances;
one is the 'current' attributes as being uploaded
and the other the 'next' one. We capture the
current state at saving into 'next' and we merge
with 'current' when saving succeeds and we
aren't already uploading (otherwise, we update
it and then discard it when uploading succeeds,
losing the last attributes).
Furthermore, because the modified flag is
reset after each save, and because we might
save and upload immediately after a
modification, we may not have the modified flag.
This means that we need some heuristics to
decide if there has been user-issued
modifications. (It is better to be conservative
here.) We try to detect this by introspecting
the commands we receive from users.
In effect, we capture the attributes when issuing
an internal save, we transfer the captured
attributes only when saving succeeds and we aren't
uploading, and from then on, uploading has to
succeed to reset the 'current' attributes. In the
meantime, if we fail to upload and issue another
save, the new attributes will be captured and
merged with the 'current' and the next upload
will not have any lost attributes.
Change-Id: I8c5e75d25ac235c6232318343678bf5c0933c31e
Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
2022-08-04 21:02:11 -05:00
|
|
|
const Attributes&, SocketPoll& socketPoll,
|
2020-12-28 13:58:07 -06:00
|
|
|
const AsyncUploadCallback& asyncUploadCallback) override;
|
|
|
|
|
2021-10-31 18:39:11 -05:00
|
|
|
/// Total time taken for making WOPI calls during uploading.
|
2020-12-06 15:55:22 -06:00
|
|
|
std::chrono::milliseconds getWopiSaveDuration() const { return _wopiSaveDuration; }
|
2016-10-14 07:46:49 -05:00
|
|
|
|
2021-07-22 15:17:14 -05:00
|
|
|
virtual AsyncUpload queryLocalFileToStorageAsyncUploadState() override
|
|
|
|
{
|
|
|
|
if (_uploadHttpSession)
|
|
|
|
return AsyncUpload(AsyncUpload::State::Running, UploadResult(UploadResult::Result::OK));
|
|
|
|
else
|
|
|
|
return AsyncUpload(AsyncUpload::State::None, UploadResult(UploadResult::Result::OK));
|
|
|
|
}
|
|
|
|
|
2020-11-15 14:40:21 -06:00
|
|
|
protected:
|
|
|
|
struct WopiUploadDetails
|
|
|
|
{
|
|
|
|
const std::string filePathAnonym;
|
|
|
|
const std::string uriAnonym;
|
|
|
|
const std::string httpResponseReason;
|
2023-05-02 13:21:33 -05:00
|
|
|
const http::StatusCode httpResponseCode;
|
2020-11-15 14:40:21 -06:00
|
|
|
const std::size_t size;
|
|
|
|
const bool isSaveAs;
|
|
|
|
const bool isRename;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Handles the response from the server when uploading the document.
|
2020-11-29 18:11:59 -06:00
|
|
|
UploadResult handleUploadToStorageResponse(const WopiUploadDetails& details,
|
2020-11-15 14:40:21 -06:00
|
|
|
std::string responseString);
|
|
|
|
|
2020-06-14 11:44:10 -05:00
|
|
|
private:
|
|
|
|
/// Initialize an HTTPRequest instance with the common settings and headers.
|
|
|
|
/// Older Poco versions don't support copying HTTPRequest objects, so we can't generate them.
|
|
|
|
void initHttpRequest(Poco::Net::HTTPRequest& request, const Poco::URI& uri,
|
2021-10-31 18:39:11 -05:00
|
|
|
const Authorization& auth) const;
|
2020-06-14 11:44:10 -05:00
|
|
|
|
2021-01-10 11:20:00 -06:00
|
|
|
/// Create an http::Request with the common headers.
|
2021-10-31 18:39:11 -05:00
|
|
|
http::Request initHttpRequest(const Poco::URI& uri, const Authorization& auth) const;
|
2021-01-10 11:20:00 -06:00
|
|
|
|
2021-02-07 20:53:46 -06:00
|
|
|
/// Download the document from the given URI.
|
|
|
|
/// Does not add authorization tokens or any other logic.
|
|
|
|
std::string downloadDocument(const Poco::URI& uriObject, const std::string& uriAnonym,
|
2021-10-31 18:39:11 -05:00
|
|
|
const Authorization& auth, unsigned redirectLimit);
|
2021-02-07 20:53:46 -06:00
|
|
|
|
2016-10-14 07:46:49 -05:00
|
|
|
private:
|
2021-02-06 16:11:06 -06:00
|
|
|
/// A URl provided by the WOPI host to use for GetFile.
|
|
|
|
std::string _fileUrl;
|
|
|
|
|
2021-02-15 07:54:30 -06:00
|
|
|
// Time spend in saving the file from storage
|
2020-12-06 15:55:22 -06:00
|
|
|
std::chrono::milliseconds _wopiSaveDuration;
|
2020-12-28 13:58:07 -06:00
|
|
|
|
|
|
|
/// The http::Session used for uploading asynchronously.
|
|
|
|
std::shared_ptr<http::Session> _uploadHttpSession;
|
2016-03-03 20:05:30 -06:00
|
|
|
};
|
|
|
|
|
2016-02-19 15:19:15 -06:00
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|