1/* 2 * Copyright (c) 2014 3 * @copyright Copyright (c) 2016, Björn SchieÃle <[email protected]> 4 * 5 * This file is licensed under the Affero General Public License version 3 6 * or later. 7 * 8 * See the COPYING-README file. 9 * 10 */ 11 12/* global FileActions, Files, FileList */ 13/* global dragOptions, folderDropOptions */ 14if (!OCA.Sharing) { 15 OCA.Sharing = {}; 16} 17if (!OCA.Files) { 18 OCA.Files = {}; 19} 20/** 21 * @namespace 22 */ 23OCA.Sharing.PublicApp = { 24 _initialized: false, 25 26 /** 27 * Initializes the public share app. 28 * 29 * @param $el container 30 */ 31 initialize: function ($el) { 32 var self = this; 33 var fileActions; 34 if (this._initialized) { 35 return; 36 } 37 fileActions = new OCA.Files.FileActions(); 38 // default actions 39 fileActions.registerDefaultActions(); 40 // regular actions 41 fileActions.merge(OCA.Files.fileActions); 42 43 // in case apps would decide to register file actions later, 44 // replace the global object with this one 45 OCA.Files.fileActions = fileActions; 46 47 this._initialized = true; 48 var urlParams = OC.Util.History.parseUrlQuery(); 49 this.initialDir = urlParams.path || '/'; 50 51 var token = $('#sharingToken').val(); 52 var hideDownload = $('#hideDownload').val(); 53 54 // Prevent all right-click options if hideDownload is enabled 55 if (hideDownload === 'true') { 56 window.oncontextmenu = function(event) { 57 event.preventDefault(); 58 event.stopPropagation(); 59 return false; 60 }; 61 } 62 63 // file list mode ? 64 if ($el.find('.files-filestable').length) { 65 // Toggle for grid view 66 this.$showGridView = $('input#showgridview'); 67 this.$showGridView.on('change', _.bind(this._onGridviewChange, this)); 68 69 var filesClient = new OC.Files.Client({ 70 host: OC.getHost(), 71 port: OC.getPort(), 72 userName: token, 73 // note: password not be required, the endpoint 74 // will recognize previous validation from the session 75 root: OC.getRootPath() + '/public.php/webdav', 76 useHTTPS: OC.getProtocol() === 'https' 77 }); 78 79 this.fileList = new OCA.Files.FileList( 80 $el, 81 { 82 id: 'files.public', 83 dragOptions: dragOptions, 84 folderDropOptions: folderDropOptions, 85 fileActions: fileActions, 86 detailsViewEnabled: false, 87 filesClient: filesClient, 88 enableUpload: true, 89 multiSelectMenu: [ 90 { 91 name: 'copyMove', 92 displayName: t('files', 'Move or copy'), 93 iconClass: 'icon-external', 94 }, 95 { 96 name: 'download', 97 displayName: t('files', 'Download'), 98 iconClass: 'icon-download', 99 }, 100 { 101 name: 'delete', 102 displayName: t('files', 'Delete'), 103 iconClass: 'icon-delete', 104 } 105 ] 106 } 107 ); 108 if (hideDownload === 'true') { 109 this.fileList._allowSelection = false; 110 } 111 this.files = OCA.Files.Files; 112 this.files.initialize(); 113 // TODO: move to PublicFileList.initialize() once 114 // the code was split into a separate class 115 OC.Plugins.attach('OCA.Sharing.PublicFileList', this.fileList); 116 } 117 118 var mimetype = $('#mimetype').val(); 119 var mimetypeIcon = $('#mimetypeIcon').val(); 120 mimetypeIcon = mimetypeIcon.substring(0, mimetypeIcon.length - 3); 121 mimetypeIcon = mimetypeIcon + 'svg'; 122 123 var previewSupported = $('#previewSupported').val(); 124 125 if (typeof FileActions !== 'undefined') { 126 // Show file preview if previewer is available, images are already handled by the template 127 if (mimetype.substr(0, mimetype.indexOf('/')) !== 'image' && $('.publicpreview').length === 0) { 128 // Trigger default action if not download TODO 129 var spec = FileActions.getDefaultFileAction(mimetype, 'file', OC.PERMISSION_READ); 130 if (spec && spec.action) { 131 spec.action($('#filename').val()); 132 } 133 } 134 } 135 136 // dynamically load image previews 137 var bottomMargin = 350; 138 var previewWidth = $(window).width(); 139 var previewHeight = $(window).height() - bottomMargin; 140 previewHeight = Math.max(200, previewHeight); 141 var params = { 142 x: Math.ceil(previewWidth * window.devicePixelRatio), 143 y: Math.ceil(previewHeight * window.devicePixelRatio), 144 a: 'true', 145 file: encodeURIComponent(this.initialDir + $('#filename').val()), 146 scalingup: 0 147 }; 148 149 var imgcontainer = $('<img class="publicpreview" alt="">'); 150 if (hideDownload === 'false') { 151 imgcontainer = $('<a href="' + $('#previewURL').val() + '" target="_blank"></a>').append(imgcontainer); 152 } 153 var img = imgcontainer.hasClass('publicpreview')? imgcontainer: imgcontainer.find('.publicpreview'); 154 img.css({ 155 'max-width': previewWidth, 156 'max-height': previewHeight 157 }); 158
159 if (OCA.Viewer && OCA.Viewer.mimetypes.includes(mimetype) 160 && (mimetype.startsWith('image/') || mimetype.startsWith('video/') || mimetype.startsWith('audio'))) { 161 OCA.Viewer.setRootElement('#imgframe') 162 OCA.Viewer.open({ path: '/' }) 163 } else if (mimetype.substr(0, mimetype.indexOf('/')) === 'text' && window.btoa) { 164 if (OC.appswebroots['files_texteditor'] !== undefined || 165 OC.appswebroots['text'] !== undefined) { 166 // the text editor handles the previewing 167 return; 168 } 169 // Undocumented Url to public WebDAV endpoint 170 var url = parent.location.protocol + '//' + location.host + OC.linkTo('', 'public.php/webdav'); 171 $.ajax({ 172 url: url, 173 headers: { 174 Authorization: 'Basic ' + btoa(token + ':'), 175 Range: 'bytes=0-10000' 176 } 177 }).then(function (data) { 178 self._showTextPreview(data, previewHeight); 179 }); 180 } else if ((previewSupported === 'true' && mimetype.substr(0, mimetype.indexOf('/')) !== 'video') || 181 mimetype.substr(0, mimetype.indexOf('/')) === 'image' && 182 mimetype !== 'image/svg+xml') { 183 img.attr('src', OC.generateUrl('/apps/files_sharing/publicpreview/' + token + '?' + OC.buildQueryString(params))); 184 imgcontainer.appendTo('#imgframe'); 185 } else if (mimetype.substr(0, mimetype.indexOf('/')) !== 'video') { 186 img.attr('src', mimetypeIcon); 187 img.attr('width', 128); 188 // "#imgframe" is either empty or it contains an audio preview that 189 // the icon should appear before, so the container should be 190 // prepended to the frame. 191 imgcontainer.prependTo('#imgframe'); 192 } else if (previewSupported === 'true') { 193 $('#imgframe > video').attr('poster', OC.generateUrl('/apps/files_sharing/publicpreview/' + token + '?' + OC.buildQueryString(params))); 194 } 195 196 if (this.fileList) { 197 // TODO: move this to a separate PublicFileList class that extends OCA.Files.FileList (+ unit tests) 198 this.fileList.getDownloadUrl = function (filename, dir, isDir) { 199 var path = dir || this.getCurrentDirectory(); 200 if (_.isArray(filename)) { 201 filename = JSON.stringify(filename); 202 } 203 var params = { 204 path: path 205 }; 206 if (filename) { 207 params.files = filename; 208 } 209 return OC.generateUrl('/s/' + token + '/download') + '?' + OC.buildQueryString(params); 210 }; 211 212 this.fileList._createRow = function(fileData) { 213 var $tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments); 214 if (hideDownload === 'true') { 215 this.fileActions.currentFile = $tr.find('td'); 216 217 // Remove the link. This means that files without a default action fail hard 218 $tr.find('a.name').attr('href', '#'); 219 220 delete this.fileActions.actions.all.Download; 221 } 222 return $tr; 223 }; 224 225 this.fileList.isSelectedDownloadable = function () { 226 return hideDownload !== 'true'; 227 }; 228 229 this.fileList.getUploadUrl = function(fileName, dir) { 230 if (_.isUndefined(dir)) { 231 dir = this.getCurrentDirectory(); 232 } 233 234 var pathSections = dir.split('/'); 235 if (!_.isUndefined(fileName)) { 236 pathSections.push(fileName); 237 } 238 var encodedPath = ''; 239 _.each(pathSections, function(section) { 240 if (section !== '') { 241 encodedPath += '/' + encodeURIComponent(section); 242 } 243 }); 244 var base = ''; 245 246 if (!this._uploader.isXHRUpload()) { 247 // also add auth in URL due to POST workaround 248 base = OC.getProtocol() + '://' + token + '@' + OC.getHost() + (OC.getPort() ? ':' + OC.getPort() : ''); 249 } 250 return base + OC.getRootPath() + '/public.php/webdav' + encodedPath; 251 }; 252 253 this.fileList.getAjaxUrl = function (action, params) { 254 params = params || {}; 255 params.t = token; 256 return OC.filePath('files_sharing', 'ajax', action + '.php') + '?' + OC.buildQueryString(params); 257 }; 258 259 this.fileList.linkTo = function (dir) { 260 return OC.generateUrl('/s/' + token + '') + '?' + OC.buildQueryString({path: dir}); 261 }; 262 263 this.fileList.generatePreviewUrl = function (urlSpec) { 264 urlSpec = urlSpec || {}; 265 if (!urlSpec.x) { 266 urlSpec.x = this.$table.data('preview-x') || 250; 267 } 268 if (!urlSpec.y) { 269 urlSpec.y = this.$table.data('preview-y') || 250; 270 } 271 urlSpec.x *= window.devicePixelRatio; 272 urlSpec.y *= window.devicePixelRatio; 273 urlSpec.x = Math.ceil(urlSpec.x); 274 urlSpec.y = Math.ceil(urlSpec.y); 275 var token = $('#dirToken').val(); 276 return OC.generateUrl('/apps/files_sharing/publicpreview/' + token + '?' + OC.buildQueryString(urlSpec)); 277 }; 278 279 this.fileList.updateEmptyContent = function() { 280 this.$el.find('.emptycontent .uploadmessage').text( 281 t('files_sharing', 'You can upload into this folder') 282 ); 283 OCA.Files.FileList.prototype.updateEmptyContent.apply(this, arguments); 284 }; 285 286 this.fileList._uploader.on('fileuploadadd', function(e, data) { 287 if (!data.headers) { 288 data.headers = {}; 289 } 290 291 data.headers.Authorization = 'Basic ' + btoa(token + ':'); 292 }); 293 294 // do not allow sharing from the public page 295 delete this.fileList.fileActions.actions.all.Share; 296 297 this.fileList.changeDirectory(this.initialDir || '/', false, true); 298 299 // URL history handling 300 this.fileList.$el.on('changeDirectory', _.bind(this._onDirectoryChanged, this)); 301 OC.Util.History.addOnPopStateHandler(_.bind(this._onUrlChanged, this)); 302 303 $('#download').click(function (e) { 304 e.preventDefault();
305 OC.redirect(FileList.getDownloadUrl()); 306 }); 307 308 if (hideDownload === 'true') { 309 this.fileList.$el.find('.summary').find('td:first-child').remove(); 310 } 311 } 312 313 $(document).on('click', '#directLink', function () { 314 $(this).focus(); 315 $(this).select(); 316 }); 317 318 $('.save-form').submit(function (event) { 319 event.preventDefault(); 320 321 var remote = $(this).find('#remote_address').val(); 322 var token = $('#sharingToken').val(); 323 var owner = $('#save-external-share').data('owner'); 324 var ownerDisplayName = $('#save-external-share').data('owner-display-name'); 325 var name = $('#save-external-share').data('name'); 326 var isProtected = $('#save-external-share').data('protected') ? 1 : 0; 327 OCA.Sharing.PublicApp._createFederatedShare(remote, token, owner, ownerDisplayName, name, isProtected); 328 }); 329 330 $('#remote_address').on("keyup paste", function() { 331 if ($(this).val() === '' || $('#save-external-share > .icon.icon-loading-small').length > 0) { 332 $('#save-button-confirm').prop('disabled', true); 333 } else { 334 $('#save-button-confirm').prop('disabled', false); 335 } 336 }); 337 338 self._bindShowTermsAction(); 339 340 // legacy 341 window.FileList = this.fileList; 342 }, 343 344 /** 345 * Binds the click action for the "terms of service" action. 346 * Shows an OC info dialog on click. 347 * 348 * @private 349 */ 350 _bindShowTermsAction: function() { 351 $('#show-terms-dialog').on('click', function() { 352 OC.dialogs.info($('#disclaimerText').val(), t('files_sharing', 'Terms of service')); 353 }); 354 }, 355 356 _showTextPreview: function (data, previewHeight) { 357 var textDiv = $('<div></div>').addClass('text-preview'); 358 textDiv.text(data); 359 textDiv.appendTo('#imgframe'); 360 var divHeight = textDiv.height(); 361 if (data.length > 999) { 362 var ellipsis = $('<div></div>').addClass('ellipsis'); 363 ellipsis.html('(…)'); 364 ellipsis.appendTo('#imgframe'); 365 } 366 if (divHeight > previewHeight) { 367 textDiv.height(previewHeight); 368 } 369 }, 370 371 /** 372 * Toggle showing gridview by default or not 373 * 374 * @returns {undefined} 375 */ 376 _onGridviewChange: function() { 377 const isGridView = this.$showGridView.is(':checked'); 378 this.$showGridView.next('#view-toggle') 379 .removeClass('icon-toggle-filelist icon-toggle-pictures') 380 .addClass(isGridView ? 'icon-toggle-filelist' : 'icon-toggle-pictures') 381 this.$showGridView.next('#view-toggle').attr( 382 'title', 383 isGridView ? t('files', 'Show list view') : t('files', 'Show grid view'), 384 ) 385 386 if (this.fileList) { 387 this.fileList.setGridView(isGridView); 388 } 389 }, 390 391 _onDirectoryChanged: function (e) { 392 OC.Util.History.pushState({ 393 // arghhhh, why is this not called "dir" !? 394 path: e.dir 395 }); 396 }, 397 398 _onUrlChanged: function (params) { 399 this.fileList.changeDirectory(params.path || params.dir, false, true); 400 }, 401 402 403 /** 404 * fall back to old behaviour where we redirect the user to his server to mount 405 * the public link instead of creating a dedicated federated share 406 * 407 * @param {any} remote - 408 * @param {any} token - 409 * @param {any} owner - 410 * @param {any} ownerDisplayName - 411 * @param {any} name - 412 * @param {any} isProtected - 413 * @private 414 */ 415 _legacyCreateFederatedShare: function (remote, token, owner, ownerDisplayName, name, isProtected) { 416 417 var self = this; 418 var location = window.location.protocol + '//' + window.location.host + OC.getRootPath(); 419 420 if(remote.substr(-1) !== '/') { 421 remote += '/' 422 } 423 424 var url = remote + 'index.php/apps/files#' + 'remote=' + encodeURIComponent(location) // our location is the remote for the other server 425 + "&token=" + encodeURIComponent(token) + "&owner=" + encodeURIComponent(owner) +"&ownerDisplayName=" + encodeURIComponent(ownerDisplayName) + "&name=" + encodeURIComponent(name) + "&protected=" + isProtected; 426 427 428 if (remote.indexOf('://') > 0) { 429 OC.redirect(url); 430 } else { 431 // if no protocol is specified, we automatically detect it by testing https and http 432 // this check needs to happen on the server due to the Content Security Policy directive 433 $.get(OC.generateUrl('apps/files_sharing/testremote'), {remote: remote}).then(function (protocol) { 434 if (protocol !== 'http' && protocol !== 'https') { 435 self._toggleLoading(); 436 OC.dialogs.alert(t('files_sharing', 'No compatible server found at {remote}', {remote: remote}), 437 t('files_sharing', 'Invalid server URL')); 438 } else { 439 OC.redirect(protocol + '://' + url); 440 } 441 }); 442 } 443 }, 444 445 _toggleLoading: function() { 446 var loading = $('#save-external-share > .icon.icon-loading-small').length === 0; 447 if (loading) { 448 $('#save-external-share > .icon-external') 449 .removeClass("icon-external") 450 .addClass("icon-loading-small"); 451 $('#save-external-share #save-button-confirm').prop("disabled", true); 452 453 } else { 454 $('#save-external-share > .icon-loading-small') 455 .addClass("icon-external") 456 .removeClass("icon-loading-small"); 457 $('#save-external-share #save-button-confirm').prop("disabled", false); 458 459 } 460 }, 461 462 _createFederatedShare: function (remote, token, owner, ownerDisplayName, name, isProtected) { 463 var self = this; 464 465 this._toggleLoading(); 466 467 if (remote.indexOf('@') === -1) { 468 this._legacyCreateFederatedShare(remote, token, owner, ownerDisplayName, name, isProtected); 469 return; 470 } 471 472 $.post( 473 OC.generateUrl('/apps/federatedfilesharing/createFederatedShare'), 474 { 475 'shareWith': remote, 476 'token': token 477 } 478 ).done( 479 function (data) { 480 var url = data.remoteUrl; 481 482 if (url.indexOf('://') > 0) { 483 OC.redirect(url); 484 } else { 485 OC.redirect('http://' + url); 486 } 487 } 488 ).fail( 489 function (jqXHR) { 490 OC.dialogs.alert(JSON.parse(jqXHR.responseText).message, 491 t('files_sharing', 'Failed to add the public link to your Nextcloud')); 492 self._toggleLoading(); 493 } 494 ); 495 } 496}; 497 498window.addEventListener('DOMContentLoaded', function () { 499 // FIXME: replace with OC.Plugins.register() 500 if (window.TESTING) { 501 return; 502 } 503 504 var App = OCA.Sharing.PublicApp; 505 // defer app init, to give a chance to plugins to register file actions 506 _.defer(function () { 507 App.initialize($('#preview')); 508 }); 509 510 if (window.Files) { 511 // HACK: for oc-dialogs previews that depends on Files: 512 Files.generatePreviewUrl = function (urlSpec) { 513 return App.fileList.generatePreviewUrl(urlSpec); 514 }; 515 } 516 517});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.