Smarter keyword sanitization (#2682)

* Smarter keyword sanitizing, based on context
* Deprecate yourls_sanitize_string()

Fixes #2646.
Fixes #2128.
This commit is contained in:
྅༻ Ǭɀħ ༄༆ཉ 2020-05-09 13:10:27 +02:00 committed by GitHub
parent 19da8de9ec
commit c1517f1f84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 165 additions and 147 deletions

View File

@ -351,7 +351,7 @@ $found_rows = false;
if( $url_results ) {
$found_rows = true;
foreach( $url_results as $url_result ) {
$keyword = yourls_sanitize_string( $url_result->keyword );
$keyword = yourls_sanitize_keyword($url_result->keyword);
$timestamp = strtotime( $url_result->timestamp );
$url = stripslashes( $url_result->url );
$ip = $url_result->ip;

View File

@ -192,7 +192,7 @@ function yourls_api_db_stats() {
*/
function yourls_api_url_stats( $shorturl ) {
$keyword = str_replace( yourls_get_yourls_site() . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'
$keyword = yourls_sanitize_string( $keyword );
$keyword = yourls_sanitize_keyword( $keyword );
$return = yourls_get_keyword_stats( $keyword );
$return['simple'] = 'Need either XML or JSON format for stats';
@ -205,7 +205,7 @@ function yourls_api_url_stats( $shorturl ) {
*/
function yourls_api_expand( $shorturl ) {
$keyword = str_replace( yourls_get_yourls_site() . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'
$keyword = yourls_sanitize_string( $keyword );
$keyword = yourls_sanitize_keyword( $keyword );
$longurl = yourls_get_keyword_longurl( $keyword );

View File

@ -9,6 +9,17 @@
// @codeCoverageIgnoreStart
/**
* The original string sanitize function
*
* @deprecated 1.7.10
*
*/
function yourls_sanitize_string( $string, $restrict_to_shorturl_charset = false ) {
yourls_deprecated_function( __FUNCTION__, '1.7.10', 'yourls_sanitize_keyword' );
return yourls_sanitize_keyword( $string, $restrict_to_shorturl_charset );
}
/**
* Return favicon URL (either default or custom)
*

View File

@ -51,23 +51,28 @@ function yourls_string2htmlid( $string ) {
}
/**
* Make sure a link keyword (ie "1fv" as in "http://sho.rt/1fv") is valid.
* Make sure a link keyword (ie "1fv" as in "http://sho.rt/1fv") is acceptable
*
*/
function yourls_sanitize_string( $string ) {
// make a regexp pattern with the shorturl charset, and remove everything but this
$pattern = yourls_make_regexp_pattern( yourls_get_shorturl_charset() );
$valid = (string) substr( preg_replace( '![^'.$pattern.']!', '', $string ), 0, 199 );
return yourls_apply_filter( 'sanitize_string', $valid, $string );
}
/**
* Alias function. I was always getting it wrong.
* If we are ADDING or EDITING a short URL, the keyword must comply to the short URL charset: every
* character that doesn't belong to it will be removed.
* But otherwise we must have a more conservative approach: we could be checking for a keyword that
* was once valid but now the short URL charset. In such a case, we are treating the keyword for what
* it is: just a part of a URL, hence sanitize it as a URL.
*
* @param string $keyword short URL keyword
* @param bool $restrict_to_shorturl_charset Optional, default false. True if we want the keyword to comply to short URL charset
* @return string The sanitized keyword
*/
function yourls_sanitize_keyword( $keyword ) {
return yourls_sanitize_string( $keyword );
function yourls_sanitize_keyword( $keyword, $restrict_to_shorturl_charset = false ) {
if( $restrict_to_shorturl_charset === true ) {
// make a regexp pattern with the shorturl charset, and remove everything but this
$pattern = yourls_make_regexp_pattern( yourls_get_shorturl_charset() );
$valid = (string) substr( preg_replace( '![^'.$pattern.']!', '', $keyword ), 0, 199 );
} else {
$valid = yourls_sanitize_url( $keyword );
}
return yourls_apply_filter( 'sanitize_string', $valid, $keyword, $restrict_to_shorturl_charset );
}
/**

View File

@ -484,12 +484,13 @@ function yourls_die( $message = '', $title = '', $header_code = 200 ) {
* @return string HTML of the edit row
*/
function yourls_table_edit_row( $keyword ) {
$keyword = yourls_sanitize_string( $keyword );
$keyword = yourls_sanitize_keyword($keyword);
$id = yourls_string2htmlid( $keyword ); // used as HTML #id
$url = yourls_get_keyword_longurl( $keyword );
$title = htmlspecialchars( yourls_get_keyword_title( $keyword ) );
$safe_url = yourls_esc_attr( rawurldecode( $url ) );
$safe_title = yourls_esc_attr( $title );
$safe_keyword = yourls_esc_attr( $keyword );
// Make strings sprintf() safe: '%' -> '%%'
$safe_url = str_replace( '%', '%%', $safe_url );
@ -501,7 +502,7 @@ function yourls_table_edit_row( $keyword ) {
if( $url ) {
$return = <<<RETURN
<tr id="edit-$id" class="edit-row"><td colspan="5" class="edit-row"><strong>%s</strong>:<input type="text" id="edit-url-$id" name="edit-url-$id" value="$safe_url" class="text" size="70" /><br/><strong>%s</strong>: $www<input type="text" id="edit-keyword-$id" name="edit-keyword-$id" value="$keyword" class="text" size="10" /><br/><strong>%s</strong>: <input type="text" id="edit-title-$id" name="edit-title-$id" value="$safe_title" class="text" size="60" /></td><td colspan="1"><input type="button" id="edit-submit-$id" name="edit-submit-$id" value="%s" title="%s" class="button" onclick="edit_link_save('$id');" />&nbsp;<input type="button" id="edit-close-$id" name="edit-close-$id" value="%s" title="%s" class="button" onclick="edit_link_hide('$id');" /><input type="hidden" id="old_keyword_$id" value="$keyword"/><input type="hidden" id="nonce_$id" value="$nonce"/></td></tr>
<tr id="edit-$id" class="edit-row"><td colspan="5" class="edit-row"><strong>%s</strong>:<input type="text" id="edit-url-$id" name="edit-url-$id" value="$safe_url" class="text" size="70" /><br/><strong>%s</strong>: $www<input type="text" id="edit-keyword-$id" name="edit-keyword-$id" value="$safe_keyword" class="text" size="10" /><br/><strong>%s</strong>: <input type="text" id="edit-title-$id" name="edit-title-$id" value="$safe_title" class="text" size="60" /></td><td colspan="1"><input type="button" id="edit-submit-$id" name="edit-submit-$id" value="%s" title="%s" class="button" onclick="edit_link_save('$id');" />&nbsp;<input type="button" id="edit-close-$id" name="edit-close-$id" value="%s" title="%s" class="button" onclick="edit_link_hide('$id');" /><input type="hidden" id="old_keyword_$id" value="$safe_keyword"/><input type="hidden" id="nonce_$id" value="$nonce"/></td></tr>
RETURN;
$return = sprintf( $return, yourls__( 'Long URL' ), yourls__( 'Short URL' ), yourls__( 'Title' ), yourls__( 'Save' ), yourls__( 'Save new values' ), yourls__( 'Cancel' ), yourls__( 'Cancel editing' ) );
} else {
@ -519,7 +520,7 @@ RETURN;
* @return string HTML of the edit row
*/
function yourls_table_add_row( $keyword, $url, $title = '', $ip, $clicks, $timestamp ) {
$keyword = yourls_sanitize_string( $keyword );
$keyword = yourls_sanitize_keyword($keyword);
$id = yourls_string2htmlid( $keyword ); // used as HTML #id
$shorturl = yourls_link( $keyword );
@ -621,13 +622,8 @@ function yourls_table_add_row( $keyword, $url, $title = '', $ip, $clicks, $times
// Row cells: the HTML. Replace every %stuff% in 'template' with 'stuff' value.
$row = "<tr id=\"id-$id\">";
foreach( $cells as $cell_id => $elements ) {
$callback = new yourls_table_add_row_callback( $elements );
$row .= sprintf( '<td class="%s" id="%s">', $cell_id, $cell_id . '-' . $id );
$row .= preg_replace_callback( '/%([^%]+)?%/', array( $callback, 'callback' ), $elements['template'] );
// For the record, in PHP 5.3+ we don't need to introduce a class in order to pass additional parameters
// to the callback function. Instead, we would have used the 'use' keyword :
// $row .= preg_replace_callback( '/%([^%]+)?%/', function( $match ) use ( $elements ) { return $elements[ $match[1] ]; }, $elements['template'] );
$row .= preg_replace_callback( '/%([^%]+)?%/', function( $match ) use ( $elements ) { return $elements[ $match[1] ]; }, $elements['template'] );
$row .= '</td>';
}
$row .= "</tr>";
@ -636,26 +632,6 @@ function yourls_table_add_row( $keyword, $url, $title = '', $ip, $clicks, $times
return $row;
}
/**
* Callback class for yourls_table_add_row
*
* See comment about PHP 5.3+ in yourls_table_add_row()
*
* @since 1.7
*/
class yourls_table_add_row_callback {
private $elements;
function __construct($elements) {
$this->elements = $elements;
}
function callback( $matches ) {
return $this->elements[ $matches[1] ];
}
}
/**
* Echo the main table head
*
@ -870,12 +846,12 @@ HTML;
* @param $page PHP file to display
*/
function yourls_page( $page ) {
$include = YOURLS_PAGEDIR . "/$page.php";
if( !file_exists( $include ) ) {
if( !yourls_is_page($page)) {
yourls_die( yourls_s('Page "%1$s" not found', $page), yourls__('Not found'), 404 );
}
}
yourls_do_action( 'pre_page', $page );
include_once( $include );
include_once( YOURLS_PAGEDIR . "/$page.php" );
yourls_do_action( 'post_page', $page );
}

View File

@ -121,20 +121,29 @@ function yourls_remove_query_arg( $key, $query = false ) {
/**
* Converts keyword into short link (prepend with YOURLS base URL)
*
* This function does not check for a valid keyword
*
*/
function yourls_link( $keyword = '' ) {
$link = yourls_get_yourls_site() . '/' . yourls_sanitize_keyword( $keyword );
$keyword = yourls_sanitize_keyword($keyword);
$link = yourls_get_yourls_site() . '/' . $keyword;
return yourls_apply_filter( 'yourls_link', $link, $keyword );
}
/**
* Converts keyword into stat link (prepend with YOURLS base URL, append +)
*
* This function does not make sure the keyword matches an actual short URL
*
*/
function yourls_statlink( $keyword = '' ) {
$link = yourls_get_yourls_site() . '/' . yourls_sanitize_keyword( $keyword ) . '+';
if( yourls_is_ssl() )
$keyword = yourls_sanitize_keyword($keyword);
$link = yourls_get_yourls_site() . '/' . $keyword . '+';
if( yourls_is_ssl() ) {
$link = yourls_set_url_scheme( $link, 'https' );
}
return yourls_apply_filter( 'yourls_statlink', $link, $keyword );
}

View File

@ -76,8 +76,9 @@ function yourls_add_new_link( $url, $keyword = '', $title = '' ) {
yourls_do_action( 'add_new_link_custom_keyword', $url, $keyword, $title );
$keyword = yourls_sanitize_string( $keyword );
$keyword = yourls_sanitize_keyword( $keyword, true );
$keyword = yourls_apply_filter( 'custom_keyword', $keyword, $url, $title );
if ( !yourls_keyword_is_free( $keyword ) ) {
// This shorturl either reserved or taken already
$return['status'] = 'fail';
@ -187,7 +188,7 @@ function yourls_is_shorturl( $shorturl ) {
}
// Check if it's a valid && used keyword
if( $keyword && $keyword == yourls_sanitize_string( $keyword ) && yourls_keyword_is_taken( $keyword ) ) {
if( $keyword && $keyword == yourls_sanitize_keyword( $keyword ) && yourls_keyword_is_taken( $keyword ) ) {
$is_short = true;
}
@ -206,7 +207,7 @@ function yourls_keyword_is_reserved( $keyword ) {
$reserved = false;
if ( in_array( $keyword, $yourls_reserved_URL)
or file_exists( YOURLS_PAGEDIR ."/$keyword.php" )
or yourls_is_page($keyword)
or is_dir( YOURLS_ABSPATH ."/$keyword" )
)
$reserved = true;
@ -227,7 +228,7 @@ function yourls_delete_link_by_keyword( $keyword ) {
global $ydb;
$table = YOURLS_DB_TABLE_URL;
$keyword = yourls_sanitize_string($keyword);
$keyword = yourls_sanitize_keyword($keyword);
$delete = $ydb->fetchAffected("DELETE FROM `$table` WHERE `keyword` = :keyword", array('keyword' => $keyword));
yourls_do_action( 'delete_link', $keyword, $delete );
return $delete;
@ -302,9 +303,9 @@ function yourls_edit_link( $url, $keyword, $newkeyword='', $title='' ) {
$table = YOURLS_DB_TABLE_URL;
$url = yourls_sanitize_url($url);
$keyword = yourls_sanitize_string($keyword);
$keyword = yourls_sanitize_keyword($keyword);
$title = yourls_sanitize_title($title);
$newkeyword = yourls_sanitize_string($newkeyword);
$newkeyword = yourls_sanitize_keyword($newkeyword, true);
$strip_url = stripslashes( $url );
$strip_title = stripslashes( $title );
@ -376,24 +377,45 @@ function yourls_edit_link_title( $keyword, $title ) {
return $update;
}
/**
* Check if keyword id is free (ie not already taken, and not reserved). Return bool.
*
* @param string $keyword short URL keyword
* @return bool true if keyword is taken (ie there is a short URL for it), false otherwise
*/
function yourls_keyword_is_free( $keyword ) {
function yourls_keyword_is_free( $keyword ) {
$free = true;
if ( yourls_keyword_is_reserved( $keyword ) or yourls_keyword_is_taken( $keyword ) )
if ( yourls_keyword_is_reserved( $keyword ) or yourls_keyword_is_taken( $keyword, false ) ) {
$free = false;
}
return yourls_apply_filter( 'keyword_is_free', $free, $keyword );
}
/**
* Check if a keyword matches a "page"
*
* @see https://github.com/YOURLS/YOURLS/wiki/Pages
* @since 1.7.10
* @param string $keyword Short URL $keyword
* @return bool true if is page, false otherwise
*/
function yourls_is_page($keyword) {
return yourls_apply_filter( 'is_page', file_exists( YOURLS_PAGEDIR . "/$keyword.php" ) );
}
/**
* Check if a keyword is taken (ie there is already a short URL with this id). Return bool.
*
*/
function yourls_keyword_is_taken( $keyword ) {
/**
* Check if a keyword is taken (ie there is already a short URL with this id). Return bool.
*
* @param string $keyword short URL keyword
* @param bool $use_cache optional, default true: do we want to use what is cached in memory, if any, or force a new SQL query
* @return bool true if keyword is taken (ie there is a short URL for it), false otherwise
*/
function yourls_keyword_is_taken( $keyword, $use_cache = true ) {
// Allow plugins to short-circuit the whole function
$pre = yourls_apply_filter( 'shunt_keyword_is_taken', false, $keyword );
@ -401,13 +423,13 @@ function yourls_keyword_is_taken( $keyword ) {
return $pre;
global $ydb;
$keyword = yourls_sanitize_keyword($keyword);
$taken = false;
$table = YOURLS_DB_TABLE_URL;
$already_exists = $ydb->fetchValue("SELECT COUNT(`keyword`) FROM `$table` WHERE `keyword` = :keyword;", array('keyword' => $keyword));
if ( $already_exists )
// To check if a keyword is already associated with a short URL, we fetch all info matching that keyword. This
// will save a query in case of a redirection in yourls-go.php because info will be cached
if ( yourls_get_keyword_infos($keyword, $use_cache) ) {
$taken = true;
}
return yourls_apply_filter( 'keyword_is_taken', $taken, $keyword );
}
@ -426,7 +448,7 @@ function yourls_keyword_is_taken( $keyword ) {
*/
function yourls_get_keyword_infos( $keyword, $use_cache = true ) {
global $ydb;
$keyword = yourls_sanitize_string( $keyword );
$keyword = yourls_sanitize_keyword( $keyword );
yourls_do_action( 'pre_get_keyword', $keyword, $use_cache );
@ -462,7 +484,7 @@ function yourls_get_keyword_info( $keyword, $field, $notfound = false ) {
if ( false !== $pre )
return $pre;
$keyword = yourls_sanitize_string( $keyword );
$keyword = yourls_sanitize_keyword( $keyword );
$infos = yourls_get_keyword_infos( $keyword );
$return = $notfound;

View File

@ -91,7 +91,7 @@ function yourls_update_clicks( $keyword, $clicks = false ) {
return $pre;
global $ydb;
$keyword = yourls_sanitize_string( $keyword );
$keyword = yourls_sanitize_keyword( $keyword );
$table = YOURLS_DB_TABLE_URL;
if ( $clicks !== false && is_int( $clicks ) && $clicks >= 0 )
$update = $ydb->fetchAffected( "UPDATE `$table` SET `clicks` = :clicks WHERE `keyword` = :keyword", [ 'clicks' => $clicks, 'keyword' => $keyword ] );
@ -416,7 +416,7 @@ function yourls_log_redirect( $keyword ) {
$ip = yourls_get_IP();
$binds = [
'now' => date( 'Y-m-d H:i:s' ),
'keyword' => yourls_sanitize_string($keyword),
'keyword' => yourls_sanitize_keyword($keyword),
'referrer' => substr( yourls_get_referrer(), 0, 200 ),
'ua' => substr(yourls_get_user_agent(), 0, 255),
'ip' => $ip,
@ -910,6 +910,8 @@ function yourls_get_request($yourls_site = false, $uri = false) {
$request = current( explode( '?', $request ) );
}
$request = yourls_sanitize_url( $request );
return (string)yourls_apply_filter( 'get_request', $request );
}

View File

@ -134,7 +134,7 @@ class Format_General extends PHPUnit_Framework_TestCase {
From: http://stackoverflow.com/a/12941133/36850
Cool to know :)
We're testing it as used in yourls_sanitize_string()
We're testing it as used in yourls_sanitize_keyword()
TODO: more random char strings to test?
*/

View File

@ -187,14 +187,17 @@ class Format_Sanitize extends PHPUnit_Framework_TestCase {
}
/**
* Checking that string2htmlid is an alphanumeric string
* Checking that keyword are correctly sanitized
*
* @dataProvider keywords_to_sanitize
* @since 0.1
*/
public function test_sanitize_string( $string, $expected ) {
$this->assertSame( $expected, yourls_sanitize_string( $string ) );
$this->assertSame( $expected, yourls_sanitize_keyword( $string ) );
public function test_sanitize_keywords( $keyword, $expected ) {
// the "soft" way: assume keyword can be anything we have in a URL (here, should remain unchanged)
$this->assertSame( $keyword, yourls_sanitize_keyword( $keyword ) );
// the "hard" way: keyword must comply to acceptable short URL charset
$this->assertSame( $expected, yourls_sanitize_keyword( $keyword, true ) );
}
}

View File

@ -2,36 +2,27 @@
define( 'YOURLS_GO', true );
require_once( dirname( __FILE__ ) . '/includes/load-yourls.php' );
// Variables should be defined in yourls-loader.php, if not try GET request (old behavior of yourls-go.php)
if( !isset( $keyword ) && isset( $_GET['id'] ) )
$keyword = $_GET['id'];
$keyword = yourls_sanitize_string( $keyword );
// First possible exit:
if ( !isset( $keyword ) ) {
// Variables should be defined in yourls-loader.php
if( !isset( $keyword ) ) {
yourls_do_action( 'redirect_no_keyword' );
yourls_redirect( YOURLS_SITE, 301 );
}
// Get URL From Database
$url = yourls_get_keyword_longurl( $keyword );
$keyword = yourls_sanitize_keyword($keyword);
// URL found
if( !empty( $url ) ) {
yourls_redirect_shorturl($url, $keyword);
// URL not found. Either reserved, or page, or doesn't exist
} else {
// Do we have a page?
if ( file_exists( YOURLS_PAGEDIR . "/$keyword.php" ) ) {
yourls_page( $keyword );
// Either reserved id, or no such id
} else {
yourls_do_action( 'redirect_keyword_not_found', $keyword );
yourls_redirect( YOURLS_SITE, 302 ); // no 404 to tell browser this might change, and also to not pollute logs
}
// if we have a page, display and exit
if( yourls_is_page($keyword) ) {
yourls_page( $keyword );
return;
}
// if we can get a long URL from the DB, redirect
if( $url = yourls_get_keyword_longurl( $keyword ) ) {
yourls_redirect_shorturl($url, $keyword);
return;
}
// Either reserved keyword, or no such keyword
yourls_do_action( 'redirect_keyword_not_found', $keyword );
yourls_redirect( YOURLS_SITE, 302 ); // no 404 to tell browser this might change, and also to not pollute logs
exit();

View File

@ -2,22 +2,16 @@
// TODO: make things cleaner. This file is an awful HTML/PHP soup.
define( 'YOURLS_INFOS', true );
require_once( dirname( __FILE__ ).'/includes/load-yourls.php' );
require_once( YOURLS_INC.'/functions-infos.php' );
yourls_maybe_require_auth();
// Variables should be defined in yourls-loader.php, if not try GET request (old behavior of yourls-infos.php)
if( !isset( $keyword ) && isset( $_GET['id'] ) )
$keyword = $_GET['id'];
if( !isset( $aggregate ) && isset( $_GET['all'] ) && $_GET['all'] == 1 && yourls_allow_duplicate_longurls() )
$aggregate = true;
// Variables should be defined in yourls-loader.php
if ( !isset( $keyword ) ) {
yourls_do_action( 'infos_no_keyword' );
yourls_redirect( YOURLS_SITE, 302 );
}
// Get basic infos for this shortened URL
$keyword = yourls_sanitize_string( $keyword );
$keyword = yourls_sanitize_keyword( $keyword );
$longurl = yourls_get_keyword_longurl( $keyword );
$clicks = yourls_get_keyword_clicks( $keyword );
$timestamp = yourls_get_keyword_timestamp( $keyword );

View File

@ -18,49 +18,54 @@ if ( '/robots.txt' == $_SERVER['REQUEST_URI'] ) {
require_once __DIR__ . '/includes/load-yourls.php';
// Get request in YOURLS base (eg in 'http://sho.rt/yourls/abcd' get 'abdc')
// At this point, $request is NOT sanitized.
$request = yourls_get_request();
// Make valid regexp pattern from authorized charset in keywords
$pattern = yourls_make_regexp_pattern( yourls_get_shorturl_charset() );
// Now load required template and exit
yourls_do_action( 'pre_load_template', $request );
// At this point, $request is not sanitized. Sanitize in loaded template.
// Let's look at the request : what we want to catch here is "anything", or "anything+" / "anything+all" (stat page)
preg_match( "@^(.+?)(\+(all)?)?/?$@", $request, $matches );
$keyword = isset($matches[1]) ? $matches[1] : null; // 'anything' whatever the request is (keyword, bookmarklet URL...)
$stats = isset($matches[2]) ? $matches[2] : null; // null, or '+' if request is 'anything+', '+all' if request is 'anything+all'
$stats_all = isset($matches[3]) ? $matches[3] : null; // null, or 'all' if request is 'anything+all'
// Redirection:
if( preg_match( "@^([$pattern]+)/?$@", $request, $matches ) ) {
$keyword = isset( $matches[1] ) ? $matches[1] : '';
$keyword = yourls_sanitize_keyword( $keyword );
yourls_do_action( 'load_template_go', $keyword );
require_once( YOURLS_ABSPATH.'/yourls-go.php' );
exit;
// if request has a scheme (eg scheme://uri) : "Prefix-n-Shorten" sends to bookmarklet (doesn't work on Windows)
if ( yourls_get_protocol($keyword) ) {
$url = yourls_sanitize_url_safe($keyword);
$parse = yourls_get_protocol_slashes_and_rest( $url, [ 'up', 'us', 'ur' ] );
yourls_do_action( 'load_template_redirect_admin', $url );
$parse = array_map( 'rawurlencode', $parse );
yourls_do_action( 'pre_redirect_bookmarklet', $url );
// Redirect to /admin/index.php?up=<url protocol>&us=<url slashes>&ur=<url rest>
yourls_redirect( yourls_add_query_arg( $parse , yourls_admin_url( 'index.php' ) ), 302 );
exit;
}
// Stats:
if( preg_match( "@^([$pattern]+)\+(all)?/?$@", $request, $matches ) ) {
$keyword = isset( $matches[1] ) ? $matches[1] : '';
$keyword = yourls_sanitize_keyword( $keyword );
$aggregate = isset( $matches[2] ) ? (bool)$matches[2] && yourls_allow_duplicate_longurls() : false;
yourls_do_action( 'load_template_infos', $keyword );
require_once( YOURLS_ABSPATH.'/yourls-infos.php' );
exit;
// if request is an existing short URL keyword ('abc') or stat page ('abc+') or an existing page :
if ( yourls_keyword_is_taken($keyword) or yourls_is_page($keyword) ) {
// we have a short URL or a page
if( $keyword && !$stats ) {
yourls_do_action( 'load_template_go', $keyword );
require_once( YOURLS_ABSPATH.'/yourls-go.php' );
exit;
}
// we have a stat page
if( $keyword && $stats ) {
$aggregate = $stats_all && yourls_allow_duplicate_longurls();
yourls_do_action( 'load_template_infos', $keyword );
require_once( YOURLS_ABSPATH.'/yourls-infos.php' );
exit;
}
}
// Prefix-n-Shorten sends to bookmarklet (doesn't work on Windows)
if( preg_match( "@^[a-zA-Z]+://.+@", $request, $matches ) ) {
$url = yourls_sanitize_url( $matches[0] );
if( $parse = yourls_get_protocol_slashes_and_rest( $url, [ 'up', 'us', 'ur' ] ) ) {
yourls_do_action( 'load_template_redirect_admin', $url );
$parse = array_map( 'rawurlencode', $parse );
// Redirect to /admin/index.php?up=<url protocol>&us=<url slashes>&ur=<url rest>
yourls_redirect( yourls_add_query_arg( $parse , yourls_admin_url( 'index.php' ) ), 302 );
exit;
}
}
// Past this point this is a request the loader could not understand
// Past this point this is a request the loader could not understand : not a valid shorturl, not a bookmarklet
yourls_do_action( 'redirect_keyword_not_found', $keyword );
yourls_do_action( 'loader_failed', $request );
yourls_redirect( YOURLS_SITE, 302 );
exit;