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