235 lines
8.2 KiB
PHP
235 lines
8.2 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/*
|
||
* helpers.php
|
||
*/
|
||
|
||
// sourced from https://github.com/flattr/normalize-url/blob/master/normalize_url.php
|
||
if (! function_exists('normalize_url')) {
|
||
function normalize_url(?string $url): ?string
|
||
{
|
||
if ($url === null) {
|
||
return null;
|
||
}
|
||
$newUrl = '';
|
||
$url = parse_url($url);
|
||
$defaultSchemes = ['http' => 80, 'https' => 443];
|
||
|
||
if (isset($url['scheme'])) {
|
||
$url['scheme'] = strtolower($url['scheme']);
|
||
// Strip scheme default ports
|
||
if (
|
||
isset($defaultSchemes[$url['scheme']]) &&
|
||
isset($url['port']) &&
|
||
$defaultSchemes[$url['scheme']] == $url['port']
|
||
) {
|
||
unset($url['port']);
|
||
}
|
||
$newUrl .= "{$url['scheme']}://";
|
||
}
|
||
|
||
if (isset($url['host'])) {
|
||
$url['host'] = mb_strtolower($url['host']);
|
||
$newUrl .= $url['host'];
|
||
}
|
||
|
||
if (isset($url['port'])) {
|
||
$newUrl .= ":{$url['port']}";
|
||
}
|
||
|
||
if (isset($url['path'])) {
|
||
// Case normalization
|
||
$url['path'] = normalizer_normalize($url['path'], Normalizer::FORM_C);
|
||
// Strip duplicate slashes
|
||
while (preg_match("/\/\//", $url['path'])) {
|
||
$url['path'] = preg_replace('/\/\//', '/', $url['path']);
|
||
}
|
||
|
||
/*
|
||
* Decode unreserved characters, http://www.apps.ietf.org/rfc/rfc3986.html#sec-2.3
|
||
* Heavily rewritten version of urlDecodeUnreservedChars() in Glen Scott's url-normalizer.
|
||
*/
|
||
$u = [];
|
||
for ($o = 65; $o <= 90; $o++) {
|
||
$u[] = dechex($o);
|
||
}
|
||
for ($o = 97; $o <= 122; $o++) {
|
||
$u[] = dechex($o);
|
||
}
|
||
for ($o = 48; $o <= 57; $o++) {
|
||
$u[] = dechex($o);
|
||
}
|
||
$chrs = ['-', '.', '_', '~'];
|
||
foreach ($chrs as $chr) {
|
||
$u[] = dechex(ord($chr));
|
||
}
|
||
$url['path'] = preg_replace_callback(
|
||
array_map(
|
||
function ($str) {
|
||
return "/%" . strtoupper($str) . "/x";
|
||
},
|
||
$u
|
||
),
|
||
function ($matches) {
|
||
return chr(hexdec($matches[0]));
|
||
},
|
||
$url['path']
|
||
);
|
||
// Remove directory index
|
||
$defaultIndexes = ["/default\.aspx/" => 'default.aspx/', "/default\.asp/" => 'default.asp/',
|
||
"/index\.html/" => 'index.html/', "/index\.htm/" => 'index.htm/',
|
||
"/default\.html/" => 'default.html/', "/default\.htm/" => 'default.htm/',
|
||
"/index\.php/" => 'index.php/', "/index\.jsp/" => 'index.jsp/', ];
|
||
foreach ($defaultIndexes as $index => $strip) {
|
||
if (preg_match($index, $url['path'])) {
|
||
$url['path'] = str_replace($strip, '', $url['path']);
|
||
}
|
||
}
|
||
// here we only want to drop a slash for the root domain
|
||
// e.g. http://example.com/ -> http://example.com
|
||
// but http://example.com/path/ -/-> http://example.com/path
|
||
if ($url['path'] == '/') {
|
||
unset($url['path']);
|
||
}
|
||
|
||
/**
|
||
* Path segment normalization, http://www.apps.ietf.org/rfc/rfc3986.html#sec-5.2.4
|
||
* Heavily rewritten version of removeDotSegments() in Glen Scott's url-normalizer.
|
||
*/
|
||
$new_path = '';
|
||
while (! empty($url['path'])) {
|
||
if (preg_match('!^(\.\./|\./)!x', $url['path'])) {
|
||
$url['path'] = preg_replace('!^(\.\./|\./)!x', '', $url['path']);
|
||
} elseif (
|
||
preg_match('!^(/\./)!x', $url['path'], $matches)
|
||
|| preg_match('!^(/\.)$!x', $url['path'], $matches)
|
||
) {
|
||
$url['path'] = preg_replace('!^' . $matches[1] . '!', '/', $url['path']);
|
||
} elseif (preg_match('!^(/\.\./|/\.\.)!x', $url['path'], $matches)) {
|
||
$url['path'] = preg_replace('!^' . preg_quote($matches[1], '!') . '!x', '/', $url['path']);
|
||
$new_path = preg_replace('!/([^/]+)$!x', '', $new_path);
|
||
} elseif (preg_match('!^(\.|\.\.)$!x', $url['path'])) {
|
||
$url['path'] = preg_replace('!^(\.|\.\.)$!x', '', $url['path']);
|
||
} else {
|
||
if (preg_match('!(/*[^/]*)!x', $url['path'], $matches)) {
|
||
$first_path_segment = $matches[1];
|
||
$url['path'] = preg_replace(
|
||
'/^' . preg_quote($first_path_segment, '/') . '/',
|
||
'',
|
||
$url['path'],
|
||
1
|
||
);
|
||
$new_path .= $first_path_segment;
|
||
}
|
||
}
|
||
}
|
||
$newUrl .= $new_path;
|
||
}
|
||
|
||
if (isset($url['fragment'])) {
|
||
unset($url['fragment']);
|
||
}
|
||
|
||
// Sort GET params alphabetically, not because the RFC requires it but because it's cool!
|
||
if (isset($url['query'])) {
|
||
$queries = explode('&', $url['query']);
|
||
$url['query'] = '';
|
||
sort($queries);
|
||
foreach ($queries as $query) {
|
||
//lets drop query params we don’t want
|
||
$key = stristr($query, '=', true);
|
||
if (queryKeyIsBanned($key) === false) {
|
||
$url['query'] .= "{$query}&";
|
||
}
|
||
}
|
||
$url['query'] = preg_replace('/&\Z/', '', $url['query']);
|
||
if ($url['query'] !== '') {
|
||
$newUrl .= "?{$url['query']}";
|
||
}
|
||
}
|
||
|
||
return $newUrl;
|
||
}
|
||
|
||
function queryKeyIsBanned(string $key): bool
|
||
{
|
||
$bannedKeys = [
|
||
'ref_src',
|
||
];
|
||
|
||
return in_array($key, $bannedKeys);
|
||
}
|
||
}
|
||
|
||
// sourced from https://stackoverflow.com/a/9776726
|
||
if (! function_exists('prettyPrintJson')) {
|
||
function prettyPrintJson(string $json): string
|
||
{
|
||
$result = '';
|
||
$level = 0;
|
||
$in_quotes = false;
|
||
$in_escape = false;
|
||
$ends_line_level = null;
|
||
$json_length = strlen($json);
|
||
|
||
for ($i = 0; $i < $json_length; $i++) {
|
||
$char = $json[$i];
|
||
$new_line_level = null;
|
||
$post = '';
|
||
if ($ends_line_level !== null) {
|
||
$new_line_level = $ends_line_level;
|
||
$ends_line_level = null;
|
||
}
|
||
if ($in_escape) {
|
||
$in_escape = false;
|
||
} elseif ($char === '"') {
|
||
$in_quotes = ! $in_quotes;
|
||
} elseif (! $in_quotes) {
|
||
switch ($char) {
|
||
case '}':
|
||
case ']':
|
||
$level--;
|
||
$ends_line_level = null;
|
||
$new_line_level = $level;
|
||
break;
|
||
case '{':
|
||
case '[':
|
||
$level++;
|
||
//no break
|
||
case ',':
|
||
$ends_line_level = $level;
|
||
break;
|
||
case ':':
|
||
$post = ' ';
|
||
break;
|
||
case ' ':
|
||
case "\t":
|
||
case "\n":
|
||
case "\r":
|
||
$char = '';
|
||
$ends_line_level = $new_line_level;
|
||
$new_line_level = null;
|
||
break;
|
||
}
|
||
} elseif ($char === '\\') {
|
||
$in_escape = true;
|
||
}
|
||
if ($new_line_level !== null) {
|
||
$result .= "\n" . str_repeat("\t", $new_line_level);
|
||
}
|
||
$result .= $char . $post;
|
||
}
|
||
|
||
return str_replace("\t", ' ', $result);
|
||
}
|
||
}
|
||
|
||
// sourced from https://twitter.com/jrubsc/status/907776591320764416/photo/1
|
||
if (! function_exists('carbon')) {
|
||
function carbon(...$args)
|
||
{
|
||
return new Carbon\Carbon(...$args);
|
||
}
|
||
}
|