Completed
Push — develop ( 6da527...3e55aa )
by Greg
09:27
created

addmedia.php (7 issues)

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
<?php
2
/**
3
 * webtrees: online genealogy
4
 * Copyright (C) 2017 webtrees development team
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
 * GNU General Public License for more details.
13
 * You should have received a copy of the GNU General Public License
14
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15
 */
16
namespace Fisharebest\Webtrees;
17
18
use Fisharebest\Webtrees\Controller\PageController;
19
use Fisharebest\Webtrees\Functions\Functions;
20
use Fisharebest\Webtrees\Functions\FunctionsDb;
21
use Fisharebest\Webtrees\Functions\FunctionsEdit;
22
use Fisharebest\Webtrees\Functions\FunctionsImport;
23
use Fisharebest\Webtrees\Functions\FunctionsPrint;
24
use Fisharebest\Webtrees\Query\QueryMedia;
25
26
/** @global Tree $WT_TREE */
27
global $WT_TREE;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
28
29
require 'includes/session.php';
30
31
$NO_UPDATE_CHAN  = $WT_TREE->getPreference('NO_UPDATE_CHAN');
32
$MEDIA_DIRECTORY = $WT_TREE->getPreference('MEDIA_DIRECTORY');
33
34
$pid         = Filter::get('pid', WT_REGEX_XREF, Filter::post('pid', WT_REGEX_XREF)); // edit this media object
35
$linktoid    = Filter::get('linktoid', WT_REGEX_XREF, Filter::post('linktoid', WT_REGEX_XREF)); // create a new media object, linked to this record
36
$action      = Filter::get('action', null, Filter::post('action'));
37
$filename    = Filter::get('filename', null, Filter::post('filename'));
38
$text        = Filter::postArray('text');
39
$tag         = Filter::postArray('tag', WT_REGEX_TAG);
40
$islink      = Filter::postArray('islink');
41
$glevels     = Filter::postArray('glevels', '[0-9]');
42
$folder      = Filter::post('folder');
43
$update_CHAN = !Filter::postBool('preserve_last_changed');
44
45
$controller = new PageController;
46
$controller
47
	->restrictAccess(Auth::isMember($WT_TREE));
