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
|
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files_Sharing;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCA\Files_Sharing\AppInfo\Application;
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
use OCA\Viewer\Event\LoadViewer;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\Template\ExternalShareMenuAction;
use OCP\AppFramework\Http\Template\LinkMenuAction;
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
use OCP\AppFramework\Http\Template\SimpleMenuAction;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\Constants;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\File;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Share\IPublicShareTemplateProvider;
use OCP\Share\IShare;
use OCP\Util;
class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider {
public function __construct(
private IUserManager $userManager,
private IAccountManager $accountManager,
private IPreview $previewManager,
protected FederatedShareProvider $federatedShareProvider,
private IUrlGenerator $urlGenerator,
private IEventDispatcher $eventDispatcher,
private IL10N $l10n,
private Defaults $defaults,
private IConfig $config,
private IRequest $request,
private IInitialState $initialState,
private IAppConfig $appConfig,
) {
}
public function shouldRespond(IShare $share): bool {
return true;
}
public function renderPage(IShare $share, string $token, string $path): TemplateResponse {
$shareNode = $share->getNode();
$ownerName = '';
$ownerId = '';
// Only make the share owner public if they allowed to show their name
$owner = $this->userManager->get($share->getShareOwner());
if ($owner instanceof IUser) {
$ownerAccount = $this->accountManager->getAccount($owner);
$ownerNameProperty = $ownerAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
if ($ownerNameProperty->getScope() === IAccountManager::SCOPE_PUBLISHED) {
$ownerName = $owner->getDisplayName();
$ownerId = $owner->getUID();
}
}
$view = 'public-share';
if ($shareNode instanceof File) {
$view = 'public-file-share';
$this->initialState->provideInitialState('fileId', $shareNode->getId());
} elseif (($share->getPermissions() & Constants::PERMISSION_CREATE)
&& !($share->getPermissions() & Constants::PERMISSION_READ)
) {
// share is a folder with create but no read permissions -> file drop only
$view = 'public-file-drop';
// Only needed for file drops
$this->initialState->provideInitialState(
'disclaimer',
$this->appConfig->getValueString('core', 'shareapi_public_link_disclaimertext'),
);
}
// Set up initial state
$this->initialState->provideInitialState('isPublic', true);
$this->initialState->provideInitialState('sharingToken', $token);
$this->initialState->provideInitialState('sharePermissions', $share->getPermissions());
$this->initialState->provideInitialState('filename', $shareNode->getName());
$this->initialState->provideInitialState('view', $view);
// Load scripts and styles for UI
Util::addInitScript('files', 'init');
Util::addInitScript(Application::APP_ID, 'init');
Util::addInitScript(Application::APP_ID, 'init-public');
Util::addScript('files', 'main');
// Add file-request script if needed
$attributes = $share->getAttributes();
$isFileRequest = $attributes?->getAttribute('fileRequest', 'enabled') === true;
if ($isFileRequest) {
Util::addScript(Application::APP_ID, 'public-file-request');
}
// Load Viewer scripts
if (class_exists(LoadViewer::class)) {
$this->eventDispatcher->dispatchTyped(new LoadViewer());
}
// Allow external apps to register their scripts
$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($share));
// OpenGraph Support: http://ogp.me/
$this->addOpenGraphHeaders($share);
// CSP to allow office
$csp = new ContentSecurityPolicy();
$csp->addAllowedFrameDomain('\'self\'');
$response = new PublicTemplateResponse(
'files',
'index',
);
$response->setContentSecurityPolicy($csp);
// If the share has a label, use it as the title
if ($share->getLabel() !== '') {
$response->setHeaderTitle($share->getLabel());
} else {
$response->setHeaderTitle($shareNode->getName());
}
if ($ownerName !== '') {
$response->setHeaderDetails($this->l10n->t('shared by %s', [$ownerName]));
}
// Create the header action menu
$headerActions = [];
if ($view !== 'public-file-drop') {
// The download URL is used for the "download" header action as well as in some cases for the direct link
$downloadUrl = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', [
'token' => $token,
'filename' => ($shareNode instanceof File) ? $shareNode->getName() : null,
]);
// If not a file drop, then add the download header action
$headerActions[] = new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $downloadUrl, 0, (string)$shareNode->getSize());
// If remote sharing is enabled also add the remote share action to the menu
if ($this->federatedShareProvider->isOutgoingServer2serverShareEnabled() && !$share->getHideDownload()) {
$headerActions[] = new ExternalShareMenuAction(
// TRANSLATORS The placeholder refers to the software product name as in 'Add to your Nextcloud'
$this->l10n->t('Add to your %s', [$this->defaults->getProductName()]),
'icon-external',
$ownerId,
$ownerName,
$shareNode->getName(),
);
}
}
$shareUrl = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $token]);
// By default use the share link as the direct link
$directLink = $shareUrl;
// Add the direct link header actions
if ($shareNode->getMimePart() === 'image') {
// If this is a file and especially an image directly point to the image preview
$directLink = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $token]);
} elseif (($share->getPermissions() & Constants::PERMISSION_READ) && !$share->getHideDownload()) {
// Can read and no download restriction, so just download it
$directLink = $downloadUrl ?? $shareUrl;
}
$headerActions[] = new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $directLink);
$response->setHeaderActions($headerActions);
return $response;
}
/**
* Add OpenGraph headers to response for preview
* @param IShare $share The share for which to add the headers
*/
protected function addOpenGraphHeaders(IShare $share): void {
$shareNode = $share->getNode();
$token = $share->getToken();
$shareUrl = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $token]);
// Handle preview generation for OpenGraph
if ($this->previewManager->isMimeSupported($shareNode->getMimetype())) {
// For images we can use direct links
if ($shareNode->getMimePart() === 'image') {
$ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $token]);
// Whatsapp is kind of picky about their size requirements
if ($this->request->isUserAgent(['/^WhatsApp/'])) {
$ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview', [
'token' => $token,
'x' => 256,
'y' => 256,
'a' => true,
]);
}
} else {
// For normal files use preview API
$ogPreview = $this->urlGenerator->linkToRouteAbsolute(
'files_sharing.PublicPreview.getPreview',
[
'x' => 256,
'y' => 256,
'file' => $share->getTarget(),
'token' => $token,
],
);
}
} else {
// No preview supported, so we just add the favicon
$ogPreview = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-fb.png'));
}
Util::addHeader('meta', ['property' => 'og:title', 'content' => $shareNode->getName()]);
Util::addHeader('meta', ['property' => 'og:description', 'content' => $this->defaults->getName() . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')]);
Util::addHeader('meta', ['property' => 'og:site_name', 'content' => $this->defaults->getName()]);
Util::addHeader('meta', ['property' => 'og:url', 'content' => $shareUrl]);
Util::addHeader('meta', ['property' => 'og:type', 'content' => 'object']);
Util::addHeader('meta', ['property' => 'og:image', 'content' => $ogPreview]);
}
}
|