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\CommonMark\CensusTableExtension; |
19
|
|
|
use Fisharebest\Webtrees\CommonMark\XrefExtension; |
20
|
|
|
use Fisharebest\Webtrees\CommonMark\XrefParser; |
21
|
|
|
use League\CommonMark\Block\Renderer\DocumentRenderer; |
22
|
|
|
use League\CommonMark\Block\Renderer\ParagraphRenderer; |
23
|
|
|
use League\CommonMark\Converter; |
24
|
|
|
use League\CommonMark\DocParser; |
25
|
|
|
use League\CommonMark\Environment; |
26
|
|
|
use League\CommonMark\HtmlRenderer; |
27
|
|
|
use League\CommonMark\Inline\Parser\AutolinkParser; |
28
|
|
|
use League\CommonMark\Inline\Renderer\LinkRenderer; |
29
|
|
|
use League\CommonMark\Inline\Renderer\TextRenderer; |
30
|
|
|
use Webuni\CommonMark\TableExtension\TableExtension; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Filter input and escape output. |
34
|
|
|
*/ |
35
|
|
|
class Filter { |
36
|
|
|
// REGEX to match a URL |
37
|
|
|
// Some versions of RFC3987 have an appendix B which gives the following regex |
38
|
|
|
// (([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? |
|
|
|
|
39
|
|
|
// This matches far too much while a “precise” regex is several pages long. |
40
|
|
|
// This is a compromise. |
41
|
|
|
const URL_REGEX = '((https?|ftp]):)(//([^\s/?#<>]*))?([^\s?#<>]*)(\?([^\s#<>]*))?(#[^\s?#<>]+)?'; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Format block-level text such as notes or transcripts, etc. |
45
|
|
|
* |
46
|
|
|
* @param string $text |
47
|
|
|
* @param Tree $WT_TREE |
48
|
|
|
* |
49
|
|
|
* @return string |
50
|
|
|
*/ |
51
|
|
|
public static function formatText($text, Tree $WT_TREE) { |
52
|
|
|
switch ($WT_TREE->getPreference('FORMAT_TEXT')) { |
53
|
|
|
case 'markdown': |
54
|
|
|
return '<div class="markdown" dir="auto">' . self::markdown($text, $WT_TREE) . '</div>'; |
55
|
|
|
default: |
56
|
|
|
return '<div class="markdown" style="white-space: pre-wrap;" dir="auto">' . self::expandUrls($text, $WT_TREE) . '</div>'; |
57
|
|
|
} |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Format a block of text, expanding URLs and XREFs. |
62
|
|
|
* |
63
|
|
|
* @param string $text |
64
|
|
|
* @param Tree tree |
65
|
|
|
* |
66
|
|
|
* @return string |
67
|
|
|
*/ |
68
|
|
|
public static function expandUrls($text, Tree $tree) { |
69
|
|
|
// If it looks like a URL, turn it into a markdown autolink. |
70
|
|
|
$text = preg_replace('/' . addcslashes(self::URL_REGEX, '/') . '/', '<$0>', $text); |
71
|
|
|
|
72
|
|
|
// Create a minimal commonmark processor - just add support for autolinks. |
73
|
|
|
$environment = new Environment; |
74
|
|
|
$environment->mergeConfig([ |
75
|
|
|
'renderer' => [ |
76
|
|
|
'block_separator' => "\n", |
77
|
|
|
'inner_separator' => "\n", |
78
|
|
|
'soft_break' => "\n", |
79
|
|
|
], |
80
|
|
|
'html_input' => Environment::HTML_INPUT_ESCAPE, |
81
|
|
|
'allow_unsafe_links' => true, |
82
|
|
|
]); |
83
|
|
|
|
84
|
|
|
$environment |
85
|
|
|
->addBlockRenderer('League\CommonMark\Block\Element\Document', new DocumentRenderer) |
86
|
|
|
->addBlockRenderer('League\CommonMark\Block\Element\Paragraph', new ParagraphRenderer) |
87
|
|
|
->addInlineRenderer('League\CommonMark\Inline\Element\Text', new TextRenderer) |
88
|
|
|
->addInlineRenderer('League\CommonMark\Inline\Element\Link', new LinkRenderer) |
89
|
|
|
->addInlineParser(new AutolinkParser); |
90
|
|
|
|
91
|
|
|
$environment->addExtension(new CensusTableExtension); |
92
|
|
|
$environment->addExtension(new XrefExtension($tree)); |
93
|
|
|
|
94
|
|
|
$converter = new Converter(new DocParser($environment), new HtmlRenderer($environment)); |
95
|
|
|
|
96
|
|
|
return $converter->convertToHtml($text); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Format a block of text, using "Markdown". |
101
|
|
|
* |
102
|
|
|
* @param string $text |
103
|
|
|
* @param Tree $tree |
104
|
|
|
* |
105
|
|
|
* @return string |
106
|
|
|
*/ |
107
|
|
|
public static function markdown($text, Tree $tree) { |
108
|
|
|
$environment = Environment::createCommonMarkEnvironment(); |
109
|
|
|
$environment->mergeConfig(['html_input' => 'escape']); |
110
|
|
|
$environment->addExtension(new TableExtension); |
111
|
|
|
$environment->addExtension(new CensusTableExtension); |
112
|
|
|
$environment->addExtension(new XrefExtension($tree)); |
113
|
|
|
|
114
|
|
|
$converter = new Converter(new DocParser($environment), new HtmlRenderer($environment)); |
115
|
|
|
|
116
|
|
|
return $converter->convertToHtml($text); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Validate INPUT parameters |
121
|
|
|
* |
122
|
|
|
* @param string $source |
123
|
|
|
* @param string $variable |
124
|
|
|
* @param string|null $regexp |
125
|
|
|
* @param string $default |
126
|
|
|
* |
127
|
|
|
* @return string |
128
|
|
|
*/ |
129
|
|
|
private static function input($source, $variable, $regexp = null, $default = '') { |
130
|
|
|
if ($regexp) { |
|
|
|
|
131
|
|
|
return filter_input($source, $variable, FILTER_VALIDATE_REGEXP, [ |
132
|
|
|
'options' => [ |
133
|
|
|
'regexp' => '/^(' . $regexp . ')$/u', |
134
|
|
|
'default' => $default, |
135
|
|
|
], |
136
|
|
|
]); |
137
|
|
|
} else { |
138
|
|
|
$tmp = filter_input($source, $variable, FILTER_CALLBACK, [ |
139
|
|
|
'options' => function ($x) { |
140
|
|
|
return mb_check_encoding($x, 'UTF-8') ? $x : false; |
141
|
|
|
}, |
142
|
|
|
]); |
143
|
|
|
|
144
|
|
|
return ($tmp === null || $tmp === false) ? $default : $tmp; |
145
|
|
|
} |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Validate array INPUT parameters |
150
|
|
|
* |
151
|
|
|
* @param string $source |
152
|
|
|
* @param string $variable |
153
|
|
|
* @param string|null $regexp |
154
|
|
|
* @param string $default |
155
|
|
|
* |
156
|
|
|
* @return string[] |
157
|
|
|
*/ |
158
|
|
|
private static function inputArray($source, $variable, $regexp = null, $default = '') { |
159
|
|
|
if ($regexp) { |
|
|
|
|
160
|
|
|
return filter_input_array($source, [ |
161
|
|
|
$variable => [ |
162
|
|
|
'flags' => FILTER_REQUIRE_ARRAY, |
163
|
|
|
'filter' => FILTER_VALIDATE_REGEXP, |
164
|
|
|
'options' => [ |
165
|
|
|
'regexp' => '/^(' . $regexp . ')$/u', |
166
|
|
|
'default' => $default, |
167
|
|
|
], |
168
|
|
|
], |
169
|
|
|
])[$variable] ?: []; |
170
|
|
|
} else { |
171
|
|
|
return filter_input_array($source, [ |
172
|
|
|
$variable => [ |
173
|
|
|
'flags' => FILTER_REQUIRE_ARRAY, |
174
|
|
|
'filter' => FILTER_CALLBACK, |
175
|
|
|
'options' => function ($x) { |
176
|
|
|
return mb_check_encoding($x, 'UTF-8') ? $x : false; |
177
|
|
|
}, |
178
|
|
|
], |
179
|
|
|
])[$variable] ?: []; |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Validate GET parameters |
185
|
|
|
* |
186
|
|
|
* @param string $variable |
187
|
|
|
* @param string|null $regexp |
188
|
|
|
* @param string $default |
189
|
|
|
* |
190
|
|
|
* @return string |
191
|
|
|
*/ |
192
|
|
|
public static function get($variable, $regexp = null, $default = '') { |
193
|
|
|
return self::input(INPUT_GET, $variable, $regexp, $default); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Validate array GET parameters |
198
|
|
|
* |
199
|
|
|
* @param string $variable |
200
|
|
|
* @param string|null $regexp |
201
|
|
|
* @param string $default |
202
|
|
|
* |
203
|
|
|
* @return string[] |
204
|
|
|
*/ |
205
|
|
|
public static function getArray($variable, $regexp = null, $default = '') { |
206
|
|
|
return self::inputArray(INPUT_GET, $variable, $regexp, $default); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Validate boolean GET parameters |
211
|
|
|
* |
212
|
|
|
* @param string $variable |
213
|
|
|
* |
214
|
|
|
* @return bool |
215
|
|
|
*/ |
216
|
|
|
public static function getBool($variable) { |
217
|
|
|
return (bool) filter_input(INPUT_GET, $variable, FILTER_VALIDATE_BOOLEAN); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Validate integer GET parameters |
222
|
|
|
* |
223
|
|
|
* @param string $variable |
224
|
|
|
* @param int $min |
225
|
|
|
* @param int $max |
226
|
|
|
* @param int $default |
227
|
|
|
* |
228
|
|
|
* @return int |
229
|
|
|
*/ |
230
|
|
View Code Duplication |
public static function getInteger($variable, $min = 0, $max = PHP_INT_MAX, $default = 0) { |
|
|
|
|
231
|
|
|
return filter_input(INPUT_GET, $variable, FILTER_VALIDATE_INT, [ |
232
|
|
|
'options' => [ |
233
|
|
|
'min_range' => $min, |
234
|
|
|
'max_range' => $max, |
235
|
|
|
'default' => $default, |
236
|
|
|
], |
237
|
|
|
]); |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* Validate URL GET parameters |
242
|
|
|
* |
243
|
|
|
* @param string $variable |
244
|
|
|
* @param string $default |
245
|
|
|
* |
246
|
|
|
* @return string |
247
|
|
|
*/ |
248
|
|
|
public static function getUrl($variable, $default = '') { |
249
|
|
|
return filter_input(INPUT_GET, $variable, FILTER_VALIDATE_URL) ?: $default; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Validate POST parameters |
254
|
|
|
* |
255
|
|
|
* @param string $variable |
256
|
|
|
* @param string|null $regexp |
257
|
|
|
* @param string $default |
258
|
|
|
* |
259
|
|
|
* @return string |
260
|
|
|
*/ |
261
|
|
|
public static function post($variable, $regexp = null, $default = '') { |
262
|
|
|
return self::input(INPUT_POST, $variable, $regexp, $default); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Validate array POST parameters |
267
|
|
|
* |
268
|
|
|
* @param string $variable |
269
|
|
|
* @param string|null $regexp |
270
|
|
|
* @param string $default |
271
|
|
|
* |
272
|
|
|
* @return string[]|string[][] |
|
|
|
|
273
|
|
|
*/ |
274
|
|
|
public static function postArray($variable, $regexp = null, $default = '') { |
275
|
|
|
return self::inputArray(INPUT_POST, $variable, $regexp, $default); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Validate boolean POST parameters |
280
|
|
|
* |
281
|
|
|
* @param string $variable |
282
|
|
|
* |
283
|
|
|
* @return bool |
284
|
|
|
*/ |
285
|
|
|
public static function postBool($variable) { |
286
|
|
|
return (bool) filter_input(INPUT_POST, $variable, FILTER_VALIDATE_BOOLEAN); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* Validate integer POST parameters |
291
|
|
|
* |
292
|
|
|
* @param string $variable |
293
|
|
|
* @param int $min |
294
|
|
|
* @param int $max |
295
|
|
|
* @param int $default |
296
|
|
|
* |
297
|
|
|
* @return int |
298
|
|
|
*/ |
299
|
|
View Code Duplication |
public static function postInteger($variable, $min = 0, $max = PHP_INT_MAX, $default = 0) { |
|
|
|
|
300
|
|
|
return filter_input(INPUT_POST, $variable, FILTER_VALIDATE_INT, [ |
301
|
|
|
'options' => [ |
302
|
|
|
'min_range' => $min, |
303
|
|
|
'max_range' => $max, |
304
|
|
|
'default' => $default, |
305
|
|
|
], |
306
|
|
|
]); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* Validate URL GET parameters |
311
|
|
|
* |
312
|
|
|
* @param string $variable |
313
|
|
|
* @param string $default |
314
|
|
|
* |
315
|
|
|
* @return string |
316
|
|
|
*/ |
317
|
|
|
public static function postUrl($variable, $default = '') { |
318
|
|
|
return filter_input(INPUT_POST, $variable, FILTER_VALIDATE_URL) ?: $default; |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Validate COOKIE parameters |
323
|
|
|
* |
324
|
|
|
* @param string $variable |
325
|
|
|
* @param string|null $regexp |
326
|
|
|
* @param string $default |
327
|
|
|
* |
328
|
|
|
* @return string |
329
|
|
|
*/ |
330
|
|
|
public static function cookie($variable, $regexp = null, $default = '') { |
331
|
|
|
return self::input(INPUT_COOKIE, $variable, $regexp, $default); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Validate SERVER parameters |
336
|
|
|
* |
337
|
|
|
* @param string $variable |
338
|
|
|
* @param string|null $regexp |
339
|
|
|
* @param string $default |
340
|
|
|
* |
341
|
|
|
* @return string |
342
|
|
|
*/ |
343
|
|
|
public static function server($variable, $regexp = null, $default = '') { |
|
|
|
|
344
|
|
|
// On some servers, variables that are present in $_SERVER cannot be |
345
|
|
|
// found via filter_input(INPUT_SERVER). Instead, they are found via |
346
|
|
|
// filter_input(INPUT_ENV). Since we cannot rely on filter_input(), |
347
|
|
|
// we must use the superglobal directly. |
348
|
|
|
if (array_key_exists($variable, $_SERVER) && ($regexp === null || preg_match('/^(' . $regexp . ')$/', $_SERVER[$variable]))) { |
349
|
|
|
return $_SERVER[$variable]; |
350
|
|
|
} else { |
351
|
|
|
return $default; |
352
|
|
|
} |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Cross-Site Request Forgery tokens - ensure that the user is submitting |
357
|
|
|
* a form that was generated by the current session. |
358
|
|
|
* |
359
|
|
|
* @return string |
360
|
|
|
*/ |
361
|
|
|
public static function getCsrfToken() { |
362
|
|
|
if (!Session::has('CSRF_TOKEN')) { |
363
|
|
|
$charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcedfghijklmnopqrstuvwxyz0123456789'; |
364
|
|
|
$csrf_token = ''; |
365
|
|
|
for ($n = 0; $n < 32; ++$n) { |
366
|
|
|
$csrf_token .= substr($charset, mt_rand(0, 61), 1); |
367
|
|
|
} |
368
|
|
|
Session::put('CSRF_TOKEN', $csrf_token); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
return Session::get('CSRF_TOKEN'); |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
/** |
375
|
|
|
* Generate an <input> element - to protect the current form from CSRF attacks. |
376
|
|
|
* |
377
|
|
|
* @return string |
378
|
|
|
*/ |
379
|
|
|
public static function getCsrf() { |
380
|
|
|
return '<input type="hidden" name="csrf" value="' . self::getCsrfToken() . '">'; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Check that the POST request contains the CSRF token generated above. |
385
|
|
|
* |
386
|
|
|
* @return bool |
387
|
|
|
*/ |
388
|
|
|
public static function checkCsrf() { |
|
|
|
|
389
|
|
|
if (isset($_SERVER['HTTP_X_CSRF_TOKEN']) && $_SERVER['HTTP_X_CSRF_TOKEN'] !== self::getCsrfToken()) { |
390
|
|
|
// Oops. Something is not quite right |
391
|
|
|
Log::addAuthenticationLog('CSRF mismatch - session expired or malicious attack'); |
392
|
|
|
FlashMessages::addMessage(I18N::translate('This form has expired. Try again.'), 'error'); |
393
|
|
|
|
394
|
|
|
return false; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
return true; |
398
|
|
|
} |
399
|
|
|
} |
400
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.