48
49
$disp  = true;
50
$media = Media::getInstance($pid, $WT_TREE);
51
if ($media) {
52
	$disp = $media->canShow();
53
}
54
if ($action == 'update' || $action == 'create') {
55
	if ($linktoid) {
56
		$disp = GedcomRecord::getInstance($linktoid, $WT_TREE)->canShow();
57
	}
58
}
59
60
if (!Auth::isEditor($WT_TREE) || !$disp) {
61
	$controller
62
		->pageHeader()
63
		->addInlineJavascript('closePopupAndReloadParent();');
64
65
	return;
66
}
67
68
// There is a lot of common code in the create and update cases…
69
// …and also in the admin_media_upload.php script
70
71
switch ($action) {
72
case 'create': // Save the information from the “showcreateform” action
73
	$controller->setPageTitle(I18N::translate('Create a media object'));
74
75
	// Validate the media folder
76
	$folderName = str_replace('\\', '/', $folder);
77
	$folderName = trim($folderName, '/');
78
	if ($folderName == '.') {
79
		$folderName = '';
80
	}
81 View Code Duplication
	if ($folderName) {
82
		$folderName .= '/';
83
		// Not allowed to use “../”
84
		if (strpos('/' . $folderName, '/../') !== false) {
85
			FlashMessages::addMessage('Folder names are not allowed to include “../”');
86
			break;
87
		}
88
	}
89
90
	// Make sure the media folder exists
91 View Code Duplication
	if (!is_dir(WT_DATA_DIR . $MEDIA_DIRECTORY)) {
92
		if (File::mkdir(WT_DATA_DIR . $MEDIA_DIRECTORY)) {
93
			FlashMessages::addMessage(I18N::translate('The folder %s has been created.', Html::filename(WT_DATA_DIR . $MEDIA_DIRECTORY)));
94
		} else {
95
			FlashMessages::addMessage(I18N::translate('The folder %s does not exist, and it could not be created.', Html::filename(WT_DATA_DIR . $MEDIA_DIRECTORY)), 'danger');
96
			break;
97
		}
98
	}
99
100
	// Managers can create new media paths (subfolders). Users must use existing folders.
101 View Code Duplication
	if ($folderName && !is_dir(WT_DATA_DIR . $MEDIA_DIRECTORY . $folderName)) {
102
		if (Auth::isManager($WT_TREE)) {
103
			if (File::mkdir(WT_DATA_DIR . $MEDIA_DIRECTORY . $folderName)) {
104
				FlashMessages::addMessage(I18N::translate('The folder %s has been created.', Html::filename(WT_DATA_DIR . $MEDIA_DIRECTORY . $folderName)));
105
			} else {
106
				FlashMessages::addMessage(I18N::translate('The folder %s does not exist, and it could not be created.', Html::filename(WT_DATA_DIR . $MEDIA_DIRECTORY . $folderName)), 'danger');
107
				break;
108
			}
109
		} else {
110
			// Regular users should not have seen this option - so no need for an error message.
111
			break;
112
		}
113
	}
114
115
	// The media folder exists. Now create a thumbnail folder to match it.
116 View Code Duplication
	if (!is_dir(WT_DATA_DIR . $MEDIA_DIRECTORY . 'thumbs/' . $folderName)) {
117
		if (!File::mkdir(WT_DATA_DIR . $MEDIA_DIRECTORY . 'thumbs/' . $folderName)) {
118
			FlashMessages::addMessage(I18N::translate('The folder %s does not exist, and it could not be created.', Html::filename(WT_DATA_DIR . $MEDIA_DIRECTORY . 'thumbs/' . $folderName)), 'danger');
119
			break;
120
		}
121
	}
122
123
	// A thumbnail file with no main image?
124
	if (!empty($_FILES['thumbnail']['name']) && empty($_FILES['mediafile']['name'])) {
125
		// Assume the user used the wrong field, and treat this as a main image
126
		$_FILES['mediafile'] = $_FILES['thumbnail'];
127
		unset($_FILES['thumbnail']);
128
	}
129
130
	// Thumbnail files must contain images.
131 View Code Duplication
	if (!empty($_FILES['thumbnail']['name']) && !preg_match('/^image/', $_FILES['thumbnail']['type'])) {
132
		FlashMessages::addMessage(I18N::translate('Thumbnail files must contain images.'));
133
		break;
134
	}
135
136
	// User-specified filename?
137
	if ($tag[0] == 'FILE' && $text[0]) {
138
		$filename = $text[0];
139
	}
140
	// Use the name of the uploaded file?
141
	// If no filename specified, use the name of the uploaded file?
142 View Code Duplication
	if (!$filename && !empty($_FILES['mediafile']['name'])) {
143
		$filename = $_FILES['mediafile']['name'];
144
	}
145
146
	// Validate the media path and filename
147 View Code Duplication
	if (preg_match('/^https?:\/\//i', $text[0], $match)) {
148
		// External media needs no further validation
149
		$fileName   = $filename;
150
		$folderName = '';
151
		unset($_FILES['mediafile'], $_FILES['thumbnail']);
152
	} elseif (preg_match('/([\/\\\\<>])/', $filename, $match)) {
153
		// Local media files cannot contain certain special characters
154
		FlashMessages::addMessage(I18N::translate('Filenames are not allowed to contain the character “%s”.', $match[1]));
155
		break;
156
	} elseif (preg_match('/(\.(php|pl|cgi|bash|sh|bat|exe|com|htm|html|shtml))$/i', $filename, $match)) {
157
		// Do not allow obvious script files.
158
		FlashMessages::addMessage(I18N::translate('Filenames are not allowed to have the extension “%s”.', $match[1]));
159
		break;
160
	} elseif (!$filename) {
161
		FlashMessages::addMessage(I18N::translate('No media file was provided.'));
162
		break;
163
	} else {
164
		$fileName = $filename;
165
	}
166
167
	// Now copy the file to the correct location.
168
	if (!empty($_FILES['mediafile']['name'])) {
169
		$serverFileName = WT_DATA_DIR . $MEDIA_DIRECTORY . $folderName . $fileName;
170
		if (file_exists($serverFileName)) {
171
			FlashMessages::addMessage(I18N::translate('The file %s already exists. Use another filename.', $folderName . $fileName));
172
			break;
173
		}
174
		if (move_uploaded_file($_FILES['mediafile']['tmp_name'], $serverFileName)) {
0 ignored issues
show
Security File Manipulation introduced by
$serverFileName can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_FILES, and $filename is assigned
    in addmedia.php on line 143
  2. $fileName is assigned
    in addmedia.php on line 149
  3. $serverFileName is assigned
    in addmedia.php on line 169

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
175
			Log::addMediaLog('Media file ' . $serverFileName . ' uploaded');
176 View Code Duplication
		} else {
177
			FlashMessages::addMessage(
178
				I18N::translate('There was an error uploading your file.') .
179
				'<br>' .
180
				Functions::fileUploadErrorText($_FILES['mediafile']['error'])
181
			);
182
			break;
183
		}
184
185
		// Now copy the (optional) thumbnail
186
		if (!empty($_FILES['thumbnail']['name']) && preg_match('/^image\/(png|gif|jpeg)/', $_FILES['thumbnail']['type'], $match)) {
187
			// Thumbnails have either
188
			// (a) the same filename as the main image
189
			// (b) the same filename as the main image - but with a .png extension
190
			if ($match[1] == 'png' && !preg_match('/\.(png)$/i', $fileName)) {
191
				$thumbFile = preg_replace('/\.[a-z0-9]{3,5}$/', '.png', $fileName);
192
			} else {
193
				$thumbFile = $fileName;
194
			}
195
			$serverFileName = WT_DATA_DIR . $MEDIA_DIRECTORY . 'thumbs/' . $folderName . $thumbFile;
196
			if (move_uploaded_file($_FILES['thumbnail']['tmp_name'], $serverFileName)) {
0 ignored issues
show
Security File Manipulation introduced by
$serverFileName can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_FILES, and $filename is assigned
    in addmedia.php on line 143
  2. $fileName is assigned
    in addmedia.php on line 149
  3. $thumbFile is assigned
    in addmedia.php on line 193
  4. $serverFileName is assigned
    in addmedia.php on line 195

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
197
				Log::addMediaLog('Thumbnail file ' . $serverFileName . ' uploaded');
198
			}
199
		}
200
	}
201
202
	$controller->pageHeader();
203
	// Build the gedcom record
204
	$newged = '0 @new@ OBJE';
205
	if ($tag[0] == 'FILE') {
206
		// The admin has an edit field to change the filename
207
		$text[0] = $folderName . $fileName;
208
	} else {
209
		// Users keep the original filename
210
		$newged .= "\n1 FILE " . $folderName . $fileName;
211
	}
212
213
	$newged = FunctionsEdit::handleUpdates($newged);
214
215
	$new_media = $WT_TREE->createRecord($newged);
216
	if ($linktoid) {
217
		$record = GedcomRecord::getInstance($linktoid, $WT_TREE);
218
		$record->createFact('1 OBJE @' . $new_media->getXref() . '@', true);
219
		Log::addEditLog('Media ID ' . $new_media->getXref() . ' successfully added to ' . $linktoid);
220
		$controller->addInlineJavascript('closePopupAndReloadParent();');
221
	} else {
222
		Log::addEditLog('Media ID ' . $new_media->getXref() . ' successfully added.');
223
	}
224
	echo '<button onclick="closePopupAndReloadParent();">', I18N::translate('close'), '</button>';
225
226
	return;
227
228
case 'update': // Save the information from the “editmedia” action
229
	$controller->setPageTitle(I18N::translate('Edit the media object'));
230
231
	// Validate the media folder
232
	$folderName = str_replace('\\', '/', $folder);
233
	$folderName = trim($folderName, '/');
234
	if ($folderName == '.') {
235
		$folderName = '';
236
	}
237 View Code Duplication
	if ($folderName) {
238
		$folderName .= '/';
239
		// Not allowed to use “../”
240
		if (strpos('/' . $folderName, '/../') !== false) {
241
			FlashMessages::addMessage('Folder names are not allowed to include “../”');
242
			break;
243
		}
244
	}
245
246
	// Make sure the media folder exists
247 View Code Duplication
	if (!is_dir(WT_DATA_DIR . $MEDIA_DIRECTORY)) {
248
		if (File::mkdir(WT_DATA_DIR . $MEDIA_DIRECTORY)) {
249
			FlashMessages::addMessage(I18N::translate('The folder %s has been created.', Html::filename(WT_DATA_DIR . $MEDIA_DIRECTORY)));
250
		} else {
251
			FlashMessages::addMessage(I18N::translate('The folder %s does not exist, and it could not be created.', Html::filename(WT_DATA_DIR . $MEDIA_DIRECTORY)), 'danger');
252
			break;
253
		}
254
	}
255
256
	// Managers can create new media paths (subfolders). Users must use existing folders.
257 View Code Duplication
	if ($folderName && !is_dir(WT_DATA_DIR . $MEDIA_DIRECTORY . $folderName)) {
258
		if (Auth::isManager($WT_TREE)) {
259
			if (File::mkdir(WT_DATA_DIR . $MEDIA_DIRECTORY . $folderName)) {
260
				FlashMessages::addMessage(I18N::translate('The folder %s has been created.', Html::filename(WT_DATA_DIR . $MEDIA_DIRECTORY . $folderName)));
261
			} else {
262
				FlashMessages::addMessage(I18N::translate('The folder %s does not exist, and it could not be created.', Html::filename(WT_DATA_DIR . $MEDIA_DIRECTORY . $folderName)), 'danger');
263
				break;
264
			}
265
		} else {
266
			// Regular users should not have seen this option - so no need for an error message.
267
			break;
268
		}
269
	}
270
271
	// The media folder exists. Now create a thumbnail folder to match it.
272 View Code Duplication
	if (!is_dir(WT_DATA_DIR . $MEDIA_DIRECTORY . 'thumbs/' . $folderName)) {
273
		if (!File::mkdir(WT_DATA_DIR . $MEDIA_DIRECTORY . 'thumbs/' . $folderName)) {
274
			FlashMessages::addMessage(I18N::translate('The folder %s does not exist, and it could not be created.', Html::filename(WT_DATA_DIR . $MEDIA_DIRECTORY . 'thumbs/' . $folderName)), 'danger');
275
			break;
276
		}
277
	}
278
279
	// Validate the media path and filename
280 View Code Duplication
	if (preg_match('/^https?:\/\//i', $filename, $match)) {
281
		// External media needs no further validation
282
		$fileName   = $filename;
283
		$folderName = '';
284
		unset($_FILES['mediafile'], $_FILES['thumbnail']);
285
	} elseif (preg_match('/([\/\\\\<>])/', $filename, $match)) {
286
		// Local media files cannot contain certain special characters
287
		FlashMessages::addMessage(I18N::translate('Filenames are not allowed to contain the character “%s”.', $match[1]));
288
		break;
289
	} elseif (preg_match('/(\.(php|pl|cgi|bash|sh|bat|exe|com|htm|html|shtml))$/i', $filename, $match)) {
290
		// Do not allow obvious script files.
291
		FlashMessages::addMessage(I18N::translate('Filenames are not allowed to have the extension “%s”.', $match[1]));
292
		break;
293
	} elseif (!$filename) {
294
		FlashMessages::addMessage(I18N::translate('No media file was provided.'));
295
		break;
296
	} else {
297
		$fileName = $filename;
298
	}
299
300
	$oldFilename = $media->getFilename();
301
	$newFilename = $folderName . $fileName;
302
303
	// Cannot rename local to external or vice-versa
304
	if (Functions::isFileExternal($oldFilename) != Functions::isFileExternal($filename)) {
305
		FlashMessages::addMessage(I18N::translate('The media file %1$s could not be renamed to %2$s.', Html::filename($oldFilename), Html::filename($newFilename)));
306
		break;
307
	}
308
309
	$messages  = false;
310
	$move_file = false;
311
312
	// Move files on disk (if we can) to reflect the change to the GEDCOM data
313
	if (!$media->isExternal()) {
314
		$oldServerFile  = $media->getServerFilename('main');
315
		$oldServerThumb = $media->getServerFilename('thumb');
316
317
		$newmedia       = new Media('xxx', "0 @xxx@ OBJE\n1 FILE " . $newFilename, null, $WT_TREE);
318
		$newServerFile  = $newmedia->getServerFilename('main');
319
		$newServerThumb = $newmedia->getServerFilename('thumb');
320
321
		// We could be either renaming an existing file, or updating a record (with no valid file) to point to a new file
322
		if ($oldServerFile !== $newServerFile) {
323
			//-- check if the file is used in more than one gedcom
324
			//-- do not allow it to be moved or renamed if it is
325
			if (!$media->isExternal() && FunctionsDb::isMediaUsedInOtherTree($media->getFilename(), $WT_TREE->getTreeId())) {
326
				FlashMessages::addMessage(I18N::translate('This file is linked to another family tree on this server. It cannot be deleted, moved, or renamed until these links have been removed.'));
327
				break;
328
			}
329
330
			$move_file = true;
331 View Code Duplication
			if (!file_exists($newServerFile) || md5_file($oldServerFile) === md5_file($newServerFile)) {
332
				try {
333
					rename($oldServerFile, $newServerFile);
334
					FlashMessages::addMessage(I18N::translate('The media file %1$s has been renamed to %2$s.', Html::filename($oldFilename), Html::filename($newFilename)));
335
				} catch (\ErrorException $ex) {
336
					FlashMessages::addMessage(I18N::translate('The media file %1$s could not be renamed to %2$s.', Html::filename($oldFilename), Html::filename($newFilename)));
337
				}
338
				$messages = true;
339
			}
340 View Code Duplication
			if (!file_exists($newServerFile)) {
341
				FlashMessages::addMessage(I18N::translate('The media file %s does not exist.', Html::filename($newFilename)));
342
				$messages = true;
343
			}
344
		}
345
		if ($oldServerThumb != $newServerThumb) {
346
			$move_file = true;
347 View Code Duplication
			if (!file_exists($newServerThumb) || md5_file($oldServerFile) == md5_file($newServerThumb)) {
348
				try {
349
					rename($oldServerThumb, $newServerThumb);
350
					FlashMessages::addMessage(I18N::translate('The thumbnail file %1$s has been renamed to %2$s.', Html::filename($oldFilename), Html::filename($newFilename)));
351
				} catch (\ErrorException $ex) {
352
					FlashMessages::addMessage(I18N::translate('The thumbnail file %1$s could not be renamed to %2$s.', Html::filename($oldFilename), Html::filename($newFilename)));
353
				}
354
				$messages = true;
355
			}
356 View Code Duplication
			if (!file_exists($newServerThumb)) {
357
				FlashMessages::addMessage(I18N::translate('The thumbnail file %s does not exist.', Html::filename($newFilename)));
358
				$messages = true;
359
			}
360
		}
361
	}
362
363
	// Insert the 1 FILE xxx record into the arrays used by function FunctionsEdit::handle_updatesges()
364
	$glevels = array_merge(['1'], $glevels);
365
	$tag     = array_merge(['FILE'], $tag);
366
	$islink  = array_merge([0], $islink);
367
	$text    = array_merge([$newFilename], $text);
368
369
	$record = GedcomRecord::getInstance($pid, $WT_TREE);
370
	$newrec = '0 @' . $pid . "@ OBJE\n";
371
	$newrec = FunctionsEdit::handleUpdates($newrec);
372
	$record->updateRecord($newrec, $update_CHAN);
373
374
	if ($move_file) {
375
		// We've moved a file. Therefore we must approve the change, as rejecting
376
		// the change will create broken references.
377
		FunctionsImport::acceptAllChanges($record->getXref(), $record->getTree()->getTreeId());
378
	}
379
380
	if ($pid && $linktoid) {
381
		$record = GedcomRecord::getInstance($linktoid, $WT_TREE);
382
		$record->createFact('1 OBJE @' . $pid . '@', true);
383
		Log::addEditLog('Media ID ' . $pid . ' successfully added to ' . $linktoid);
384
	}
385
	$controller->pageHeader();
386
	if ($messages) {
387
		echo '<button onclick="closePopupAndReloadParent();">', I18N::translate('close'), '</button>';
388
	} else {
389
		$controller->addInlineJavascript('closePopupAndReloadParent();');
390
	}
391
392
	return;
393
case 'showmediaform':
394
	$controller->setPageTitle(I18N::translate('Create a media object'));
395
	$action = 'create';
396
	break;
397
case 'editmedia':
398
	$controller->setPageTitle(I18N::translate('Edit the media object'));
399
	$action = 'update';
400
	break;
401
default:
402
	throw new \Exception('Bad $action (' . $action . ') in addmedia.php');
403
}
404
405
$controller->pageHeader();
406
407
echo '<div id="addmedia-page">'; //container for media edit pop-up
408
echo '<form method="post" name="newmedia" action="addmedia.php" enctype="multipart/form-data">';
409
echo '<input type="hidden" name="action" value="', $action, '">';
410
echo '<input type="hidden" name="ged" value="', $WT_TREE->getNameHtml(), '">';
411
echo '<input type="hidden" name="pid" value="', $pid, '">';
412
if ($linktoid) {
413
	echo '<input type="hidden" name="linktoid" value="', $linktoid, '">';
414
}
415
echo '<table class="facts_table">';
416
echo '<tr><td class="topbottombar" colspan="2">';
417
echo $controller->getPageTitle(), FunctionsPrint::helpLink('OBJE');
418
echo '</td></tr>';
419 View Code Duplication
if (!$linktoid && $action == 'create') {
420
	echo '<tr><td class="descriptionbox wrap width25">';
421
	echo I18N::translate('Enter an individual, family, or source ID');
422
	echo '</td><td class="optionbox wrap"><input type="text" data-autocomplete-type="IFS" name="linktoid" id="linktoid" size="6" value="">';
423
	echo '<p class="small text-muted">', I18N::translate('Enter or search for the ID of the individual, family, or source to which this media object should be linked.'), '</p></td></tr>';
424
}
425
426
if ($media) {
427
	$gedrec = $media->getGedcom();
428
} else {
429
	$gedrec = '';
430
}
431
432
// 1 FILE
433 View Code Duplication
if (preg_match('/\n\d (FILE.*)/', $gedrec, $match)) {
434
	$gedfile = $match[1];
435
} elseif ($filename) {
436
	$gedfile = 'FILE ' . $filename;
437
} else {
438
	$gedfile = 'FILE';
439
}
440
441
if ($gedfile == 'FILE') {
442
	// Box for user to choose to upload file from local computer
443
	echo '<tr><td class="descriptionbox wrap width25">';
444
	echo I18N::translate('Media file to upload') . '</td><td class="optionbox wrap"><input type="file" name="mediafile" onchange="updateFormat(this.value);" size="40"></td></tr>';
445
	// Check for thumbnail generation support
446
	if (Auth::isManager($WT_TREE)) {
447
		echo '<tr><td class="descriptionbox wrap width25">';
448
		echo I18N::translate('Thumbnail to upload') . '</td><td class="optionbox wrap"><input type="file" name="thumbnail" size="40">';
449
		echo '<p class="small text-muted">', I18N::translate('Choose the thumbnail image that you want to upload. Although thumbnails can be generated automatically for images, you may wish to generate your own thumbnail, especially for other media types. For example, you can provide a still image from a video, or a photograph of the individual who made an audio recording.'), '</p>';
450
		echo '</td></tr>';
451
	}
452
}
453
454
// Filename on server
455
$isExternal = Functions::isFileExternal($gedfile);
456
if ($gedfile === 'FILE') {
457
	if (Auth::isManager($WT_TREE)) {
458
		FunctionsEdit::addSimpleTag(
459
			'1 FILE',
460
			'',
461
			I18N::translate('Filename on server'),
462
			'<p class="small text-muted">' . I18N::translate('Do not change to keep original filename.') . '<br>' . I18N::translate('You may enter a URL, beginning with “http://”.') . '</p>'
463
		);
464
	}
465
	$folder = '';
466
} else {
467
	if ($isExternal) {
468
		$fileName = substr($gedfile, 5);
469
		$folder   = '';
470
	} else {
471
		$tmp      = substr($gedfile, 5);
472
		$fileName = basename($tmp);
473
		$folder   = dirname($tmp);
474
		if ($folder === '.') {
475
			$folder = '';
476
		}
477
	}
478
479
	echo '<tr>';
480
	echo '<td class="descriptionbox wrap width25">';
481
	echo I18N::translate('Filename on server');
482
	echo '</td>';
483
	echo '<td class="optionbox wrap wrap">';
484
	if (Auth::isManager($WT_TREE)) {
485
		echo '<input name="filename" type="text" value="' . Filter::escapeHtml($fileName) . '" size="40"';
486
		if ($isExternal) {
487
			echo '>';
488
		} else {
489
			echo '><p class="small text-muted">' . I18N::translate('Do not change to keep original filename.') . '</p>';
490
		}
491
	} else {
492
		echo $fileName;
0 ignored issues
show
Security Cross-Site Scripting introduced by
$fileName can contain request data and is used in output context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_FILES, and $filename is assigned
    in addmedia.php on line 143
  2. $gedfile is assigned
    in addmedia.php on line 436
  3. $gedfile is passed through substr(), and $fileName is assigned
    in addmedia.php on line 468

Preventing Cross-Site-Scripting Attacks

Cross-Site-Scripting allows an attacker to inject malicious code into your website - in particular Javascript code, and have that code executed with the privileges of a visiting user. This can be used to obtain data, or perform actions on behalf of that visiting user.

In order to prevent this, make sure to escape all user-provided data:

// for HTML
$sanitized = htmlentities($tainted, ENT_QUOTES);

// for URLs
$sanitized = urlencode($tainted);

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
493
		echo '<input name="filename" type="hidden" value="' . Filter::escapeHtml($fileName) . '" size="40">';
494
	}
495
	echo '</td>';
496
	echo '</tr>';
497
}
498
499
// Box for user to choose the folder to store the image
500
if (!$isExternal) {
501
	echo '<tr><td class="descriptionbox wrap width25">';
502
	echo I18N::translate('Folder name on server'), '</td><td class="optionbox wrap">';
503
	//-- don’t let regular users change the location of media items
504
	if ($action !== 'update' || Auth::isManager($WT_TREE)) {
505
		$mediaFolders = QueryMedia::folderList();
506
		echo '<select name="folder_list" onchange="document.newmedia.folder.value=this.options[this.selectedIndex].value;">';
507
		echo '<option ';
508
		if ($folder == '') {
509
			echo 'selected';
510
		}
511
		echo ' value=""> ', I18N::translate('Choose: '), ' </option>';
512
		if (Auth::isAdmin()) {
513
			echo '<option value="other" disabled>', I18N::translate('Other folder… please type in'), '</option>';
514
		}
515
		foreach ($mediaFolders as $f) {
516
			echo '<option value="', $f, '" ';
517
			if ($folder == $f) {
518
				echo 'selected';
519
			}
520
			echo '>', $f, '</option>';
521
		}
522
		echo '</select>';
523
	} else {
524
		echo $folder;
0 ignored issues
show
Security Cross-Site Scripting introduced by
$folder can contain request data and is used in output context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_FILES, and $filename is assigned
    in addmedia.php on line 143
  2. $gedfile is assigned
    in addmedia.php on line 436
  3. $gedfile is passed through substr(), and $tmp is assigned
    in addmedia.php on line 471
  4. $tmp is passed through dirname(), and $folder is assigned
    in addmedia.php on line 473

Preventing Cross-Site-Scripting Attacks

Cross-Site-Scripting allows an attacker to inject malicious code into your website - in particular Javascript code, and have that code executed with the privileges of a visiting user. This can be used to obtain data, or perform actions on behalf of that visiting user.

In order to prevent this, make sure to escape all user-provided data:

// for HTML
$sanitized = htmlentities($tainted, ENT_QUOTES);

// for URLs
$sanitized = urlencode($tainted);

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
525
	}
526
	if (Auth::isAdmin()) {
527
		echo '<br><input type="text" name="folder" size="40" value="', $folder, '">';
0 ignored issues
show
Security Cross-Site Scripting introduced by
$folder can contain request data and is used in output context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_FILES, and $filename is assigned
    in addmedia.php on line 143
  2. $gedfile is assigned
    in addmedia.php on line 436
  3. $gedfile is passed through substr(), and $tmp is assigned
    in addmedia.php on line 471
  4. $tmp is passed through dirname(), and $folder is assigned
    in addmedia.php on line 473

Preventing Cross-Site-Scripting Attacks

Cross-Site-Scripting allows an attacker to inject malicious code into your website - in particular Javascript code, and have that code executed with the privileges of a visiting user. This can be used to obtain data, or perform actions on behalf of that visiting user.

In order to prevent this, make sure to escape all user-provided data:

// for HTML
$sanitized = htmlentities($tainted, ENT_QUOTES);

// for URLs
$sanitized = urlencode($tainted);

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
528
		if ($gedfile === 'FILE') {
529
			echo '<p class="small text-muted">', I18N::translate('This entry is ignored if you have entered a URL into the filename field.'), '</p>';
530
		}
531
	} else {
532
		echo '<input name="folder" type="hidden" value="', Filter::escapeHtml($folder), '">';
533
	}
534
	echo '<p class="small text-muted">', I18N::translate('If you have a large number of media files, you can organize them into folders and subfolders.'), '</p>'; echo '</td></tr>';
535
} else {
536
	echo '<input name="folder" type="hidden" value="">';
537
}
538
539
// 1 FILE / 2 FORM
540
if (preg_match('/\n(2 FORM .*)/', $gedrec, $match)) {
541
	$gedform = $match[1];
542
} else {
543
	$gedform = '2 FORM';
544
}
545
$formid = FunctionsEdit::addSimpleTag($gedform);
546
547
// automatically set the format field from the filename
548
$controller->addInlineJavascript('
549
	function updateFormat(filename) {
550
		var extsearch=/\.([a-zA-Z]{3,4})$/;
551
		if (extsearch.exec(filename)) {
552
			ext = RegExp.$1.toLowerCase();
553
			if (ext=="jpg") ext="jpeg";
554
			if (ext=="tif") ext="tiff";
555
		} else {
556
			ext = "";
557
		}
558
		formfield = document.getElementById("' . $formid . '");
559
		formfield.value = ext;
560
	}
561
');
562
563
// 1 FILE / 2 FORM / 3 TYPE
564
if (preg_match('/\n(3 TYPE .*)/', $gedrec, $match)) {
565
	$gedtype = $match[1];
566
} else {
567
	$gedtype = '3 TYPE photo'; // default to ‘Photo’
568
}
569
FunctionsEdit::addSimpleTag($gedtype);
570
571
// 1 FILE / 2 TITL
572
if (preg_match('/\n(2 TITL .*)/', $gedrec, $match)) {
573
	$gedtitl = $match[1];
574
} else {
575
	$gedtitl = '2 TITL';
576
}
577
FunctionsEdit::addSimpleTag($gedtitl);
578
579
// 1 FILE / 2 TITL / 3 _HEB
580 View Code Duplication
if (strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), '_HEB') !== false) {
581
	if (preg_match('/\n(3 _HEB .*)/', $gedrec, $match)) {
582
		$gedtitl = $match[1];
583
	} else {
584
		$gedtitl = '3 _HEB';
585
	}
586
	FunctionsEdit::addSimpleTag($gedtitl);
587
}
588
589
// 1 FILE / 2 TITL / 3 ROMN
590 View Code Duplication
if (strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), 'ROMN') !== false) {
591
	if (preg_match('/\n(3 ROMN .*)/', $gedrec, $match)) {
592
		$gedtitl = $match[1];
593
	} else {
594
		$gedtitl = '3 ROMN';
595
	}
596
	FunctionsEdit::addSimpleTag($gedtitl);
597
}
598
599
// 1 _PRIM
600
if (preg_match('/\n(1 _PRIM .*)/', $gedrec, $match)) {
601
	$gedprim = $match[1];
602
} else {
603
	$gedprim = '1 _PRIM';
604
}
605
FunctionsEdit::addSimpleTag($gedprim);
606
607
//-- print out editing fields for any other data in the media record
608
$sourceLevel = 0;
609
$sourceSOUR  = '';
610
$sourcePAGE  = '';
611
$sourceTEXT  = '';
612
$sourceDATE  = '';
613
$sourceQUAY  = '';
614
if (!empty($gedrec)) {
615
	preg_match_all('/\n(1 (?!FILE|FORM|TYPE|TITL|_PRIM|_THUM|CHAN|DATA).*(\n[2-9] .*)*)/', $gedrec, $matches);
616
	foreach ($matches[1] as $subrec) {
617
		$pieces = explode("\n", $subrec);
618
		foreach ($pieces as $piece) {
619
			$ft = preg_match("/(\d) (\w+)(.*)/", $piece, $match);
620
			if ($ft == 0) {
621
				continue;
622
			}
623
			$subLevel = $match[1];
624
			$fact     = trim($match[2]);
625
			$event    = trim($match[3]);
626
			if ($fact === 'NOTE' || $fact === 'TEXT') {
627
				$event .= Functions::getCont($subLevel + 1, $subrec);
628
			}
629
			if ($sourceSOUR !== '' && $subLevel <= $sourceLevel) {
630
				// Get rid of all saved Source data
631
				FunctionsEdit::addSimpleTag($sourceLevel . ' SOUR ' . $sourceSOUR);
632
				FunctionsEdit::addSimpleTag(($sourceLevel + 1) . ' PAGE ' . $sourcePAGE);
633
				FunctionsEdit::addSimpleTag(($sourceLevel + 2) . ' TEXT ' . $sourceTEXT);
634
				FunctionsEdit::addSimpleTag(($sourceLevel + 2) . ' DATE ' . $sourceDATE, '', GedcomTag::getLabel('DATA:DATE'));
635
				FunctionsEdit::addSimpleTag(($sourceLevel + 1) . ' QUAY ' . $sourceQUAY);
636
				$sourceSOUR = '';
637
			}
638
639
			if ($fact === 'SOUR') {
640
				$sourceLevel = $subLevel;
641
				$sourceSOUR  = $event;
642
				$sourcePAGE  = '';
643
				$sourceTEXT  = '';
644
				$sourceDATE  = '';
645
				$sourceQUAY  = '';
646
				continue;
647
			}
648
649
			// Save all incoming data about this source reference
650
			if ($sourceSOUR !== '') {
651
				if ($fact === 'PAGE') {
652
					$sourcePAGE = $event;
653
					continue;
654
				}
655
				if ($fact === 'TEXT') {
656
					$sourceTEXT = $event;
657
					continue;
658
				}
659
				if ($fact === 'DATE') {
660
					$sourceDATE = $event;
661
					continue;
662
				}
663
				if ($fact === 'QUAY') {
664
					$sourceQUAY = $event;
665
					continue;
666
				}
667
				continue;
668
			}
669
670
			// Output anything that isn’t part of a source reference
671
			if (!empty($fact) && $fact !== 'CONC' && $fact !== 'CONT' && $fact !== 'DATA') {
672
				FunctionsEdit::addSimpleTag($subLevel . ' ' . $fact . ' ' . $event);
673
			}
674
		}
675
	}
676
677
	if ($sourceSOUR !== '') {
678
		// Get rid of all saved Source data
679
		FunctionsEdit::addSimpleTag($sourceLevel . ' SOUR ' . $sourceSOUR);
680
		FunctionsEdit::addSimpleTag(($sourceLevel + 1) . ' PAGE ' . $sourcePAGE);
681
		FunctionsEdit::addSimpleTag(($sourceLevel + 2) . ' TEXT ' . $sourceTEXT);
682
		FunctionsEdit::addSimpleTag(($sourceLevel + 2) . ' DATE ' . $sourceDATE, '', GedcomTag::getLabel('DATA:DATE'));
683
		FunctionsEdit::addSimpleTag(($sourceLevel + 1) . ' QUAY ' . $sourceQUAY);
684
	}
685
}
686 View Code Duplication
if (Auth::isAdmin() && $action === 'update') {
687
	echo '<tr><td class="descriptionbox wrap width25">';
688
	echo I18N::translate('Last change'), '</td><td class="optionbox wrap">';
689
	if ($NO_UPDATE_CHAN) {
690
		echo '<input type="checkbox" checked name="preserve_last_changed">';
691
	} else {
692
		echo '<input type="checkbox" name="preserve_last_changed">';
693
	}
694
	echo I18N::translate('Keep the existing “last change” information'), '<br>';
695
	echo '</td></tr>';
696
}
697
echo '</table>';
698
FunctionsEdit::printAddLayer('SOUR', 1);
699
FunctionsEdit::printAddLayer('NOTE', 1);
700
FunctionsEdit::printAddLayer('SHARED_NOTE', 1);
701
FunctionsEdit::printAddLayer('RESN', 1);
702
?>
703
		<div class="row form-group">
