forked from Klomgor/online
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathStorage.hpp
627 lines (511 loc) · 22 KB
/
Storage.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*
* 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.
#pragma once
#include <common/Authorization.hpp>
#include <common/Common.hpp>
#include <common/ConfigUtil.hpp>
#include <common/Log.hpp>
#include <wsd/COOLWSD.hpp>
#include <Poco/URI.h>
#include <memory>
#include <string>
#include <chrono>
class LockContext;
/// Base class of all Storage abstractions.
class StorageBase
{
public:
/// Represents basic file's attributes.
/// Used for local and network files.
class FileInfo
{
public:
FileInfo(std::size_t size, std::string filename, std::string ownerId,
std::string modifiedTime)
: _size(size)
, _filename(std::move(filename))
, _ownerId(std::move(ownerId))
, _modifiedTime(std::move(modifiedTime))
{
}
FileInfo(const FileInfo& fileInfo)
: _size(fileInfo._size)
, _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;
}
bool isValid() const
{
// 0-byte files are valid; LO will open them as new docs.
return !_filename.empty();
}
std::size_t getSize() const { return _size; }
const std::string& getFilename() const { return _filename; }
const std::string& getOwnerId() const { return _ownerId; }
/// Set the last modified time as reported to the WOPI host.
void setLastModifiedTime(const std::string& modifiedTime) { _modifiedTime = modifiedTime; }
/// Get the last modified time as reported by the WOPI host, empty if unsafe to rely on
const std::string& getLastModifiedTime() const { return _modifiedTime; }
/// Sometimes an up-load fails, leaving our timestamp in an unknown state
bool isLastModifiedTimeSafe() const { return !_modifiedTime.empty(); }
/// Set last modified time as unsafe
void setLastModifiedTimeUnSafe() { _modifiedTime.clear(); }
private:
std::size_t _size;
std::string _filename;
std::string _ownerId;
std::string _modifiedTime; ///< Opaque modified timestamp as received from the server.
};
/// 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.
void dumpState(std::ostream& os, const std::string& indent = "\n ") const
{
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;
};
/// 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.
class UploadResult final
{
public:
STATE_ENUM(Result,
OK = 0, ///< Uploaded successfully
DISKFULL, ///< Unused.
TOO_LARGE, ///< 413
UNAUTHORIZED, ///< 401, 403, 404
DOC_CHANGED, /**< Document changed in storage */
CONFLICT, ///< 409
FAILED);
explicit UploadResult(Result result)
: _result(result)
{
}
UploadResult(Result result, std::string reason)
: _result(result)
, _reason(std::move(reason))
{
}
void setResult(Result result) { _result = result; }
Result getResult() const { return _result; }
void setSaveAsResult(const std::string& name, const std::string& url)
{
_saveAsName = name;
_saveAsUrl = url;
}
const std::string& getSaveAsName() const { return _saveAsName; }
const std::string& getSaveAsUrl() const { return _saveAsUrl; }
void setReason(const std::string& msg) { _reason = msg; }
const std::string& getReason() const { return _reason; }
private:
Result _result;
std::string _saveAsName;
std::string _saveAsUrl;
std::string _reason;
};
/// The state of an asynchronous request.
template <typename TResult> class AsyncRequest final
{
public:
STATE_ENUM(
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 TResult.
Complete ///< The last async upload request completed (regardless of the server's response).
);
AsyncRequest(State state, TResult result)
: _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 TResult& result() const { return _result; }
private:
State _state;
TResult _result;
};
/// The state of an asynchronous Upload request.
using AsyncUpload = AsyncRequest<UploadResult>;
STATE_ENUM(LockState,
LOCK, ///< Lock the document.
UNLOCK, ///< Unlock the document .
);
/// Represents the Lock request result, with a Result code
/// and a reason message (typically for errors).
/// Note: the reason message may be displayed to the clients.
class LockUpdateResult final
{
public:
STATE_ENUM(Status,
UNSUPPORTED, ///< Locking is not supported on this host.
OK, ///< Succeeded to either lock or unlock (see LockContext).
UNAUTHORIZED, ///< 401, 403, 404.
FAILED ///< Other failures.
);
/// Construct a LockUpdateResult without a failure reason.
LockUpdateResult(Status status, LockState requestedLockState)
: LockUpdateResult(status, requestedLockState, /*reason=*/std::string())
{
}
/// Construct a LockUpdateResult with a failure reason.
LockUpdateResult(Status status, LockState requestedLockState, std::string reason)
: _status(status)
, _reason(std::move(reason))
, _requestedLockState(requestedLockState)
{
}
void setState(Status status) { _status = status; }
Status getStatus() const { return _status; }
void setReason(const std::string& msg) { _reason = msg; }
const std::string& getReason() const { return _reason; }
LockState requestedLockState() const { return _requestedLockState; }
private:
Status _status;
std::string _reason;
LockState _requestedLockState;
};
/// The state of an asynchronous lock request.
using AsyncLockUpdate = AsyncRequest<LockUpdateResult>;
enum class COOLStatusCode
{
DOC_CHANGED = 1010 // Document changed externally in storage
};
/// localStorePath the absolute root path of the chroot.
/// jailPath the path within the jail that the child uses.
StorageBase(const Poco::URI& uri, const std::string& localStorePath,
const std::string& jailPath)
: _localStorePath(localStorePath)
, _jailPath(jailPath)
, _fileInfo(/*size=*/0, /*filename=*/std::string(), /*ownerId=*/"cool",
/*modifiledTime=*/std::string())
, _isDownloaded(false)
{
setUri(uri);
LOG_DBG("Storage ctor: " << COOLWSD::anonymizeUrl(_uri.toString()));
}
virtual ~StorageBase() { LOG_TRC("~StorageBase " << _uri.toString()); }
const Poco::URI& getUri() const { return _uri; }
const std::string& getJailPath() const { return _jailPath; }
/// Returns the root path to the jailed file.
const std::string& getRootFilePath() const { return _jailedFilePath; }
/// 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;
}
/// 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;
}
const std::string& getRootFilePathAnonym() const { return _jailedFilePathAnonym; }
void setRootFilePathAnonym(const std::string& newPath)
{
_jailedFilePathAnonym = newPath;
}
void setDownloaded(bool loaded) { _isDownloaded = loaded; }
bool isDownloaded() const { return _isDownloaded; }
void setFileInfo(const FileInfo& fileInfo) { _fileInfo = fileInfo; }
/// Returns the basic information about the file.
const FileInfo& getFileInfo() const { return _fileInfo; }
const std::string& getLastModifiedTime() const { return _fileInfo.getLastModifiedTime(); }
void setLastModifiedTime(const std::string& modifiedTime) { _fileInfo.setLastModifiedTime(modifiedTime); }
bool isLastModifiedTimeSafe() const { return _fileInfo.isLastModifiedTimeSafe(); }
void setLastModifiedTimeUnSafe() { _fileInfo.setLastModifiedTimeUnSafe(); }
std::string getFileExtension() const { return Poco::Path(_fileInfo.getFilename()).getExtension(); }
/// Update the locking state (check-in/out) of the associated file synchronously.
virtual LockUpdateResult updateLockState(const Authorization& auth, LockContext& lockCtx,
LockState lock, const Attributes& attribs) = 0;
/// The asynchronous upload completion callback function.
using AsyncLockStateCallback = std::function<void(const AsyncLockUpdate&)>;
/// Update the locking state (check-in/out) of the associated file asynchronously.
virtual void updateLockStateAsync(const Authorization& auth, LockContext& lockCtx,
LockState lock, const Attributes& attribs,
SocketPoll& socketPoll,
const AsyncLockStateCallback& asyncLockStateCallback) = 0;
/// Returns a local file path for the given URI.
/// If necessary copies the file locally first.
virtual std::string downloadStorageFileToLocal(const Authorization& auth, LockContext& lockCtx,
const std::string& templateUri) = 0;
/// The asynchronous upload completion callback function.
using AsyncUploadCallback = std::function<void(const AsyncUpload&)>;
/// 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.
/// @param asyncUploadCallback Used to communicate the result back to the caller.
/// @returns The size of the document.
virtual std::size_t
uploadLocalFileToStorageAsync(const Authorization& auth, LockContext& lockCtx,
const std::string& saveAsPath, const std::string& saveAsFilename,
const bool isRename, const Attributes&, SocketPoll&,
const AsyncUploadCallback& asyncUploadCallback) = 0;
/// Get the progress state of an asynchronous LocalFileToStorage upload.
virtual AsyncUpload queryLocalFileToStorageAsyncUploadState()
{
// Unsupported.
return AsyncUpload(AsyncUpload::State::None, UploadResult(UploadResult::Result::OK));
}
/// Cancels an active asynchronous LocalFileToStorage upload.
virtual void cancelLocalFileToStorageAsyncUpload()
{
// By default, nothing to do.
}
/// Must be called at startup to configure.
static void initialize();
STATE_ENUM(StorageType,
Unsupported, ///< An unsupported type.
Unauthorized, ///< The host is not allowed by the admin.
FileSystem, ///< File-System storage. Only for testing.
#if !MOBILEAPP
Wopi ///< WOPI-like storage.
#endif //!MOBILEAPP
);
/// Validates the given URI.
static StorageType validate(const Poco::URI& uri, bool takeOwnership);
/// Storage object creation factory.
/// @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);
protected:
/// Sanitize a URI by removing authorization tokens.
void 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;
}
}
}
/// Saves new URI when resource was moved
void setUri(const Poco::URI& uri)
{
_uri = uri;
sanitizeUri(_uri);
}
/// Returns the root path of the jail directory of docs.
std::string getLocalRootPath() const;
private:
Poco::URI _uri;
const std::string _localStorePath;
const std::string _jailPath;
std::string _jailedFilePath;
std::string _jailedFilePathAnonym;
FileInfo _fileInfo;
bool _isDownloaded;
static bool FilesystemEnabled;
};
/// Trivial implementation of local storage that does not need do anything.
class LocalStorage : public StorageBase
{
public:
LocalStorage(const Poco::URI& uri, const std::string& localStorePath,
const std::string& jailPath, [[maybe_unused]] bool isTemporaryFile)
: StorageBase(uri, localStorePath, jailPath)
#if !MOBILEAPP
, _isTemporaryFile(isTemporaryFile)
#endif
, _isCopy(false)
{
LOG_INF("LocalStorage ctor with localStorePath: ["
<< localStorePath << "], jailPath: [" << jailPath << "], uri: ["
<< COOLWSD::anonymizeUrl(uri.toString()) << "].");
}
class LocalFileInfo
{
public:
LocalFileInfo(const std::string& userId,
const std::string& username)
: _userId(userId),
_username(username)
{
}
const std::string& getUserId() const { return _userId; }
const std::string& getUsername() const { return _username; }
private:
std::string _userId;
std::string _username;
};
/// Returns the URI specific file data
/// Also stores the basic file information which can then be
/// obtained using getFileInfo method
std::unique_ptr<LocalFileInfo> getLocalFileInfo();
LockUpdateResult updateLockState(const Authorization&, LockContext&,
StorageBase::LockState requestedLockState,
const Attributes&) override
{
return LockUpdateResult(LockUpdateResult::Status::OK, requestedLockState);
}
void updateLockStateAsync(const Authorization&, LockContext&, LockState requestedLockState,
const Attributes&, SocketPoll&,
const AsyncLockStateCallback& asyncLockStateCallback) override
{
if (asyncLockStateCallback)
{
asyncLockStateCallback(AsyncLockUpdate(
AsyncLockUpdate::State::Complete,
LockUpdateResult(LockUpdateResult::Status::OK, requestedLockState)));
}
}
std::string downloadStorageFileToLocal(const Authorization& auth, LockContext& lockCtx,
const std::string& templateUri) override;
std::size_t
uploadLocalFileToStorageAsync(const Authorization& auth, LockContext& lockCtx,
const std::string& saveAsPath, const std::string& saveAsFilename,
const bool isRename, const Attributes&, SocketPoll&,
const AsyncUploadCallback& asyncUploadCallback) override;
private:
#if !MOBILEAPP
/// True if we the source file a temporary that we own.
/// Typically for convert-to requests.
const bool _isTemporaryFile;
#endif
/// True if the jailed file is not linked but copied.
bool _isCopy;
static std::atomic<unsigned> LastLocalStorageId;
};
/// Represents whether the underlying file is locked
/// and with what token.
class LockContext final
{
/// Do we have support for locking for a storage.
bool _supportsLocks;
/// Do we own the (leased) lock currently
StorageBase::LockState _lockState;
/// Name if we need it to use consistently for locking
std::string _lockToken;
/// Time of last successful lock (re-)acquisition
std::chrono::steady_clock::time_point _lastLockTime;
public:
LockContext()
: _supportsLocks(false)
, _lockState(StorageBase::LockState::UNLOCK)
, _refreshSeconds(ConfigUtil::getConfigValue<int>("storage.wopi.locking.refresh", 900))
{
}
/// one-time setup for supporting locks & create token
void initSupportsLocks();
/// Returns true if locks are supported.
bool supportsLocks() const { return _supportsLocks; }
/// Returns the lock token used identify our lock on the server.
/// Meaningful only when supportsLocks is true.
const std::string& lockToken() const { return _lockToken; }
/// Returns true if locked.
bool isLocked() const { return _lockState == StorageBase::LockState::LOCK; }
/// Sets the new state and bumps the timer.
void setState(StorageBase::LockState state)
{
_lockState = state;
bumpTimer();
}
/// wait another refresh cycle
void bumpTimer() { _lastLockTime = std::chrono::steady_clock::now(); }
/// do we need to refresh our lock ?
bool needsRefresh(const std::chrono::steady_clock::time_point& now) const;
void dumpState(std::ostream& os) const;
private:
const std::chrono::seconds _refreshSeconds;
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */