8000 Better use of keyword sanitization by ozh · Pull Request #2682 · YOURLS/YOURLS · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Better use of keyword sanitization #2682

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion admin/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@
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;
Expand Down
4 changes: 2 additions & 2 deletions includes/functions-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 );

Expand Down
11 changes: 11 additions & 0 deletions includes/functions-deprecated.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
*
Expand Down
37 changes: 21 additions & 16 deletions includes/functions-formatting.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,28 @@ function yourls_string2htmlid( $string ) {
}

/**
* Make sure a link keyword (ie "1fv" as in "http://sho.rt/1fv") is valid.
*
*/
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 );
}
* Make sure a link keyword (ie "1fv" as in "http://sho.rt/1fv") is acceptable
*
* 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, $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 );
}

/**
* Alias function. I was always getting it wrong.
*
*/
function yourls_sanitize_keyword( $keyword ) {
return yourls_sanitize_string( $keyword );
return yourls_apply_filter( 'sanitize_string', $valid, $keyword, $restrict_to_shorturl_charset );
}

/**
Expand Down
42 changes: 9 additions & 33 deletions includes/functions-html.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand All @@ -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" />&nbsp;<input type="button" id="edit-close-$id" name="edit-close-$id" value="%s" title="%s" class="button" /><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" />&nbsp;<input type="button" id="edit-close-$id" name="edit-close-$id" value="%s" title="%s" class="button" /><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 {
Expand All @@ -519,7 +520,7 @@ function yourls_table_edit_row( $keyword ) {
* @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 );

Expand Down Expand Up @@ -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>";
Expand All @@ -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
*
Expand Down Expand Up @@ -870,12 +846,12 @@ function yourls_notice_box( $message, $style = 'notice' ) {
* @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 );
}

Expand Down
15 changes: 12 additions & 3 deletions includes/functions-links.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}

Expand Down
54 changes: 38 additions & 16 deletions includes/functions-shorturls.php
10000
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 );

Expand Down Expand Up @@ -376,38 +377,59 @@ 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.
*
*/
/**
* 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 ) {
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 );
if ( false !== $pre )
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 );
}
Expand All @@ -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 );

Expand Down Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ] );
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 );
}

Expand Down
2 changes: 1 addition & 1 deletion tests/tests/format/general.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function test_valid_regexp() {
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?
*/

Expand Down
Loading
2A25
0