704
			<div class="col-sm-9 offset-sm-3">
705
				<button class="btn btn-primary" type="submit">
706
					<?= FontAwesome::decorativeIcon('save') ?>
707
					<?= /* I18N: A button label. */ I18N::translate('save') ?>
708
				</button>
709
				<a class="btn btn-secondary" href="<?= $media->getHtmlUrl() ?>">
0 ignored issues
show
Security Cross-Site Scripting introduced by
$media->getHtmlUrl() can contain request data and is used in html attribute with double-quotes context(s) leading to a potential security vulnerability.

4 paths for user data to reach this point

  1. Path: Read from $_FILES, and $file is assigned in action.php on line 112
  1. Read from $_FILES, and $file is assigned
    in action.php on line 112
  2. $gedcom is assigned
    in action.php on line 148
  3. $gedcom is passed to Tree::createRecord()
    in action.php on line 158
  4. $gedcom is passed to GedcomRecord::getInstance()
    in app/Tree.php on line 782
  5. $gedcom is passed to GedcomRecord::__construct()
    in app/GedcomRecord.php on line 202
  6. GedcomRecord::$gedcom is assigned
    in app/GedcomRecord.php on line 80
  7. Tainted property GedcomRecord::$gedcom is read
    in app/GedcomRecord.php on line 504
  8. GedcomRecord::privatizeGedcom() returns tainted data, and $newgedrec is assigned
    in app/Report/ReportParserGenerate.php on line 594
  9. ReportParserGenerate::$gedrec is assigned
    in app/Report/ReportParserGenerate.php on line 618
  10. Tainted property ReportParserGenerate::$gedrec is read
    in app/Report/ReportParserGenerate.php on line 1262
  11. Data is passed through explode()
    in vendor/app/Functions/Functions.php on line 160
  12. $thisSubrecord is assigned
    in vendor/app/Functions/Functions.php on line 161
  13. Data is passed through substr()
    in vendor/app/Functions/Functions.php on line 167
  14. ReportParserGenerate::$desc is assigned
    in app/Report/ReportParserGenerate.php on line 1262
  15. Tainted property ReportParserGenerate::$desc is read, and $value is assigned
    in app/Report/ReportParserGenerate.php on line 1308
  16. ReportParserGenerate::$vars is assigned
    in app/Report/ReportParserGenerate.php on line 1358
  17. Tainted property ReportParserGenerate::$vars is read, and $id is assigned
    in app/Report/ReportParserGenerate.php on line 827
  18. $id is passed to GedcomRecord::getInstance()
    in app/Report/ReportParserGenerate.php on line 841
  19. $xref is passed to GedcomRecord::__construct()
    in app/GedcomRecord.php on line 202
  20. GedcomRecord::$xref is assigned
    in app/GedcomRecord.php on line 79
  21. Tainted property GedcomRecord::$xref is read
    in app/GedcomRecord.php on line 280
  22. GedcomRecord::getXref() returns tainted data
    in app/GedcomRecord.php on line 360
  23. GedcomRecord::getLinkUrl() returns tainted data
    in app/GedcomRecord.php on line 330
  24. GedcomRecord::getHtmlUrl() returns tainted data
    in addmedia.php on line 709
  2. Path: Read from $_POST, and $newged is assigned in edit_interface.php on line 425
  1. Read from $_POST, and $newged is assigned
    in edit_interface.php on line 425
  2. $newged is assigned
    in edit_interface.php on line 445
  3. $newged is passed through substr(), and $newged is assigned
    in edit_interface.php on line 458
  4. $newged is passed to GedcomRecord::updateFact()
    in edit_interface.php on line 459
  5. $gedcom is passed through preg_replace(), and $gedcom is assigned
    in app/GedcomRecord.php on line 1201
  6. $gedcom is passed through trim(), and $gedcom is assigned
    in app/GedcomRecord.php on line 1202
  7. $new_gedcom is assigned
    in app/GedcomRecord.php on line 1239
  8. GedcomRecord::$gedcom is assigned
    in app/GedcomRecord.php on line 1258
  9. Tainted property GedcomRecord::$gedcom is read
    in app/GedcomRecord.php on line 504
  10. GedcomRecord::privatizeGedcom() returns tainted data, and $newgedrec is assigned
    in app/Report/ReportParserGenerate.php on line 594
  11. ReportParserGenerate::$gedrec is assigned
    in app/Report/ReportParserGenerate.php on line 618
  12. Tainted property ReportParserGenerate::$gedrec is read
    in app/Report/ReportParserGenerate.php on line 1262
  13. Data is passed through explode()
    in vendor/app/Functions/Functions.php on line 160
  14. $thisSubrecord is assigned
    in vendor/app/Functions/Functions.php on line 161
  15. Data is passed through substr()
    in vendor/app/Functions/Functions.php on line 167
  16. ReportParserGenerate::$desc is assigned
    in app/Report/ReportParserGenerate.php on line 1262
  17. Tainted property ReportParserGenerate::$desc is read, and $value is assigned
    in app/Report/ReportParserGenerate.php on line 1308
  18. ReportParserGenerate::$vars is assigned
    in app/Report/ReportParserGenerate.php on line 1358
  19. Tainted property ReportParserGenerate::$vars is read, and $id is assigned
    in app/Report/ReportParserGenerate.php on line 827
  20. $id is passed to GedcomRecord::getInstance()
    in app/Report/ReportParserGenerate.php on line 841
  21. $xref is passed to GedcomRecord::__construct()
    in app/GedcomRecord.php on line 202
  22. GedcomRecord::$xref is assigned
    in app/GedcomRecord.php on line 79
  23. Tainted property GedcomRecord::$xref is read
    in app/GedcomRecord.php on line 280
  24. GedcomRecord::getXref() returns tainted data
    in app/GedcomRecord.php on line 360
  25. GedcomRecord::getLinkUrl() returns tainted data
    in app/GedcomRecord.php on line 330
  26. GedcomRecord::getHtmlUrl() returns tainted data
    in addmedia.php on line 709
  3. Path: Read from $_POST, and $newged is assigned in edit_interface.php on line 429
  1. Read from $_POST, and $newged is assigned
    in edit_interface.php on line 429
  2. $newged is assigned
    in edit_interface.php on line 445
  3. $newged is passed through substr(), and $newged is assigned
    in edit_interface.php on line 458
  4. $newged is passed to GedcomRecord::updateFact()
    in edit_interface.php on line 459
  5. $gedcom is passed through preg_replace(), and $gedcom is assigned
    in app/GedcomRecord.php on line 1201
  6. $gedcom is passed through trim(), and $gedcom is assigned
    in app/GedcomRecord.php on line 1202
  7. $new_gedcom is assigned
    in app/GedcomRecord.php on line 1239
  8. GedcomRecord::$gedcom is assigned
    in app/GedcomRecord.php on line 1258
  9. Tainted property GedcomRecord::$gedcom is read
    in app/GedcomRecord.php on line 504
  10. GedcomRecord::privatizeGedcom() returns tainted data, and $newgedrec is assigned
    in app/Report/ReportParserGenerate.php on line 594
  11. ReportParserGenerate::$gedrec is assigned
    in app/Report/ReportParserGenerate.php on line 618
  12. Tainted property ReportParserGenerate::$gedrec is read
    in app/Report/ReportParserGenerate.php on line 1262
  13. Data is passed through explode()
    in vendor/app/Functions/Functions.php on line 160
  14. $thisSubrecord is assigned
    in vendor/app/Functions/Functions.php on line 161
  15. Data is passed through substr()
    in vendor/app/Functions/Functions.php on line 167
  16. ReportParserGenerate::$desc is assigned
    in app/Report/ReportParserGenerate.php on line 1262
  17. Tainted property ReportParserGenerate::$desc is read, and $value is assigned
    in app/Report/ReportParserGenerate.php on line 1308
  18. ReportParserGenerate::$vars is assigned
    in app/Report/ReportParserGenerate.php on line 1358
  19. Tainted property ReportParserGenerate::$vars is read, and $id is assigned
    in app/Report/ReportParserGenerate.php on line 827
  20. $id is passed to GedcomRecord::getInstance()
    in app/Report/ReportParserGenerate.php on line 841
  21. $xref is passed to GedcomRecord::__construct()
    in app/GedcomRecord.php on line 202
  22. GedcomRecord::$xref is assigned
    in app/GedcomRecord.php on line 79
  23. Tainted property GedcomRecord::$xref is read
    in app/GedcomRecord.php on line 280
  24. GedcomRecord::getXref() returns tainted data
    in app/GedcomRecord.php on line 360
  25. GedcomRecord::getLinkUrl() returns tainted data
    in app/GedcomRecord.php on line 330
  26. GedcomRecord::getHtmlUrl() returns tainted data
    in addmedia.php on line 709
  4. Path: Read from $_POST, and $newged is assigned in edit_interface.php on line 453
  1. Read from $_POST, and $newged is assigned
    in edit_interface.php on line 453
  2. $newged is passed through substr(), and $newged is assigned
    in edit_interface.php on line 458
  3. $newged is passed to GedcomRecord::updateFact()
    in edit_interface.php on line 459
  4. $gedcom is passed through preg_replace(), and $gedcom is assigned
    in app/GedcomRecord.php on line 1201
  5. $gedcom is passed through trim(), and $gedcom is assigned
    in app/GedcomRecord.php on line 1202
  6. $new_gedcom is assigned
    in app/GedcomRecord.php on line 1239
  7. GedcomRecord::$gedcom is assigned
    in app/GedcomRecord.php on line 1258
  8. Tainted property GedcomRecord::$gedcom is read
    in app/GedcomRecord.php on line 504
  9. GedcomRecord::privatizeGedcom() returns tainted data, and $newgedrec is assigned
    in app/Report/ReportParserGenerate.php on line 594
  10. ReportParserGenerate::$gedrec is assigned
    in app/Report/ReportParserGenerate.php on line 618
  11. Tainted property ReportParserGenerate::$gedrec is read
    in app/Report/ReportParserGenerate.php on line 1262
  12. Data is passed through explode()
    in vendor/app/Functions/Functions.php on line 160
  13. $thisSubrecord is assigned
    in vendor/app/Functions/Functions.php on line 161
  14. Data is passed through substr()
    in vendor/app/Functions/Functions.php on line 167
  15. ReportParserGenerate::$desc is assigned
    in app/Report/ReportParserGenerate.php on line 1262
  16. Tainted property ReportParserGenerate::$desc is read, and $value is assigned
    in app/Report/ReportParserGenerate.php on line 1308
  17. ReportParserGenerate::$vars is assigned
    in app/Report/ReportParserGenerate.php on line 1358
  18. Tainted property ReportParserGenerate::$vars is read, and $id is assigned
    in app/Report/ReportParserGenerate.php on line 827
  19. $id is passed to GedcomRecord::getInstance()
    in app/Report/ReportParserGenerate.php on line 841
  20. $xref is passed to GedcomRecord::__construct()
    in app/GedcomRecord.php on line 202
  21. GedcomRecord::$xref is assigned
    in app/GedcomRecord.php on line 79
  22. Tainted property GedcomRecord::$xref is read
    in app/GedcomRecord.php on line 280
  23. GedcomRecord::getXref() returns tainted data
    in app/GedcomRecord.php on line 360
  24. GedcomRecord::getLinkUrl() returns tainted data
    in app/GedcomRecord.php on line 330
  25. GedcomRecord::getHtmlUrl() returns tainted data
    in addmedia.php on line 709

Preventing Cross-Site-Scripting Attacks

Cross-Site-Scripting allows an attacker to inject malicious code into your website - in particular Javascript code, and have that code executed with the privileges of a visiting user. This can be used to obtain data, or perform actions on behalf of that visiting user.

In order to prevent this, make sure to escape all user-provided data:

// for HTML
$sanitized = htmlentities($tainted, ENT_QUOTES);

// for URLs
$sanitized = urlencode($tainted);

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
710
					<?= FontAwesome::decorativeIcon('cancel') ?>
711
					<?= /* I18N: A button label. */ I18N::translate('cancel') ?>
712
				</a>
713
			</div>
714
		</div>
715
	</form>
716
</div>
717