Issues (61)

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;
0 ignored issues
show
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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>' + errorMessage + '</p>';
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
		 * We only want it to be called for that specific case as all other file uploading
274
		 * operations will call Files.highlightFiles
275
		 *
276
		 * @see OC.Upload.init/file_upload_param.done()
277
		 *
278
		 * @private
279
		 */
280
		_setupUploader: function () {
281
			$('#file_upload_start').on('fileuploaddone', function (e, data) {
282
				if (data.files[0] === data.originalFiles[data.originalFiles.length - 1]
283
					&& data.files[0].relativePath) {
284
285
					//Ask for a refresh of the photowall
286
					Gallery.getFiles(Gallery.currentAlbum).done(function () {
287
						Gallery.view.init(Gallery.currentAlbum);
288
					});
289
				}
290
			});
291
292
			// Since 9.0
293
			if (OC.Upload) {
294
				OC.Upload._isReceivedSharedFile = function (file) {
295
					var path = file.name;
296
					var sharedWith = false;
297
298
					if (Gallery.currentAlbum !== '' && Gallery.currentAlbum !== '/') {
299
						path = Gallery.currentAlbum + '/' + path;
300
					}
301
					if (Gallery.imageMap[path] && Gallery.imageMap[path].sharedWithUser) {
302
						sharedWith = true;
303
					}
304
305
					return sharedWith;
306
				};
307
			}
308
		},
309
310
		/**
311
		 * Adds all the click handlers to buttons the first time they appear in the interface
312
		 *
313
		 * @private
314
		 */
315
		_initButtons: function () {
316
			this.element.on("contextmenu", function(e) { e.preventDefault(); });
317
			$('#filelist-button').click(Gallery.switchToFilesView);
318
			$('#download').click(Gallery.download);
319
			$('#share-button').click(Gallery.share);
320
			Gallery.infoBox = new Gallery.InfoBox();
321
			$('#album-info-button').click(Gallery.showInfo);
322
			$('#sort-name-button').click(Gallery.sorter);
323
			$('#sort-date-button').click(Gallery.sorter);
324
			$('#save #save-button').click(Gallery.showSaveForm);
325
			$('.save-form').submit(Gallery.saveForm);
326
			this._renderNewButton();
327
			// Trigger cancelling of file upload
328
			$('#uploadprogresswrapper .stop').on('click', function () {
329
				OC.Upload.cancelUploads();
330
			});
331
			this.requestId = Math.random();
332
		},
333
334
		/**
335
		 * Sets up all the buttons of the interface and the breadcrumbs
336
		 *
337
		 * @param {string} albumPath
338
		 * @private
339
		 */
340
		_setupButtons: function (albumPath) {
341
			this._shareButtonSetup(albumPath);
342
			this._infoButtonSetup();
343
344
			var availableWidth = $(window).width() - Gallery.buttonsWidth;
345
			this.breadcrumb.init(albumPath, availableWidth);
346
			var album = Gallery.albumMap[albumPath];
347
			
348
			var sum = album.images.length + album.subAlbums.length;
349
			//If sum of the number of images and subalbums exceeds 1 then show the buttons.
350
			if(sum > 1)
351
			{
352
				$('#sort-name-button').show();
353
				$('#sort-date-button').show();
354
			}
355
			else
356
			{
357
				$('#sort-name-button').hide();
358
				$('#sort-date-button').hide();
359
			}
360
			var currentSort = Gallery.config.albumSorting;
361
			this.sortControlsSetup(currentSort.type, currentSort.order);
362
			Gallery.albumMap[Gallery.currentAlbum].images.sort(
363
				Gallery.utility.sortBy(currentSort.type,
364
					currentSort.order));
365
			Gallery.albumMap[Gallery.currentAlbum].subAlbums.sort(Gallery.utility.sortBy('name',
366
				currentSort.albumOrder));
367
368
			$('#save-button').show();
369
			$('#download').show();
370
			$('a.button.new').show();
371
		},
372
373
		/**
374
		 * Hide buttons in the controls bar
375
		 *
376
		 * @param uploadAllowed
377
		 */
378
		_hideButtons: function (uploadAllowed) {
379
			$('#album-info-button').hide();
380
			$('#share-button').hide();
381
			$('#sort-name-button').hide();
382
			$('#sort-date-button').hide();
383
			$('#save-button').hide();
384
			$('#download').hide();
385
386
			if (!uploadAllowed) {
387
				$('a.button.new').hide();
388
			}
389
		},
390
391
		/**
392
		 * Shows or hides the share button depending on if we're in a public gallery or not
393
		 *
394
		 * @param {string} albumPath
395
		 * @private
396
		 */
397
		_shareButtonSetup: function (albumPath) {
398
			var shareButton = $('#share-button');
399
			if (albumPath === '' || Gallery.token) {
400
				shareButton.hide();
401
			} else {
402
				shareButton.show();
403
			}
404
		},
405
406
		/**
407
		 * Shows or hides the info button based on the information we've received from the server
408
		 *
409
		 * @private
410
		 */
