Issues (108)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

js/galleryview.js (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
/* global Handlebars, Gallery */
2
(function ($, _, OC, t, Gallery) {
3
	"use strict";
4
5
	var TEMPLATE_ADDBUTTON = '<a href="#" class="button new"><img src="{{iconUrl}}" alt="{{addText}}"></img></a>';
6
7
	/**
8
	 * Builds and updates the Gallery view
9
	 *
10
	 * @constructor
11
	 */
12
	var View = function () {
13
		this.element = $('#gallery');
14
		this.loadVisibleRows.loading = false;
15
		this._setupUploader();
16
		this.breadcrumb = new Gallery.Breadcrumb();
17
		this.emptyContentElement = $('#emptycontent');
18
		this.controlsElement = $('#controls');
19
	};
20
21
	View.prototype = {
22
		element: null,
23
		breadcrumb: null,
24
		requestId: -1,
25
		emptyContentElement: null,
26
		controlsElement: null,
27
28
		/**
29
		 * Removes all thumbnails from the view
30
		 */
31
		clear: function () {
32
			this.loadVisibleRows.processing = false;
33
			this.loadVisibleRows.loading = null;
34
			// We want to keep all the events
35
			this.element.children().detach();
36
			this.showLoading();
37
		},
38
39
		/**
40
		 * Populates the view if there are images or albums to show
41
		 *
42
		 * @param {string} albumPath
43
		 * @param {string|undefined} errorMessage
44
		 */
45
		init: function (albumPath, errorMessage) {
46
			// Only do it when the app is initialised
47
			if (this.requestId === -1) {
48
				this._initButtons();
49
				this._blankUrl();
50
			}
51
			if ($.isEmptyObject(Gallery.imageMap)) {
52
				Gallery.view.showEmptyFolder(albumPath, errorMessage);
53
			} else {
54
				this.viewAlbum(albumPath);
55
			}
56
57
			this._setBackgroundColour();
58
		},
59
60
		/**
61
		 * Starts the slideshow
62
		 *
63
		 * @param {string} path
64
		 * @param {string} albumPath
65
		 */
66
		startSlideshow: function (path, albumPath) {
67
			var album = Gallery.albumMap[albumPath];
68
			var images = album.images;
69
			var startImage = Gallery.imageMap[path];
70
			Gallery.slideShow(images, startImage, false);
71
		},
72
73
		/**
74
		 * Sets up the controls and starts loading the gallery rows
75
		 *
76
		 * @param {string|null} albumPath
77
		 */
78
		viewAlbum: function (albumPath) {
79
			albumPath = albumPath || '';
80
			if (!Gallery.albumMap[albumPath]) {
81
				return;
82
			}
83
84
			this.clear();
85
86
			if (albumPath !== Gallery.currentAlbum
87
				|| (albumPath === Gallery.currentAlbum &&
88
				Gallery.albumMap[albumPath].etag !== Gallery.currentEtag)) {
89
				Gallery.currentAlbum = albumPath;
90
				Gallery.currentEtag = Gallery.albumMap[albumPath].etag;
91
				this._setupButtons(albumPath);
92
			}
93
94
			Gallery.albumMap[albumPath].viewedItems = 0;
95
			Gallery.albumMap[albumPath].preloadOffset = 0;
96
97
			// Each request has a unique ID, so that we can track which request a row belongs to
98
			this.requestId = Math.random();
99
			Gallery.albumMap[Gallery.currentAlbum].requestId = this.requestId;
100
101
			// Loading rows without blocking the execution of the rest of the script
102
			setTimeout(function () {
103
				this.loadVisibleRows.activeIndex = 0;
104
				this.loadVisibleRows(Gallery.albumMap[Gallery.currentAlbum]);
105
			}.bind(this), 0);
106
		},
107
108
		/**
109
		 * Manages the sorting interface
110
		 *
111
		 * @param {string} sortType name or date
112
		 * @param {string} sortOrder asc or des
113
		 */
114
		sortControlsSetup: function (sortType, sortOrder) {
115
			var reverseSortType = 'date';
116
			if (sortType === 'date') {
117
				reverseSortType = 'name';
118
			}
119
			this._setSortButton(sortType, sortOrder, true);
120
			this._setSortButton(reverseSortType, 'asc', false); // default icon
121
		},
122
123
		/**
124
		 * Loads and displays gallery rows on screen
125
		 *
126
		 * view.loadVisibleRows.loading holds the Promise of a row
127
		 *
128
		 * @param {Album} album
129
		 */
130
		loadVisibleRows: function (album) {
131
			var view = this;
132
			// Wait for the previous request to be completed
133
			if (this.loadVisibleRows.processing) {
134
				return;
135
			}
136
137
			/**
138
			 * At this stage, there is no loading taking place, so we can look for new rows
139
			 */
140
141
			var scroll = $('#content-wrapper').scrollTop() + $(window).scrollTop();
142
			// 2 windows worth of rows is the limit from which we need to start loading new rows.
143
			// As we scroll down, it grows
144
			var targetHeight = ($(window).height() * 2) + scroll;
145
			// We throttle rows in order to try and not generate too many CSS resizing events at
146
			// the same time
147
			var showRows = _.throttle(function (album) {
148
149
				// If we've reached the end of the album, we kill the loader
150
				if (!(album.viewedItems < album.subAlbums.length + album.images.length)) {
151
					view.loadVisibleRows.processing = false;
152
					view.loadVisibleRows.loading = null;
153
					return;
154
				}
155
156
				// Prevents creating rows which are no longer required. I.e when changing album
157
				if (view.requestId !== album.requestId) {
158
					return;
159
				}
160
161
				// We can now safely create a new row
162
				var row = album.getRow($(window).width());
163
				var rowDom = row.getDom();
164
				view.element.append(rowDom);
165
166
				return album.fillNextRow(row).then(function () {
167
					if (album.viewedItems < album.subAlbums.length + album.images.length &&
168
						view.element.height() < targetHeight) {
169
						return showRows(album);
170
					}
171
					// No more rows to load at the moment
172
					view.loadVisibleRows.processing = false;
173
					view.loadVisibleRows.loading = null;
174
				}, function () {
175
					// Something went wrong, so kill the loader
176
					view.loadVisibleRows.processing = false;
177
					view.loadVisibleRows.loading = null;
178
				});
179
			}, 100);
180
			if (this.element.height() < targetHeight) {
181
				this._showNormal();
182
				this.loadVisibleRows.processing = true;
183
				album.requestId = view.requestId;
184
				this.loadVisibleRows.loading = showRows(album);
185
			}
186
		},
187
188
		/**
189
		 * Shows an empty gallery message
190
		 *
191
		 * @param {string} albumPath
192
		 * @param {string|null} errorMessage
193
		 */
194
		showEmptyFolder: function (albumPath, errorMessage) {
195
			var message = '<div class="icon-gallery"></div>';
196
			var uploadAllowed = true;
197
198
			this.element.children().detach();
199
			this.removeLoading();
200
201
			if (!_.isUndefined(errorMessage) && errorMessage !== null) {
202
				message += '<h2>' + t('gallery',
203
						'Album cannot be shown') + '</h2>';
204
				message += '<p>' + escapeHTML(errorMessage) + '</p>';
0 ignored issues
show
escapeHTML does not seem to be defined.
Loading history...
205
				uploadAllowed = false;
206
			} else {
207
				message += '<h2>' + t('gallery',
208
						'No media files found') + '</h2>';
209
				// We can't upload yet on the public side
210
				if (Gallery.token) {
211
					message += '<p>' + t('gallery',
212
							'Upload pictures in the files app to display them here') + '</p>';
213
				} else {
214
					message += '<p>' + t('gallery',
215
							'Upload new files via drag and drop or by using the [+] button above') +
216
						'</p>';
217
				}
218
			}
219
			this.emptyContentElement.html(message);
220
			this.emptyContentElement.removeClass('hidden');
221
222
			this._hideButtons(uploadAllowed);
223
			Gallery.currentAlbum = albumPath;
224
			var availableWidth = $(window).width() - Gallery.buttonsWidth;
225
			this.breadcrumb.init(albumPath, availableWidth);
226
			Gallery.config.albumDesign = null;
227
		},
228
229
		/**
230
		 * Dims the controls bar when retrieving new content. Matches the effect in Files
231
		 */
232
		dimControls: function () {
233
			// Use the existing mask if its already there
234
			var $mask = this.controlsElement.find('.mask');
235
			if ($mask.exists()) {
236
				return;
237
			}
238
			$mask = $('<div class="mask transparent"></div>');
239
			this.controlsElement.append($mask);
240
			$mask.removeClass('transparent');
241
		},
242
243
		/**
244
		 * Shows the infamous loading spinner
245
		 */
246
		showLoading: function () {
247
			this.emptyContentElement.addClass('hidden');
248
			this.controlsElement.removeClass('hidden');
249
			$('#content').addClass('icon-loading');
250
			this.dimControls();
251
		},
252
253
		/**
254
		 * Removes the spinner in the main area and restore normal visibility of the controls bar
255
		 */
256
		removeLoading: function () {
257
			$('#content').removeClass('icon-loading');
258
			this.controlsElement.find('.mask').remove();
259
		},
260
261
		/**
262
		 * Shows thumbnails
263
		 */
264
		_showNormal: function () {
265
			this.emptyContentElement.addClass('hidden');
266
			this.controlsElement.removeClass('hidden');
267
			this.removeLoading();
268
		},
269
270
		/**
271
		 * Sets up our custom handlers for folder uploading operations
272
		 *
273
		 * @see OC.Upload.init/file_upload_param.done()
274
		 *
275
		 * @private
276
		 */
277
		_setupUploader: function () {
278
			var self = this;
279
			var $uploadEl = $('#file_upload_start');
280
			var uploads = [];
281
			if (!$uploadEl.exists()) {
282
				return;
283
			}
284
			this._uploader = new OC.Uploader($uploadEl, {
285
				fileList: FileList,
286
				dropZone: $('#content')
287
			});
288
			this._uploader.on('add', function (e, data) {
289
				data.targetDir = '/' + Gallery.currentAlbum;
290
			});
291
			this._uploader.on('done', function (e, upload) {
292
				var data = upload.data;
293
				// is that the last upload ?
294
				if (data.files[0] === data.originalFiles[data.originalFiles.length - 1]) {
295
					var fileList = data.originalFiles;
296
					//Ask for a refresh of the photowall
297
					Gallery.getFiles(Gallery.currentAlbum).done(function () {
298
						var fileId, path;
299
						// Removes the cached thumbnails of files which have been re-uploaded
300
						_(fileList).each(function (fileName) {
301
							path = Gallery.currentAlbum + '/' + fileName;
302
							if (Gallery.imageMap[path]) {
303
								fileId = Gallery.imageMap[path].fileId;
304
								if (Thumbnails.map[fileId]) {
305
									delete Thumbnails.map[fileId];
306
								}
307
							}
308
						});
309
310
						Gallery.view.init(Gallery.currentAlbum);
311
					});
312
				}
313
			});
314
315
			// Since 9.0
316
			if (OC.Uploader) {
317
				OC.Uploader.prototype._isReceivedSharedFile = function (file) {
318
					var path = file.name;
319
					var sharedWith = false;
320
321
					if (Gallery.currentAlbum !== '' && Gallery.currentAlbum !== '/') {
322
						path = Gallery.currentAlbum + '/' + path;
323
					}
324
					if (Gallery.imageMap[path] && Gallery.imageMap[path].sharedWithUser) {
325
						sharedWith = true;
326
					}
327
328
					return sharedWith;
329
				};
330
			}
331
		},
332
333
		/**
334
		 * Adds all the click handlers to buttons the first time they appear in the interface
335
		 *
336
		 * @private
337
		 */
338
		_initButtons: function () {
339
			this.element.on("contextmenu", function(e) { e.preventDefault(); });
340
			$('#filelist-button').click(Gallery.switchToFilesView);
341
			$('#download').click(Gallery.download);
342
			$('#share-button').click(Gallery.share);
343
			Gallery.infoBox = new Gallery.InfoBox();
344
			$('#album-info-button').click(Gallery.showInfo);
345
			$('#sort-name-button').click(Gallery.sorter);
346
			$('#sort-date-button').click(Gallery.sorter);
347
			$('#save #save-button').click(Gallery.showSaveForm);
348
			$('.save-form').submit(Gallery.saveForm);
349
			this._renderNewButton();
350
			// Trigger cancelling of file upload
351
			$('#uploadprogresswrapper .stop').on('click', function () {
352
				OC.Upload.cancelUploads();
353
			});
354
			this.requestId = Math.random();
355
		},
356
357
		/**
358
		 * Sets up all the buttons of the interface and the breadcrumbs
359
		 *
360
		 * @param {string} albumPath
361
		 * @private
362
		 */
363
		_setupButtons: function (albumPath) {
364
			this._shareButtonSetup(albumPath);
365
			this._infoButtonSetup();
366
367
			var availableWidth = $(window).width() - Gallery.buttonsWidth;
368
			this.breadcrumb.init(albumPath, availableWidth);
369
			var album = Gallery.albumMap[albumPath];
370
			
371
			var sum = album.images.length + album.subAlbums.length;
372
			//If sum of the number of images and subalbums exceeds 1 then show the buttons.
373
			if(sum > 1)
374
			{
375
				$('#sort-name-button').show();
376
				$('#sort-date-button').show();
377
			}
378
			else
379
			{
380
				$('#sort-name-button').hide();
381
				$('#sort-date-button').hide();
382
			}
383
			var currentSort = Gallery.config.albumSorting;
384
			this.sortControlsSetup(currentSort.type, currentSort.order);
385
			Gallery.albumMap[Gallery.currentAlbum].images.sort(
386
				Gallery.utility.sortBy(currentSort.type,
387
					currentSort.order));
388
			Gallery.albumMap[Gallery.currentAlbum].subAlbums.sort(Gallery.utility.sortBy('name',
389
				currentSort.albumOrder));
390
391
			$('#save-button').show();
392
			$('#download').show();
393
			$('a.button.new').show();
394
		},
395
396
		/**
397
		 * Hide buttons in the controls bar
398
		 *
399
		 * @param uploadAllowed
400
		 */
401
		_hideButtons: function (uploadAllowed) {
402
			$('#album-info-button').hide();
403
			$('#share-button').hide();
404
			$('#sort-name-button').hide();
405
			$('#sort-date-button').hide();
406
			$('#save-button').hide();
407
			$('#download').hide();
408
409
			if (!uploadAllowed) {
410
				$('a.button.new').hide();
411
			}
412
		},
413
414
		/**
415
		 * Shows or hides the share button depending on if we're in a public gallery or not
416
		 *
417
		 * @param {string} albumPath
418
		 * @private
419
		 */
420
		_shareButtonSetup: function (albumPath) {
421
			var shareButton = $('#share-button');
422
			if (albumPath === '' || Gallery.token) {
423
				shareButton.hide();
424
			} else {
425
				shareButton.show();
426
			}
427
		},
428
429
		/**
430
		 * Shows or hides the info button based on the information we've received from the server
431
		 *
432
		 * @private
433
		 */
434
		_infoButtonSetup: function () {
435
			var infoButton = $('#album-info-button');
436
			infoButton.find('span').hide();
437
			var infoContentContainer = $('.album-info-container');
438
			infoContentContainer.slideUp();
439
			infoContentContainer.css('max-height',
440
				$(window).height() - Gallery.browserToolbarHeight);
441
			var albumInfo = Gallery.config.albumInfo;
442
			if (Gallery.config.albumError) {
443
				infoButton.hide();
444
				var text = '<strong>' + t('gallery', 'Configuration error') + '</strong></br>' +
445
					Gallery.config.albumError.message + '</br></br>';
446
				Gallery.utility.showHtmlNotification(text, 7);
447
			} else if ($.isEmptyObject(albumInfo)) {
448
				infoButton.hide();
449
			} else {
450
				infoButton.show();
451
				if (albumInfo.inherit !== 'yes' || albumInfo.level === 0) {
452
					infoButton.find('span').delay(1000).slideDown();
453
				}
454
			}
455
		},
456
457
		/**
458
		 * Sets the background colour of the photowall
459
		 *
460
		 * @private
461
		 */
462
		_setBackgroundColour: function () {
463
			var wrapper = $('#content-wrapper');
464
			var albumDesign = Gallery.config.albumDesign;
465
			if (!$.isEmptyObject(albumDesign) && albumDesign.background) {
466
				wrapper.css('background-color', albumDesign.background);
467
			} else {
468
				wrapper.css('background-color', '#fff');
469
			}
470
		},
471
472
		/**
473
		 * Picks the image which matches the sort order
474
		 *
475
		 * @param {string} sortType name or date
476
		 * @param {string} sortOrder asc or des
477
		 * @param {boolean} active determines if we're setting up the active sort button
478
		 * @private
479
		 */
480
		_setSortButton: function (sortType, sortOrder, active) {
481
			var button = $('#sort-' + sortType + '-button');
482
			// Removing all the classes which control the image in the button
483
			button.removeClass('active-button');
484
			button.find('img').removeClass('front');
485
			button.find('img').removeClass('back');
486
487
			// We need to determine the reverse order in order to send that image to the back
488
			var reverseSortOrder = 'des';
489
			if (sortOrder === 'des') {
490
				reverseSortOrder = 'asc';
491
			}
492
493
			// We assign the proper order to the button images
494
			button.find('img.' + sortOrder).addClass('front');
495
			button.find('img.' + reverseSortOrder).addClass('back');
496
497
			// The active button needs a hover action for the flip effect
498
			if (active) {
499
				button.addClass('active-button');
500
				if (button.is(":hover")) {
501
					button.removeClass('hover');
502
				}
503
				// We can't use a toggle here
504
				button.hover(function () {
505
						$(this).addClass('hover');
506
					},
507
					function () {
508
						$(this).removeClass('hover');
509
					});
510
			}
511
		},
512
		
513
		/**
514
		 * If no url is entered then do not show the error box.
515
		 *
516
		 */
517
		_blankUrl: function() {
518
			$('#remote_address').on("change keyup paste", function() {
519
 				if ($(this).val() === '') {
520
 					$('#save-button-confirm').prop('disabled', true);
521
 				} else {
522
 					$('#save-button-confirm').prop('disabled', false);
523
 				}
524
			});
525
		},
526
		
527
		/**
528
		 * Creates the [+] button allowing users who can't drag and drop to upload files
529
		 *
530
		 * @see core/apps/files/js/filelist.js
531
		 * @private
532
		 */
533
		_renderNewButton: function () {
534
			// if no actions container exist, skip
535
			var $actionsContainer = $('.actions');
536
			if (!$actionsContainer.length) {
537
				return;
538
			}
539
			if (!this._addButtonTemplate) {
540
				this._addButtonTemplate = Handlebars.compile(TEMPLATE_ADDBUTTON);
541
			}
542
			var $newButton = $(this._addButtonTemplate({
543
				addText: t('gallery', 'New'),
544
				iconUrl: OC.imagePath('core', 'actions/add')
545
			}));
546
547
			$actionsContainer.prepend($newButton);
548
			$newButton.tooltip({'placement': 'bottom'});
549
550
			$newButton.click(_.bind(this._onClickNewButton, this));
551
			this._newButton = $newButton;
552
		},
553
554
		/**
555
		 * Creates the click handler for the [+] button
556
		 * @param event
557
		 * @returns {boolean}
558
		 *
559
		 * @see core/apps/files/js/filelist.js
560
		 * @private
561
		 */
562
		_onClickNewButton: function (event) {
563
			var $target = $(event.target);
564
			if (!$target.hasClass('.button')) {
565
				$target = $target.closest('.button');
566
			}
567
			this._newButton.tooltip('hide');
568
			event.preventDefault();
569
			if ($target.hasClass('disabled')) {
570
				return false;
571
			}
572
			if (!this._newFileMenu) {
573
				this._newFileMenu = new Gallery.NewFileMenu();
574
				$('body').append(this._newFileMenu.$el);
575
			}
576
			this._newFileMenu.showAt($target);
577
578
			if (Gallery.currentAlbum === '') {
579
				$('.menuitem[data-action="hideAlbum"]').parent().hide();
580
			}
581
			return false;
582
		}
583
	};
584
585
	Gallery.View = View;
586
})(jQuery, _, OC, t, Gallery);
587