411
		_infoButtonSetup: function () {
412
			var infoButton = $('#album-info-button');
413
			infoButton.find('span').hide();
414
			var infoContentContainer = $('.album-info-container');
415
			infoContentContainer.slideUp();
416
			infoContentContainer.css('max-height',
417
				$(window).height() - Gallery.browserToolbarHeight);
418
			var albumInfo = Gallery.config.albumInfo;
419
			if (Gallery.config.albumError) {
420
				infoButton.hide();
421
				var text = '<strong>' + t('gallery', 'Configuration error') + '</strong></br>' +
422
					Gallery.config.albumError.message + '</br></br>';
423
				Gallery.utility.showHtmlNotification(text, 7);
424
			} else if ($.isEmptyObject(albumInfo)) {
425
				infoButton.hide();
426
			} else {
427
				infoButton.show();
428
				if (albumInfo.inherit !== 'yes' || albumInfo.level === 0) {
429
					infoButton.find('span').delay(1000).slideDown();
430
				}
431
			}
432
		},
433
434
		/**
435
		 * Sets the background colour of the photowall
436
		 *
437
		 * @private
438
		 */
439
		_setBackgroundColour: function () {
440
			var wrapper = $('#content-wrapper');
441
			var albumDesign = Gallery.config.albumDesign;
442
			if (!$.isEmptyObject(albumDesign) && albumDesign.background) {
443
				wrapper.css('background-color', albumDesign.background);
444
			} else {
445
				wrapper.css('background-color', '#fff');
446
			}
447
		},
448
449
		/**
450
		 * Picks the image which matches the sort order
451
		 *
452
		 * @param {string} sortType name or date
453
		 * @param {string} sortOrder asc or des
454
		 * @param {boolean} active determines if we're setting up the active sort button
455
		 * @private
456
		 */
457
		_setSortButton: function (sortType, sortOrder, active) {
458
			var button = $('#sort-' + sortType + '-button');
459
			// Removing all the classes which control the image in the button
460
			button.removeClass('active-button');
461
			button.find('img').removeClass('front');
462
			button.find('img').removeClass('back');
463
464
			// We need to determine the reverse order in order to send that image to the back
465
			var reverseSortOrder = 'des';
466
			if (sortOrder === 'des') {
467
				reverseSortOrder = 'asc';
468
			}
469
470
			// We assign the proper order to the button images
471
			button.find('img.' + sortOrder).addClass('front');
472
			button.find('img.' + reverseSortOrder).addClass('back');
473
474
			// The active button needs a hover action for the flip effect
475
			if (active) {
476
				button.addClass('active-button');
477
				if (button.is(":hover")) {
478
					button.removeClass('hover');
479
				}
480
				// We can't use a toggle here
481
				button.hover(function () {
482
						$(this).addClass('hover');
483
					},
484
					function () {
485
						$(this).removeClass('hover');
486
					});
487
			}
488
		},
489
		
490
		/**
491
		 * If no url is entered then do not show the error box.
492
		 *
493
		 */
494
		_blankUrl: function() {
495
			$('#remote_address').on("change keyup paste", function() {
496
 				if ($(this).val() === '') {
497
 					$('#save-button-confirm').prop('disabled', true);
498
 				} else {
499
 					$('#save-button-confirm').prop('disabled', false);
500
 				}
501
			});
502
		},
503
		
504
		/**
505
		 * Creates the [+] button allowing users who can't drag and drop to upload files
506
		 *
507
		 * @see core/apps/files/js/filelist.js
508
		 * @private
509
		 */
510
		_renderNewButton: function () {
511
			// if no actions container exist, skip
512
			var $actionsContainer = $('.actions');
513
			if (!$actionsContainer.length) {
514
				return;
515
			}
516
			if (!this._addButtonTemplate) {
517
				this._addButtonTemplate = Handlebars.compile(TEMPLATE_ADDBUTTON);
518
			}
519
			var $newButton = $(this._addButtonTemplate({
520
				addText: t('gallery', 'New'),
521
				iconUrl: OC.imagePath('core', 'actions/add')
522
			}));
523
524
			$actionsContainer.prepend($newButton);
525
			$newButton.tooltip({'placement': 'bottom'});
526
527
			$newButton.click(_.bind(this._onClickNewButton, this));
528
			this._newButton = $newButton;
529
		},
530
531
		/**
532
		 * Creates the click handler for the [+] button
533
		 * @param event
534
		 * @returns {boolean}
535
		 *
536
		 * @see core/apps/files/js/filelist.js
537
		 * @private
538
		 */
539
		_onClickNewButton: function (event) {
540
			var $target = $(event.target);
541
			if (!$target.hasClass('.button')) {
542
				$target = $target.closest('.button');
543
			}
544
			this._newButton.tooltip('hide');
545
			event.preventDefault();
546
			if ($target.hasClass('disabled')) {
547
				return false;
548
			}
549
			if (!this._newFileMenu) {
550
				this._newFileMenu = new Gallery.NewFileMenu();
551
				$('body').append(this._newFileMenu.$el);
552
			}
553
			this._newFileMenu.showAt($target);
554
555
			if (Gallery.currentAlbum === '') {
556
				$('.menuitem[data-action="hideAlbum"]').parent().hide();
557
			}
558
			return false;
559
		}
560
	};
561
562
	Gallery.View = View;
563
})(jQuery, _, OC, t, Gallery);
564