mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-01-09 11:44:10 +09:00
Merge pull request #366 from kijin/pr/security-refactor
보안관련 클래스 전반적 정리 및 기능 개선 프로젝트
This commit is contained in:
commit
4f015f7bbc
78 changed files with 3860 additions and 3336 deletions
|
|
@ -394,11 +394,11 @@ class Context
|
|||
}
|
||||
if (strpos($current_url, 'xn--') !== false)
|
||||
{
|
||||
$current_url = self::decodeIdna($current_url);
|
||||
$current_url = Rhymix\Framework\URL::decodeIdna($current_url);
|
||||
}
|
||||
if (strpos($request_uri, 'xn--') !== false)
|
||||
{
|
||||
$request_uri = self::decodeIdna($request_uri);
|
||||
$request_uri = Rhymix\Framework\URL::decodeIdna($request_uri);
|
||||
}
|
||||
self::set('current_url', $current_url);
|
||||
self::set('request_uri', $request_uri);
|
||||
|
|
@ -574,8 +574,8 @@ class Context
|
|||
$db_info->sitelock_title = $config['lock']['title'];
|
||||
$db_info->sitelock_message = $config['lock']['message'];
|
||||
$db_info->sitelock_whitelist = count($config['lock']['allow']) ? $config['lock']['allow'] : array('127.0.0.1');
|
||||
$db_info->embed_white_iframe = $config['embedfilter']['iframe'];
|
||||
$db_info->embed_white_object = $config['embedfilter']['object'];
|
||||
$db_info->embed_white_iframe = $config['mediafilter']['iframe'] ?: $config['embedfilter']['iframe'];
|
||||
$db_info->embed_white_object = $config['mediafilter']['object'] ?: $config['embedfilter']['object'];
|
||||
$db_info->use_mobile_view = $config['use_mobile_view'] ? 'Y' : 'N';
|
||||
$db_info->use_prepared_statements = $config['use_prepared_statements'] ? 'Y' : 'N';
|
||||
$db_info->use_rewrite = $config['use_rewrite'] ? 'Y' : 'N';
|
||||
|
|
@ -712,61 +712,66 @@ class Context
|
|||
$current_site = self::getRequestUri();
|
||||
|
||||
// Step 1: if the current site is not the default site, send SSO validation request to the default site
|
||||
if($default_url !== $current_site && !self::get('SSOID') && $_COOKIE['sso'] !== md5($current_site))
|
||||
if($default_url !== $current_site && !self::get('sso_response') && $_COOKIE['sso'] !== md5($current_site))
|
||||
{
|
||||
// Set sso cookie to prevent multiple simultaneous SSO validation requests
|
||||
setcookie('sso', md5($current_site), 0, '/');
|
||||
|
||||
// Redirect to the default site
|
||||
$redirect_url = sprintf('%s?return_url=%s', $default_url, urlencode(base64_encode($current_site)));
|
||||
$sso_request = Rhymix\Framework\Security::encrypt(Rhymix\Framework\URL::getCurrentURL());
|
||||
$redirect_url = $default_url . '?sso_request=' . urlencode($sso_request);
|
||||
header('Location:' . $redirect_url);
|
||||
return FALSE;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: receive and process SSO validation request at the default site
|
||||
if($default_url === $current_site && self::get('return_url'))
|
||||
if($default_url === $current_site && self::get('sso_request'))
|
||||
{
|
||||
// Get the URL of the origin site
|
||||
$url = base64_decode(self::get('return_url'));
|
||||
$url_info = parse_url($url);
|
||||
$sso_request = Rhymix\Framework\Security::decrypt(self::get('sso_request'));
|
||||
if (!$sso_request || !preg_match('!^https?://!', $sso_request))
|
||||
{
|
||||
self::displayErrorPage('SSO Error', 'Invalid SSO Request', 400);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the origin site is a valid site in this XE installation (to prevent open redirect vuln)
|
||||
if(!getModel('module')->getSiteInfoByDomain(rtrim($url, '/'))->site_srl)
|
||||
{
|
||||
htmlHeader();
|
||||
echo self::getLang("msg_invalid_request");
|
||||
htmlFooter();
|
||||
return FALSE;
|
||||
self::displayErrorPage('SSO Error', 'Invalid SSO Request', 400);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Redirect back to the origin site
|
||||
$url_info['query'] .= ($url_info['query'] ? '&' : '') . 'SSOID=' . session_id();
|
||||
$redirect_url = sprintf('%s://%s%s%s%s', $url_info['scheme'], $url_info['host'], $url_info['port'] ? (':' . $url_info['port']) : '', $url_info['path'], ($url_info['query'] ? ('?' . $url_info['query']) : ''));
|
||||
header('Location:' . $redirect_url);
|
||||
return FALSE;
|
||||
$sso_response = Rhymix\Framework\Security::encrypt(session_id());
|
||||
header('Location: ' . Rhymix\Framework\URL::modifyURL($sso_request, array('sso_response' => $sso_response)));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: back at the origin site, set session ID to be the same as the default site
|
||||
if($default_url !== $current_site && self::get('SSOID'))
|
||||
if($default_url !== $current_site && self::get('sso_response'))
|
||||
{
|
||||
// Check that the session ID was given by the default site (to prevent session fixation CSRF)
|
||||
// Check SSO response
|
||||
$sso_response = Rhymix\Framework\Security::decrypt(self::get('sso_response'));
|
||||
if ($sso_response === false)
|
||||
{
|
||||
self::displayErrorPage('SSO Error', 'Invalid SSO Response', 400);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the response was given by the default site (to prevent session fixation CSRF)
|
||||
if(isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], $default_url) !== 0)
|
||||
{
|
||||
htmlHeader();
|
||||
echo self::getLang("msg_invalid_request");
|
||||
htmlFooter();
|
||||
return FALSE;
|
||||
self::displayErrorPage('SSO Error', 'Invalid SSO Response', 400);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set session ID
|
||||
setcookie(session_name(), self::get('SSOID'));
|
||||
setcookie(session_name(), $sso_response);
|
||||
|
||||
// Finally, redirect to the originally requested URL
|
||||
$url_info = parse_url(self::getRequestUrl());
|
||||
$url_info['query'] = preg_replace('/(^|\b)SSOID=([^&?]+)/', '', $url_info['query']);
|
||||
$redirect_url = sprintf('%s://%s%s%s%s', $url_info['scheme'], $url_info['host'], $url_info['port'] ? (':' . $url_info['port']) : '', $url_info['path'], ($url_info['query'] ? ('?' . $url_info['query']) : ''));
|
||||
header('Location:' . $redirect_url);
|
||||
return FALSE;
|
||||
header('Location: ' . Rhymix\Framework\URL::getCurrentURL(array('sso_response' => null)));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If none of the conditions above apply, proceed normally
|
||||
|
|
@ -1073,15 +1078,7 @@ class Context
|
|||
*/
|
||||
public static function encodeIdna($domain)
|
||||
{
|
||||
if(function_exists('idn_to_ascii'))
|
||||
{
|
||||
return idn_to_ascii($domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
$encoder = new TrueBV\Punycode();
|
||||
return $encoder->encode($domain);
|
||||
}
|
||||
return Rhymix\Framework\URL::encodeIdna($domain);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1092,15 +1089,7 @@ class Context
|
|||
*/
|
||||
public static function decodeIdna($domain)
|
||||
{
|
||||
if(function_exists('idn_to_utf8'))
|
||||
{
|
||||
return idn_to_utf8($domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
$decoder = new TrueBV\Punycode();
|
||||
return $decoder->decode($domain);
|
||||
}
|
||||
return Rhymix\Framework\URL::decodeIdna($domain);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1283,11 +1272,15 @@ class Context
|
|||
}
|
||||
|
||||
$xml = $GLOBALS['HTTP_RAW_POST_DATA'];
|
||||
if(Security::detectingXEE($xml))
|
||||
if(!Rhymix\Framework\Security::checkXEE($xml))
|
||||
{
|
||||
header("HTTP/1.0 400 Bad Request");
|
||||
exit;
|
||||
}
|
||||
if(function_exists('libxml_disable_entity_loader'))
|
||||
{
|
||||
libxml_disable_entity_loader(true);
|
||||
}
|
||||
|
||||
$oXml = new XmlParser();
|
||||
$xml_obj = $oXml->parse($xml);
|
||||
|
|
@ -1490,13 +1483,9 @@ class Context
|
|||
}
|
||||
|
||||
// Allow if the current user is in the list of allowed IPs.
|
||||
$allowed_list = config('lock.allow');
|
||||
foreach ($allowed_list as $allowed_ip)
|
||||
if (Rhymix\Framework\Filters\IpFilter::inRanges(RX_CLIENT_IP, config('lock.allow')))
|
||||
{
|
||||
if (Rhymix\Framework\IpFilter::inRange(RX_CLIENT_IP, $allowed_ip))
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Set headers and constants for backward compatibility.
|
||||
|
|
|
|||
|
|
@ -3,313 +3,78 @@
|
|||
|
||||
class EmbedFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* allow script access list
|
||||
* Deprecated properties
|
||||
* @var array
|
||||
*/
|
||||
var $allowscriptaccessList = array();
|
||||
|
||||
/**
|
||||
* allow script access key
|
||||
* @var int
|
||||
*/
|
||||
var $allowscriptaccessKey = 0;
|
||||
var $whiteUrlList = array();
|
||||
var $whiteIframeUrlList = array();
|
||||
var $mimeTypeList = array();
|
||||
var $extList = array();
|
||||
var $parser = NULL;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @return void
|
||||
*/
|
||||
function __construct()
|
||||
{
|
||||
$this->_makeWhiteDomainList();
|
||||
}
|
||||
public $whiteUrlList = array();
|
||||
public $whiteIframeUrlList = array();
|
||||
public $mimeTypeList = array();
|
||||
public $extList = array();
|
||||
|
||||
/**
|
||||
* Return EmbedFilter object
|
||||
* This method for singleton
|
||||
*
|
||||
* @return EmbedFilter
|
||||
*/
|
||||
function getInstance()
|
||||
{
|
||||
if(!isset($GLOBALS['__EMBEDFILTER_INSTANCE__']))
|
||||
{
|
||||
$GLOBALS['__EMBEDFILTER_INSTANCE__'] = new EmbedFilter();
|
||||
}
|
||||
return $GLOBALS['__EMBEDFILTER_INSTANCE__'];
|
||||
return new self();
|
||||
}
|
||||
|
||||
|
||||
public function getWhiteUrlList()
|
||||
{
|
||||
return $this->whiteUrlList;
|
||||
return Rhymix\Framework\Filters\MediaFilter::getObjectWhitelist();
|
||||
}
|
||||
|
||||
|
||||
public function getWhiteIframeUrlList()
|
||||
{
|
||||
return $this->whiteIframeUrlList;
|
||||
return Rhymix\Framework\Filters\MediaFilter::getIframeWhitelist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the content.
|
||||
* @return void
|
||||
*/
|
||||
function check(&$content)
|
||||
{
|
||||
$content = preg_replace_callback('/<(object|param|embed)[^>]*/is', array($this, '_checkAllowScriptAccess'), $content);
|
||||
$content = preg_replace_callback('/<object[^>]*>/is', array($this, '_addAllowScriptAccess'), $content);
|
||||
|
||||
$this->checkObjectTag($content);
|
||||
$this->checkEmbedTag($content);
|
||||
$this->checkParamTag($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check iframe tag in the content.
|
||||
* @return void
|
||||
*/
|
||||
function checkIframeTag(&$content)
|
||||
{
|
||||
// check in Purifier class
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check object tag in the content.
|
||||
* @return void
|
||||
*/
|
||||
function checkObjectTag(&$content)
|
||||
{
|
||||
$content = preg_replace_callback('/<\s*object\s*[^>]+(?:\/?>?)/is', function($m) {
|
||||
$html = Sunra\PhpSimple\HtmlDomParser::str_get_html($m[0]);
|
||||
foreach ($html->find('object') as $element)
|
||||
{
|
||||
if ($element->data && !$this->isWhiteDomain($element->data))
|
||||
{
|
||||
return escape($m[0], false);
|
||||
}
|
||||
if ($element->type && !$this->isWhiteMimetype($element->type))
|
||||
{
|
||||
return escape($m[0], false);
|
||||
}
|
||||
}
|
||||
return $m[0];
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check embed tag in the content.
|
||||
* @return void
|
||||
*/
|
||||
function checkEmbedTag(&$content)
|
||||
{
|
||||
$content = preg_replace_callback('/<\s*embed\s*[^>]+(?:\/?>?)/is', function($m) {
|
||||
$html = Sunra\PhpSimple\HtmlDomParser::str_get_html($m[0]);
|
||||
foreach ($html->find('embed') as $element)
|
||||
{
|
||||
if ($element->src && !$this->isWhiteDomain($element->src))
|
||||
{
|
||||
return escape($m[0], false);
|
||||
}
|
||||
if ($element->type && !$this->isWhiteMimetype($element->type))
|
||||
{
|
||||
return escape($m[0], false);
|
||||
}
|
||||
}
|
||||
return $m[0];
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check param tag in the content.
|
||||
* @return void
|
||||
*/
|
||||
function checkParamTag(&$content)
|
||||
{
|
||||
$content = preg_replace_callback('/<\s*param\s*[^>]+(?:\/?>?)/is', function($m) {
|
||||
$html = Sunra\PhpSimple\HtmlDomParser::str_get_html($m[0]);
|
||||
foreach ($html->find('param') as $element)
|
||||
{
|
||||
foreach (array('movie', 'src', 'href', 'url', 'source') as $attr)
|
||||
{
|
||||
if ($element->$attr && !$this->isWhiteDomain($element->$attr))
|
||||
{
|
||||
return escape($m[0], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $m[0];
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check white domain in object data attribute or embed src attribute.
|
||||
* @return string
|
||||
*/
|
||||
|
||||
function isWhiteDomain($urlAttribute)
|
||||
{
|
||||
if(is_array($this->whiteUrlList))
|
||||
{
|
||||
foreach($this->whiteUrlList AS $key => $value)
|
||||
{
|
||||
if(preg_match('@^https?://' . preg_quote($value, '@') . '@i', $urlAttribute))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
return Rhymix\Framework\Filters\MediaFilter::matchObjectWhitelist($urlAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check white domain in iframe src attribute.
|
||||
* @return string
|
||||
*/
|
||||
|
||||
function isWhiteIframeDomain($urlAttribute)
|
||||
{
|
||||
if(is_array($this->whiteIframeUrlList))
|
||||
{
|
||||
foreach($this->whiteIframeUrlList AS $key => $value)
|
||||
{
|
||||
if(preg_match('@^https?://' . preg_quote($value, '@') . '@i', $urlAttribute))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
return Rhymix\Framework\Filters\MediaFilter::matchIframeWhitelist($urlAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check white mime type in object type attribute or embed type attribute.
|
||||
* @return string
|
||||
*/
|
||||
|
||||
function isWhiteMimetype($mimeType)
|
||||
{
|
||||
if(isset($this->mimeTypeList[$mimeType]))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function isWhiteExt($ext)
|
||||
{
|
||||
if(isset($this->extList[$ext]))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
return true;
|
||||
}
|
||||
|
||||
function _checkAllowScriptAccess($m)
|
||||
|
||||
function check(&$content)
|
||||
{
|
||||
if($m[1] == 'object')
|
||||
{
|
||||
$this->allowscriptaccessList[] = 1;
|
||||
}
|
||||
|
||||
if($m[1] == 'param')
|
||||
{
|
||||
if(stripos($m[0], 'allowscriptaccess'))
|
||||
{
|
||||
$m[0] = '<param name="allowscriptaccess" value="never"';
|
||||
if(substr($m[0], -1) == '/')
|
||||
{
|
||||
$m[0] .= '/';
|
||||
}
|
||||
$this->allowscriptaccessList[count($this->allowscriptaccessList) - 1]--;
|
||||
}
|
||||
}
|
||||
else if($m[1] == 'embed')
|
||||
{
|
||||
if(stripos($m[0], 'allowscriptaccess'))
|
||||
{
|
||||
$m[0] = preg_replace('/always|samedomain/i', 'never', $m[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$m[0] = preg_replace('/\<embed/i', '<embed allowscriptaccess="never"', $m[0]);
|
||||
}
|
||||
}
|
||||
return $m[0];
|
||||
// This functionality has been moved to the HTMLFilter class.
|
||||
}
|
||||
|
||||
function _addAllowScriptAccess($m)
|
||||
|
||||
function checkIframeTag(&$content)
|
||||
{
|
||||
if($this->allowscriptaccessList[$this->allowscriptaccessKey] == 1)
|
||||
{
|
||||
$m[0] = $m[0] . '<param name="allowscriptaccess" value="never"></param>';
|
||||
}
|
||||
$this->allowscriptaccessKey++;
|
||||
return $m[0];
|
||||
// This functionality has been moved to the HTMLFilter class.
|
||||
}
|
||||
|
||||
/**
|
||||
* Make white domain list cache file from xml config file.
|
||||
* @param $whitelist array
|
||||
* @return void
|
||||
*/
|
||||
function _makeWhiteDomainList($whitelist = NULL)
|
||||
|
||||
function checkObjectTag(&$content)
|
||||
{
|
||||
$whiteUrlDefaultList = (include RX_BASEDIR . 'common/defaults/whitelist.php');
|
||||
$this->extList = $whiteUrlDefaultList['extensions'];
|
||||
$this->mimeTypeList = $whiteUrlDefaultList['mime'];
|
||||
$this->whiteUrlList = array();
|
||||
$this->whiteIframeUrlList = array();
|
||||
|
||||
if($whitelist !== NULL)
|
||||
{
|
||||
if(!is_array($whitelist) || !isset($whitelist['object']) || !isset($whitelist['iframe']))
|
||||
{
|
||||
$whitelist = array(
|
||||
'object' => isset($whitelist->object) ? $whitelist->object : array(),
|
||||
'iframe' => isset($whitelist->iframe) ? $whitelist->iframe : array(),
|
||||
);
|
||||
}
|
||||
foreach ($whitelist['object'] as $prefix)
|
||||
{
|
||||
$this->whiteUrlList[] = preg_match('@^https?://(.*)$@i', $prefix, $matches) ? $matches[1] : $prefix;
|
||||
}
|
||||
foreach ($whitelist['iframe'] as $prefix)
|
||||
{
|
||||
$this->whiteIframeUrlList[] = preg_match('@^https?://(.*)$@i', $prefix, $matches) ? $matches[1] : $prefix;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ($whiteUrlDefaultList['object'] as $prefix)
|
||||
{
|
||||
$this->whiteUrlList[] = $prefix;
|
||||
}
|
||||
foreach ($whiteUrlDefaultList['iframe'] as $prefix)
|
||||
{
|
||||
$this->whiteIframeUrlList[] = $prefix;
|
||||
}
|
||||
if ($embedfilter_object = config('embedfilter.object'))
|
||||
{
|
||||
foreach ($embedfilter_object as $prefix)
|
||||
{
|
||||
$this->whiteUrlList[] = preg_match('@^https?://(.*)$@i', $prefix, $matches) ? $matches[1] : $prefix;
|
||||
}
|
||||
}
|
||||
if ($embedfilter_iframe = config('embedfilter.iframe'))
|
||||
{
|
||||
foreach ($embedfilter_iframe as $prefix)
|
||||
{
|
||||
$this->whiteIframeUrlList[] = preg_match('@^https?://(.*)$@i', $prefix, $matches) ? $matches[1] : $prefix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->whiteUrlList = array_unique($this->whiteUrlList);
|
||||
$this->whiteIframeUrlList = array_unique($this->whiteIframeUrlList);
|
||||
natcasesort($this->whiteUrlList);
|
||||
natcasesort($this->whiteIframeUrlList);
|
||||
// This functionality has been moved to the HTMLFilter class.
|
||||
}
|
||||
|
||||
function checkEmbedTag(&$content)
|
||||
{
|
||||
// This functionality has been moved to the HTMLFilter class.
|
||||
}
|
||||
|
||||
function checkParamTag(&$content)
|
||||
{
|
||||
// This functionality has been moved to the HTMLFilter class.
|
||||
}
|
||||
}
|
||||
/* End of file : EmbedFilter.class.php */
|
||||
|
|
|
|||
|
|
@ -6,26 +6,12 @@ class IpFilter
|
|||
public function filter($ip_list, $ip = NULL)
|
||||
{
|
||||
if(!$ip) $ip = $_SERVER['REMOTE_ADDR'];
|
||||
foreach($ip_list as $filter)
|
||||
{
|
||||
if(Rhymix\Framework\IpFilter::inRange($ip, $filter))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Rhymix\Framework\Filters\IpFilter::inRanges($ip, $ip_list);
|
||||
}
|
||||
|
||||
public function validate($ip_list = array())
|
||||
{
|
||||
foreach($ip_list as $filter)
|
||||
{
|
||||
if(!Rhymix\Framework\IpFilter::validateRange($filter))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return Rhymix\Framework\Filters\IpFilter::validateRanges($ip_list);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,458 +1,79 @@
|
|||
<?php
|
||||
/* Copyright (C) NAVER <http://www.navercorp.com> */
|
||||
|
||||
/**
|
||||
* This class can be used to hash passwords using various algorithms and check their validity.
|
||||
* It is fully compatible with previous defaults, while also supporting bcrypt and pbkdf2.
|
||||
*
|
||||
* @file Password.class.php
|
||||
* @author Kijin Sung (kijin@kijinsung.com)
|
||||
* @package /classes/security
|
||||
* @version 1.1
|
||||
*/
|
||||
class Password
|
||||
{
|
||||
/**
|
||||
* @brief Custom algorithms are stored here
|
||||
* @var array
|
||||
*/
|
||||
protected static $_custom = array();
|
||||
|
||||
/**
|
||||
* @brief Register a custom algorithm for password checking
|
||||
* @param string $name The name of the algorithm
|
||||
* @param string $regexp The regular expression to detect the algorithm
|
||||
* @param callable $callback The function to call to regenerate the hash
|
||||
* @return void
|
||||
*/
|
||||
public static function registerCustomAlgorithm($name, $regexp, $callback)
|
||||
{
|
||||
self::$_custom[$name] = array('regexp' => $regexp, 'callback' => $callback);
|
||||
Rhymix\Framework\Password::addAlgorithm($name, $regexp, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the list of hashing algorithms supported by this server
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedAlgorithms()
|
||||
{
|
||||
$retval = array();
|
||||
if(function_exists('hash_hmac') && in_array('sha256', hash_algos()))
|
||||
{
|
||||
$retval['pbkdf2'] = 'pbkdf2';
|
||||
}
|
||||
if(version_compare(PHP_VERSION, '5.3.7', '>=') && defined('CRYPT_BLOWFISH'))
|
||||
{
|
||||
$retval['bcrypt'] = 'bcrypt';
|
||||
}
|
||||
$retval['md5'] = 'md5';
|
||||
return $retval;
|
||||
return Rhymix\Framework\Password::getSupportedAlgorithms();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the best hashing algorithm supported by this server
|
||||
* @return string
|
||||
*/
|
||||
public function getBestAlgorithm()
|
||||
{
|
||||
$algos = $this->getSupportedAlgorithms();
|
||||
return key($algos);
|
||||
return Rhymix\Framework\Password::getBestSupportedAlgorithm();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the currently selected hashing algorithm
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrentlySelectedAlgorithm()
|
||||
{
|
||||
if(function_exists('getModel'))
|
||||
{
|
||||
$config = getModel('member')->getMemberConfig();
|
||||
$algorithm = $config->password_hashing_algorithm;
|
||||
if(strval($algorithm) === '')
|
||||
{
|
||||
$algorithm = 'md5'; // Historical default for XE
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$algorithm = 'md5';
|
||||
}
|
||||
return $algorithm;
|
||||
return Rhymix\Framework\Password::getDefaultAlgorithm();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the currently configured work factor for bcrypt and other adjustable algorithms
|
||||
* @return int
|
||||
*/
|
||||
public function getWorkFactor()
|
||||
{
|
||||
if(function_exists('getModel'))
|
||||
{
|
||||
$config = getModel('member')->getMemberConfig();
|
||||
$work_factor = $config->password_hashing_work_factor;
|
||||
if(!$work_factor || $work_factor < 4 || $work_factor > 31)
|
||||
{
|
||||
$work_factor = 8; // Reasonable default
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$work_factor = 8;
|
||||
}
|
||||
return $work_factor;
|
||||
return Rhymix\Framework\Password::getWorkFactor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a hash using the specified algorithm
|
||||
* @param string $password The password
|
||||
* @param string $algorithm The algorithm (optional)
|
||||
* @return string
|
||||
*/
|
||||
public function createHash($password, $algorithm = null)
|
||||
{
|
||||
if($algorithm === null)
|
||||
{
|
||||
$algorithm = $this->getCurrentlySelectedAlgorithm();
|
||||
}
|
||||
if(!array_key_exists($algorithm, $this->getSupportedAlgorithms()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$password = trim($password);
|
||||
|
||||
switch($algorithm)
|
||||
{
|
||||
case 'md5':
|
||||
return md5($password);
|
||||
|
||||
case 'pbkdf2':
|
||||
$iterations = pow(2, $this->getWorkFactor() + 5);
|
||||
$salt = $this->createSecureSalt(12, 'alnum');
|
||||
$hash = base64_encode($this->pbkdf2($password, $salt, 'sha256', $iterations, 24));
|
||||
return 'sha256:'.sprintf('%07d', $iterations).':'.$salt.':'.$hash;
|
||||
|
||||
case 'bcrypt':
|
||||
return $this->bcrypt($password);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return Rhymix\Framework\Password::hashPassword($password, $algorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if a password matches a hash
|
||||
* @param string $password The password
|
||||
* @param string $hash The hash
|
||||
* @param string $algorithm The algorithm (optional)
|
||||
* @return bool
|
||||
*/
|
||||
public function checkPassword($password, $hash, $algorithm = null)
|
||||
{
|
||||
if($algorithm === null)
|
||||
{
|
||||
$algorithm = $this->checkAlgorithm($hash);
|
||||
}
|
||||
|
||||
$password = trim($password);
|
||||
|
||||
switch($algorithm)
|
||||
{
|
||||
case 'md5':
|
||||
return md5($password) === $hash || md5(sha1(md5($password))) === $hash;
|
||||
|
||||
case 'mysql_old_password':
|
||||
return (class_exists('Context') && substr(Context::getDBType(), 0, 5) === 'mysql') ?
|
||||
DB::getInstance()->isValidOldPassword($password, $hash) : false;
|
||||
|
||||
case 'mysql_password':
|
||||
return $hash[0] === '*' && substr($hash, 1) === strtoupper(sha1(sha1($password, true)));
|
||||
|
||||
case 'pbkdf2':
|
||||
$hash = explode(':', $hash);
|
||||
$hash[3] = base64_decode($hash[3]);
|
||||
$hash_to_compare = $this->pbkdf2($password, $hash[2], $hash[0], intval($hash[1], 10), strlen($hash[3]));
|
||||
return $this->strcmpConstantTime($hash_to_compare, $hash[3]);
|
||||
|
||||
case 'bcrypt':
|
||||
$hash_to_compare = $this->bcrypt($password, $hash);
|
||||
return $this->strcmpConstantTime($hash_to_compare, $hash);
|
||||
|
||||
default:
|
||||
if($algorithm && isset(self::$_custom[$algorithm]))
|
||||
{
|
||||
$hash_callback = self::$_custom[$algorithm]['callback'];
|
||||
$hash_to_compare = $hash_callback($password, $hash);
|
||||
return $this->strcmpConstantTime($hash_to_compare, $hash);
|
||||
}
|
||||
if(in_array($algorithm, hash_algos()))
|
||||
{
|
||||
return $this->strcmpConstantTime(hash($algorithm, $password), $hash);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return Rhymix\Framework\Password::checkPassword($password, $hash, $algorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check the algorithm used to create a hash
|
||||
* @param string $hash The hash
|
||||
* @return string
|
||||
*/
|
||||
function checkAlgorithm($hash)
|
||||
{
|
||||
foreach(self::$_custom as $name => $definition)
|
||||
{
|
||||
if(preg_match($definition['regexp'], $hash))
|
||||
{
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
if(preg_match('/^\$2[axy]\$([0-9]{2})\$/', $hash, $matches))
|
||||
{
|
||||
return 'bcrypt';
|
||||
}
|
||||
elseif(preg_match('/^sha[0-9]+:([0-9]+):/', $hash, $matches))
|
||||
{
|
||||
return 'pbkdf2';
|
||||
}
|
||||
elseif(strlen($hash) === 32 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'md5';
|
||||
}
|
||||
elseif(strlen($hash) === 40 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'sha1';
|
||||
}
|
||||
elseif(strlen($hash) === 64 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'sha256';
|
||||
}
|
||||
elseif(strlen($hash) === 96 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'sha384';
|
||||
}
|
||||
elseif(strlen($hash) === 128 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'sha512';
|
||||
}
|
||||
elseif(strlen($hash) === 16 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'mysql_old_password';
|
||||
}
|
||||
elseif(strlen($hash) === 41 && $hash[0] === '*')
|
||||
{
|
||||
return 'mysql_password';
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$algos = Rhymix\Framework\Password::checkAlgorithm($hash);
|
||||
return count($algos) ? $algos[0] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check the work factor of a hash
|
||||
* @param string $hash The hash
|
||||
* @return int
|
||||
*/
|
||||
function checkWorkFactor($hash)
|
||||
{
|
||||
if(preg_match('/^\$2[axy]\$([0-9]{2})\$/', $hash, $matches))
|
||||
{
|
||||
return intval($matches[1], 10);
|
||||
}
|
||||
elseif(preg_match('/^sha[0-9]+:([0-9]+):/', $hash, $matches))
|
||||
{
|
||||
return max(0, round(log($matches[1], 2)) - 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Rhymix\Framework\Password::checkWorkFactor($hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a cryptographically secure random string to use as a salt
|
||||
* @param int $length The number of bytes to return
|
||||
* @param string $format hex or alnum
|
||||
* @return string
|
||||
*/
|
||||
public function createSecureSalt($length, $format = 'hex')
|
||||
{
|
||||
// Find out how many bytes of entropy we really need
|
||||
switch($format)
|
||||
{
|
||||
case 'hex':
|
||||
$entropy_required_bytes = ceil($length / 2);
|
||||
break;
|
||||
case 'alnum':
|
||||
case 'printable':
|
||||
$entropy_required_bytes = ceil($length * 3 / 4);
|
||||
break;
|
||||
default:
|
||||
$entropy_required_bytes = $length;
|
||||
}
|
||||
|
||||
// Cap entropy to 256 bits from any one source, because anything more is meaningless
|
||||
$entropy_capped_bytes = min(32, $entropy_required_bytes);
|
||||
|
||||
// Find and use the most secure way to generate a random string
|
||||
$entropy = false;
|
||||
$is_windows = (defined('PHP_OS') && strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
|
||||
if(function_exists('random_bytes')) // PHP 7
|
||||
{
|
||||
$entropy = random_bytes($entropy_capped_bytes);
|
||||
}
|
||||
elseif(function_exists('openssl_random_pseudo_bytes') && (!$is_windows || version_compare(PHP_VERSION, '5.4', '>=')))
|
||||
{
|
||||
$entropy = openssl_random_pseudo_bytes($entropy_capped_bytes);
|
||||
}
|
||||
elseif(function_exists('mcrypt_create_iv') && (!$is_windows || version_compare(PHP_VERSION, '5.3.7', '>=')))
|
||||
{
|
||||
$entropy = mcrypt_create_iv($entropy_capped_bytes, MCRYPT_DEV_URANDOM);
|
||||
}
|
||||
elseif(function_exists('mcrypt_create_iv') && $is_windows)
|
||||
{
|
||||
$entropy = mcrypt_create_iv($entropy_capped_bytes, MCRYPT_RAND);
|
||||
}
|
||||
elseif(!$is_windows && @is_readable('/dev/urandom'))
|
||||
{
|
||||
$fp = fopen('/dev/urandom', 'rb');
|
||||
if (function_exists('stream_set_read_buffer')) // This function does not exist in HHVM
|
||||
{
|
||||
stream_set_read_buffer($fp, 0); // Prevent reading several KB of unnecessary data from urandom
|
||||
}
|
||||
$entropy = fread($fp, $entropy_capped_bytes);
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
// Use built-in source of entropy if an error occurs while using other functions
|
||||
if($entropy === false || strlen($entropy) < $entropy_capped_bytes)
|
||||
{
|
||||
$entropy = '';
|
||||
for($i = 0; $i < $entropy_capped_bytes; $i += 2)
|
||||
{
|
||||
$entropy .= pack('S', rand(0, 65536) ^ mt_rand(0, 65535));
|
||||
}
|
||||
}
|
||||
|
||||
// Mixing (see RFC 4086 section 5)
|
||||
$output = '';
|
||||
for($i = 0; $i < $entropy_required_bytes; $i += 32)
|
||||
{
|
||||
$output .= hash('sha256', $entropy . $i . rand(), true);
|
||||
}
|
||||
|
||||
// Encode and return the random string
|
||||
switch($format)
|
||||
{
|
||||
case 'hex':
|
||||
return substr(bin2hex($output), 0, $length);
|
||||
case 'binary':
|
||||
return substr($output, 0, $length);
|
||||
case 'printable':
|
||||
$salt = '';
|
||||
for($i = 0; $i < $length; $i++)
|
||||
{
|
||||
$salt .= chr(33 + (crc32(sha1($i . $output)) % 94));
|
||||
}
|
||||
return $salt;
|
||||
case 'alnum':
|
||||
default:
|
||||
$salt = substr(base64_encode($output), 0, $length);
|
||||
$replacements = chr(rand(65, 90)) . chr(rand(97, 122)) . rand(0, 9);
|
||||
return strtr($salt, '+/=', $replacements);
|
||||
}
|
||||
return Rhymix\Framework\Security::getRandom($length, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a temporary password using the secure salt generator
|
||||
* @param int $length The number of bytes to return
|
||||
* @return string
|
||||
*/
|
||||
public function createTemporaryPassword($length = 16)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
$source = base64_encode($this->createSecureSalt(64, 'binary'));
|
||||
$source = strtr($source, 'iIoOjl10/', '@#$%&*-!?');
|
||||
$source_length = strlen($source);
|
||||
for($i = 0; $i < $source_length - $length; $i++)
|
||||
{
|
||||
$candidate = substr($source, $i, $length);
|
||||
if(preg_match('/[a-z]/', $candidate) && preg_match('/[A-Z]/', $candidate) &&
|
||||
preg_match('/[0-9]/', $candidate) && preg_match('/[^a-zA-Z0-9]/', $candidate))
|
||||
{
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Rhymix\Framework\Password::getRandomPassword($length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate the PBKDF2 hash of a string using a salt
|
||||
* @param string $password The password
|
||||
* @param string $salt The salt
|
||||
* @param string $algorithm The algorithm (optional, default is sha256)
|
||||
* @param int $iterations Iteration count (optional, default is 8192)
|
||||
* @param int $length The length of the hash (optional, default is 32)
|
||||
* @return string
|
||||
*/
|
||||
public function pbkdf2($password, $salt, $algorithm = 'sha256', $iterations = 8192, $length = 24)
|
||||
{
|
||||
if(function_exists('hash_pbkdf2'))
|
||||
{
|
||||
return hash_pbkdf2($algorithm, $password, $salt, $iterations, $length, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$output = '';
|
||||
$block_count = ceil($length / strlen(hash($algorithm, '', true))); // key length divided by the length of one hash
|
||||
for($i = 1; $i <= $block_count; $i++)
|
||||
{
|
||||
$last = $salt . pack('N', $i); // $i encoded as 4 bytes, big endian
|
||||
$last = $xorsum = hash_hmac($algorithm, $last, $password, true); // first iteration
|
||||
for($j = 1; $j < $iterations; $j++) // The other $count - 1 iterations
|
||||
{
|
||||
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
|
||||
}
|
||||
$output .= $xorsum;
|
||||
}
|
||||
return substr($output, 0, $length);
|
||||
}
|
||||
$hash = Rhymix\Framework\Security::pbkdf2($password, $salt, $algorithm, $iterations, $length);
|
||||
$hash = explode(':', $hash);
|
||||
return base64_decode($hash[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate the bcrypt hash of a string using a salt
|
||||
* @param string $password The password
|
||||
* @param string $salt The salt (optional, auto-generated if empty)
|
||||
* @return string
|
||||
*/
|
||||
public function bcrypt($password, $salt = null)
|
||||
{
|
||||
if($salt === null)
|
||||
{
|
||||
$salt = '$2y$'.sprintf('%02d', $this->getWorkFactor()).'$'.$this->createSecureSalt(22, 'alnum');
|
||||
}
|
||||
return crypt($password, $salt);
|
||||
return Rhymix\Framework\Security::bcrypt($password, $salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compare two strings in constant time
|
||||
* @param string $a The first string
|
||||
* @param string $b The second string
|
||||
* @return bool
|
||||
*/
|
||||
function strcmpConstantTime($a, $b)
|
||||
{
|
||||
$diff = strlen($a) ^ strlen($b);
|
||||
$maxlen = min(strlen($a), strlen($b));
|
||||
for($i = 0; $i < $maxlen; $i++)
|
||||
{
|
||||
$diff |= ord($a[$i]) ^ ord($b[$i]);
|
||||
}
|
||||
return $diff === 0;
|
||||
return Rhymix\Framework\Security::compareStrings($a, $b);
|
||||
}
|
||||
}
|
||||
/* End of file : Password.class.php */
|
||||
|
|
|
|||
|
|
@ -3,161 +3,14 @@
|
|||
|
||||
class Purifier
|
||||
{
|
||||
|
||||
private $_cacheDir;
|
||||
private $_htmlPurifier;
|
||||
private $_config;
|
||||
private $_def;
|
||||
|
||||
public function __construct()
|
||||
public static function getInstance()
|
||||
{
|
||||
$this->_checkCacheDir();
|
||||
$this->_setConfig();
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function getInstance()
|
||||
{
|
||||
if(!isset($GLOBALS['__PURIFIER_INSTANCE__']))
|
||||
{
|
||||
$GLOBALS['__PURIFIER_INSTANCE__'] = new Purifier();
|
||||
}
|
||||
return $GLOBALS['__PURIFIER_INSTANCE__'];
|
||||
}
|
||||
|
||||
private function _setConfig()
|
||||
{
|
||||
$this->_config = HTMLPurifier_Config::createDefault();
|
||||
$this->_config->set('HTML.TidyLevel', 'light');
|
||||
$this->_config->set('Output.FlashCompat', TRUE);
|
||||
$this->_config->set('HTML.SafeObject', TRUE);
|
||||
$this->_config->set('HTML.SafeEmbed', TRUE);
|
||||
$this->_config->set('HTML.SafeIframe', TRUE);
|
||||
$this->_config->set('URI.SafeIframeRegexp', $this->_getWhiteDomainRegexp());
|
||||
$this->_config->set('Cache.SerializerPath', $this->_cacheDir);
|
||||
$this->_config->set('Attr.AllowedFrameTargets', array('_blank'));
|
||||
//$allowdClasses = array('emoticon');
|
||||
//$this->_config->set('Attr.AllowedClasses', $allowdClasses);
|
||||
$this->_def = $this->_config->getHTMLDefinition(TRUE);
|
||||
}
|
||||
|
||||
private function _setDefinition(&$content)
|
||||
{
|
||||
// add attribute for edit component
|
||||
$editComponentAttrs = $this->_searchEditComponent($content);
|
||||
if(is_array($editComponentAttrs))
|
||||
{
|
||||
foreach($editComponentAttrs AS $k => $v)
|
||||
{
|
||||
$this->_def->addAttribute('img', $v, 'CDATA');
|
||||
$this->_def->addAttribute('div', $v, 'CDATA');
|
||||
}
|
||||
}
|
||||
|
||||
// add attribute for widget component
|
||||
$widgetAttrs = $this->_searchWidget($content);
|
||||
if(is_array($widgetAttrs))
|
||||
{
|
||||
foreach($widgetAttrs AS $k => $v)
|
||||
{
|
||||
$this->_def->addAttribute('img', $v, 'CDATA');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search attribute of edit component tag
|
||||
* @param string $content
|
||||
* @return array
|
||||
*/
|
||||
private function _searchEditComponent($content)
|
||||
{
|
||||
preg_match_all('!<(?:(div)|img)([^>]*)editor_component=([^>]*)>(?(1)(.*?)</div>)!is', $content, $m);
|
||||
|
||||
$attributeList = array();
|
||||
if(is_array($m[2]))
|
||||
{
|
||||
foreach($m[2] as $key => $value)
|
||||
{
|
||||
unset($script, $m2);
|
||||
$script = " {$m[2][$key]} editor_component={$m[3][$key]}";
|
||||
|
||||
if(preg_match_all('/([a-z0-9_-]+)="([^"]+)"/is', $script, $m2))
|
||||
{
|
||||
foreach($m2[1] as $value2)
|
||||
{
|
||||
//SECISSUE check style attr
|
||||
if($value2 == 'style')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$attributeList[] = $value2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($attributeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search edit component tag
|
||||
* @param string $content
|
||||
* @return array
|
||||
*/
|
||||
private function _searchWidget(&$content)
|
||||
{
|
||||
preg_match_all('!<(?:(div)|img)([^>]*)class="zbxe_widget_output"([^>]*)>(?(1)(.*?)</div>)!is', $content, $m);
|
||||
|
||||
$attributeList = array();
|
||||
if(is_array($m[3]))
|
||||
{
|
||||
$content = str_replace('<img class="zbxe_widget_output"', '<img src="" class="zbxe_widget_output"', $content);
|
||||
|
||||
foreach($m[3] as $key => $value)
|
||||
{
|
||||
if (preg_match_all('/([a-z0-9_-]+)="([^"]+)"/is', $m[3][$key], $m2))
|
||||
{
|
||||
foreach($m2[1] as $value2)
|
||||
{
|
||||
//SECISSUE check style attr
|
||||
if($value2 == 'style')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$attributeList[] = $value2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_unique($attributeList);
|
||||
}
|
||||
|
||||
private function _getWhiteDomainRegexp()
|
||||
{
|
||||
$oEmbedFilter = EmbedFilter::getInstance();
|
||||
$whiteIframeUrlList = $oEmbedFilter->getWhiteIframeUrlList();
|
||||
|
||||
$whiteDomains = array();
|
||||
foreach($whiteIframeUrlList as $domain)
|
||||
{
|
||||
$whiteDomains[] = preg_quote($domain, '%');
|
||||
}
|
||||
return '%^https?://(' . implode('|', $whiteDomains) . ')%';
|
||||
}
|
||||
|
||||
private function _checkCacheDir()
|
||||
{
|
||||
// check htmlpurifier cache directory
|
||||
$this->_cacheDir = _XE_PATH_ . 'files/cache/htmlpurifier';
|
||||
FileHandler::makeDir($this->_cacheDir);
|
||||
}
|
||||
|
||||
|
||||
public function purify(&$content)
|
||||
{
|
||||
$this->_setDefinition($content);
|
||||
$this->_htmlPurifier = new HTMLPurifier($this->_config);
|
||||
|
||||
$content = $this->_htmlPurifier->purify($content);
|
||||
$content = Rhymix\Framework\Filters\HTMLFilter::clean($content);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ class Security
|
|||
* Action target variable. If this value is null, the method will use Context variables
|
||||
* @var mixed
|
||||
*/
|
||||
var $_targetVar = NULL;
|
||||
public $_targetVar = NULL;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param mixed $var Target context
|
||||
* @return void
|
||||
*/
|
||||
function __construct($var = NULL)
|
||||
public function __construct($var = NULL)
|
||||
{
|
||||
$this->_targetVar = $var;
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ class Security
|
|||
* separate the owner(object or array) and the item(property or element) using a dot(.)
|
||||
* @return mixed
|
||||
*/
|
||||
function encodeHTML(/* , $varName1, $varName2, ... */)
|
||||
public function encodeHTML(/* , $varName1, $varName2, ... */)
|
||||
{
|
||||
$varNames = func_get_args();
|
||||
if(count($varNames) < 0)
|
||||
|
|
@ -109,7 +109,7 @@ class Security
|
|||
* @param array $name
|
||||
* @return mixed
|
||||
*/
|
||||
function _encodeHTML($var, $name = array())
|
||||
protected function _encodeHTML($var, $name = array())
|
||||
{
|
||||
if(is_string($var))
|
||||
{
|
||||
|
|
@ -183,46 +183,9 @@ class Security
|
|||
* @param string $xml
|
||||
* @return bool
|
||||
*/
|
||||
static function detectingXEE($xml)
|
||||
public static function detectingXEE($xml)
|
||||
{
|
||||
if(!$xml) return FALSE;
|
||||
|
||||
if(strpos($xml, '<!ENTITY') !== FALSE)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Strip XML declaration.
|
||||
$header = preg_replace('/<\?xml.*?\?'.'>/s', '', substr($xml, 0, 100), 1);
|
||||
$xml = trim(substr_replace($xml, $header, 0, 100));
|
||||
if($xml == '')
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Strip DTD.
|
||||
$header = preg_replace('/^<!DOCTYPE[^>]*+>/i', '', substr($xml, 0, 200), 1);
|
||||
$xml = trim(substr_replace($xml, $header, 0, 200));
|
||||
if($xml == '')
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Confirm the XML now starts with a valid root tag. A root tag can end in [> \t\r\n]
|
||||
$root_tag = substr($xml, 0, strcspn(substr($xml, 0, 20), "> \t\r\n"));
|
||||
|
||||
// Reject a second DTD.
|
||||
if(strtoupper($root_tag) == '<!DOCTYPE')
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if(!in_array($root_tag, array('<methodCall', '<methodResponse', '<fault')))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
return !Rhymix\Framework\Security::checkXEE($xml);
|
||||
}
|
||||
}
|
||||
/* End of file : Security.class.php */
|
||||
|
|
|
|||
|
|
@ -3,40 +3,9 @@
|
|||
|
||||
class UploadFileFilter
|
||||
{
|
||||
private static $_block_list = array ('exec', 'system', 'passthru', 'show_source', 'phpinfo', 'fopen', 'file_get_contents', 'file_put_contents', 'fwrite', 'proc_open', 'popen');
|
||||
|
||||
public function check($file)
|
||||
{
|
||||
// TODO: 기능개선후 enable
|
||||
|
||||
return TRUE; // disable
|
||||
if (! $file || ! FileHandler::exists($file)) return TRUE;
|
||||
return self::_check ( $file );
|
||||
}
|
||||
|
||||
private function _check($file)
|
||||
{
|
||||
if (! ($fp = fopen ( $file, 'r' ))) return FALSE;
|
||||
|
||||
$has_php_tag = FALSE;
|
||||
|
||||
while ( ! feof ( $fp ) )
|
||||
{
|
||||
$content = fread ( $fp, 8192 );
|
||||
if (FALSE === $has_php_tag) $has_php_tag = strpos ( $content, '<?' );
|
||||
foreach ( self::$_block_list as $v )
|
||||
{
|
||||
if (FALSE !== $has_php_tag && FALSE !== strpos ( $content, $v ))
|
||||
{
|
||||
fclose ( $fp );
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose ( $fp );
|
||||
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ else
|
|||
*/
|
||||
if (isset($_SERVER['HTTP_CF_CONNECTING_IP']))
|
||||
{
|
||||
include_once __DIR__ . '/framework/ipfilter.php';
|
||||
Rhymix\Framework\IpFilter::getCloudFlareRealIP();
|
||||
include_once __DIR__ . '/framework/filters/ipfilter.php';
|
||||
Rhymix\Framework\Filters\IpFilter::getCloudFlareRealIP();
|
||||
}
|
||||
if (isset($_SERVER['REMOTE_ADDR']) && preg_match('/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/', $_SERVER['REMOTE_ADDR'], $matches))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ return array(
|
|||
'display_to' => 'admin',
|
||||
'allow' => array(),
|
||||
),
|
||||
'embedfilter' => array(
|
||||
'mediafilter' => array(
|
||||
'iframe' => array(),
|
||||
'object' => array(),
|
||||
),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -34,7 +34,7 @@ class Config
|
|||
}
|
||||
else
|
||||
{
|
||||
if (self::$_config = Compat\ConfigParser::convert())
|
||||
if (self::$_config = Parsers\ConfigParser::convert())
|
||||
{
|
||||
self::save();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -445,13 +445,9 @@ class Debug
|
|||
return $cache = true;
|
||||
|
||||
case 'ip':
|
||||
$allowed_ip = Config::get('debug.allow');
|
||||
foreach ($allowed_ip as $range)
|
||||
if (Filters\IpFilter::inRanges(RX_CLIENT_IP, Config::get('debug.allow')))
|
||||
{
|
||||
if (IpFilter::inRange(RX_CLIENT_IP, $range))
|
||||
{
|
||||
return $cache = true;
|
||||
}
|
||||
return $cache = true;
|
||||
}
|
||||
return $cache = false;
|
||||
|
||||
|
|
|
|||
49
common/framework/filters/filenamefilter.php
Normal file
49
common/framework/filters/filenamefilter.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework\Filters;
|
||||
|
||||
/**
|
||||
* The filename filter class.
|
||||
*/
|
||||
class FilenameFilter
|
||||
{
|
||||
/**
|
||||
* Remove illegal and dangerous characters from a filename.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
public static function clean($filename)
|
||||
{
|
||||
// Replace dangerous characters with safe alternatives, maintaining meaning as much as possible.
|
||||
$illegal = array('\\', '/', '<', '>', '{', '}', ':', ';', '|', '"', '~', '`', '@', '#', '$', '%', '^', '&', '*', '?');
|
||||
$replace = array('', '', '(', ')', '(', ')', '_', ',', '_', '', '_', '\'', '_', '_', '_', '_', '_', '_', '', '');
|
||||
$filename = str_replace($illegal, $replace, $filename);
|
||||
|
||||
// Remove control characters.
|
||||
$filename = preg_replace('/([\\x00-\\x1f\\x7f\\xff]+)/u', '', $filename);
|
||||
|
||||
// Standardize whitespace characters.
|
||||
$filename = trim(preg_replace('/[\\pZ\\pC]+/u', ' ', $filename));
|
||||
|
||||
// Remove excess spaces and replacement characters.
|
||||
$filename = trim($filename, ' .-_');
|
||||
$filename = preg_replace('/__+/', '_', $filename);
|
||||
|
||||
// Change .php files to .phps to make them non-executable.
|
||||
if (strtolower(substr($filename, strlen($filename) - 4)) === '.php')
|
||||
{
|
||||
$filename = substr($filename, 0, strlen($filename) - 4) . '.phps';
|
||||
}
|
||||
|
||||
// Truncate filenames over 127 chars long, or extensions over 16 chars long.
|
||||
if (mb_strlen($filename, 'UTF-8') > 127)
|
||||
{
|
||||
$extension = strrchr($filename, '.');
|
||||
if (mb_strlen($extension, 'UTF-8') > 16) $extension = mb_substr($extension, 0, 16);
|
||||
$filename = mb_substr($filename, 0, 127 - mb_strlen($extension)) . $extension;
|
||||
}
|
||||
|
||||
return $filename;
|
||||
}
|
||||
}
|
||||
499
common/framework/filters/htmlfilter.php
Normal file
499
common/framework/filters/htmlfilter.php
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework\Filters;
|
||||
|
||||
use Rhymix\Framework\Security;
|
||||
|
||||
/**
|
||||
* The HTML filter class.
|
||||
*/
|
||||
class HTMLFilter
|
||||
{
|
||||
/**
|
||||
* HTMLPurifier instance is cached here.
|
||||
*/
|
||||
protected static $_htmlpurifier;
|
||||
|
||||
/**
|
||||
* Pre-processing and post-processing filters are stored here.
|
||||
*/
|
||||
protected static $_preproc = array();
|
||||
protected static $_postproc = array();
|
||||
|
||||
/**
|
||||
* Prepend a pre-processing filter.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function prependPreFilter($callback)
|
||||
{
|
||||
array_unshift(self::$_preproc, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a pre-processing filter.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function appendPreFilter($callback)
|
||||
{
|
||||
self::$_preproc[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend a post-processing filter.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function prependPostFilter($callback)
|
||||
{
|
||||
array_unshift(self::$_postproc, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a post-processing filter.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function appendPostFilter($callback)
|
||||
{
|
||||
self::$_postproc[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter HTML content to block XSS attacks.
|
||||
*
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
public static function clean($input)
|
||||
{
|
||||
foreach (self::$_preproc as $callback)
|
||||
{
|
||||
$input = $callback($input);
|
||||
}
|
||||
|
||||
$input = self::_preprocess($input);
|
||||
$output = self::getHTMLPurifier()->purify($input);
|
||||
$output = self::_postprocess($output);
|
||||
|
||||
foreach (self::$_postproc as $callback)
|
||||
{
|
||||
$output = $callback($output);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of HTMLPurifier.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function getHTMLPurifier()
|
||||
{
|
||||
// Create an instance with reasonable defaults.
|
||||
if (self::$_htmlpurifier === null)
|
||||
{
|
||||
// Get the default configuration.
|
||||
$config = \HTMLPurifier_Config::createDefault();
|
||||
|
||||
// Customize the default configuration.
|
||||
$config->set('Attr.AllowedFrameTargets', array('_blank'));
|
||||
$config->set('Attr.DefaultImageAlt', '');
|
||||
$config->set('Attr.EnableID', false);
|
||||
$config->set('AutoFormat.AutoParagraph', false);
|
||||
$config->set('AutoFormat.DisplayLinkURI', false);
|
||||
$config->set('AutoFormat.Linkify', false);
|
||||
$config->set('Core.Encoding', 'UTF-8');
|
||||
$config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
|
||||
$config->set('HTML.FlashAllowFullScreen', true);
|
||||
$config->set('HTML.MaxImgLength', null);
|
||||
$config->set('CSS.MaxImgLength', null);
|
||||
$config->set('CSS.Proprietary', true);
|
||||
$config->set('Output.FlashCompat', true);
|
||||
$config->set('Output.Newline', "\n");
|
||||
$config->set('URI.MakeAbsolute', false);
|
||||
|
||||
// Allow embedding of external multimedia content.
|
||||
$config->set('HTML.SafeEmbed', true);
|
||||
$config->set('HTML.SafeIframe', true);
|
||||
$config->set('HTML.SafeObject', true);
|
||||
$config->set('URI.SafeIframeRegexp', MediaFilter::getIframeWhitelistRegex());
|
||||
|
||||
// Set the serializer path.
|
||||
$config->set('Cache.SerializerPath', RX_BASEDIR . 'files/cache/htmlpurifier');
|
||||
\FileHandler::makeDir(RX_BASEDIR . 'files/cache/htmlpurifier');
|
||||
|
||||
// Modify the HTML definition to support editor components and widgets.
|
||||
$def = $config->getHTMLDefinition(true);
|
||||
$def->addAttribute('img', 'editor_component', 'Text');
|
||||
$def->addAttribute('img', 'rx_encoded_properties', 'Text');
|
||||
$def->addAttribute('div', 'rx_encoded_properties', 'Text');
|
||||
|
||||
// Support HTML5 and CSS3.
|
||||
self::_supportHTML5($config);
|
||||
self::_supportCSS3($config);
|
||||
|
||||
// Cache our instance of HTMLPurifier.
|
||||
self::$_htmlpurifier = new \HTMLPurifier($config);
|
||||
}
|
||||
|
||||
// Return the cached instance.
|
||||
return self::$_htmlpurifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch HTMLPurifier to support some HTML5 tags and attributes.
|
||||
*
|
||||
* These changes are based on https://github.com/xemlock/htmlpurifier-html5
|
||||
* but modified to support even more tags and attributes.
|
||||
*
|
||||
* @param object $config
|
||||
* @return void
|
||||
*/
|
||||
protected static function _supportHTML5($config)
|
||||
{
|
||||
// Get the HTML definition.
|
||||
$def = $config->getHTMLDefinition(true);
|
||||
|
||||
// Add various block-level tags.
|
||||
$def->addElement('header', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('footer', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('nav', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('main', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('section', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('article', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('aside', 'Block', 'Flow', 'Common');
|
||||
|
||||
// Add various inline tags.
|
||||
$def->addElement('s', 'Inline', 'Inline', 'Common');
|
||||
$def->addElement('sub', 'Inline', 'Inline', 'Common');
|
||||
$def->addElement('sup', 'Inline', 'Inline', 'Common');
|
||||
$def->addElement('mark', 'Inline', 'Inline', 'Common');
|
||||
$def->addElement('wbr', 'Inline', 'Empty', 'Core');
|
||||
|
||||
// Support figures.
|
||||
$def->addElement('figure', 'Block', 'Optional: (figcaption, Flow) | (Flow, figcaption) | Flow', 'Common');
|
||||
$def->addElement('figcaption', 'Inline', 'Flow', 'Common');
|
||||
|
||||
// Support insertions and deletions.
|
||||
$def->addElement('ins', 'Block', 'Flow', 'Common', array('cite' => 'URI', 'datetime' => 'Text'));
|
||||
$def->addElement('del', 'Block', 'Flow', 'Common', array('cite' => 'URI', 'datetime' => 'Text'));
|
||||
|
||||
// Support the <time> tag.
|
||||
$time = $def->addElement('time', 'Inline', 'Inline', 'Common', array('datetime' => 'Text', 'pubdate' => 'Bool'));
|
||||
$time->excludes = array('time' => true);
|
||||
|
||||
// Suppport <audio> and <video> tags. DO NOT ALLOW AUTOPLAY.
|
||||
$def->addElement('audio', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', array(
|
||||
'src' => 'URI',
|
||||
'type' => 'Text',
|
||||
'preload' => 'Enum#auto,metadata,none',
|
||||
'controls' => 'Bool',
|
||||
'muted' => 'Bool',
|
||||
'loop' => 'Bool',
|
||||
));
|
||||
$def->addElement('video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', array(
|
||||
'src' => 'URI',
|
||||
'type' => 'Text',
|
||||
'width' => 'Length',
|
||||
'height' => 'Length',
|
||||
'poster' => 'URI',
|
||||
'preload' => 'Enum#auto,metadata,none',
|
||||
'controls' => 'Bool',
|
||||
'muted' => 'Bool',
|
||||
'loop' => 'Bool',
|
||||
));
|
||||
$def->addElement('source', 'Block', 'Empty', 'Common', array(
|
||||
'src' => 'URI',
|
||||
'media' => 'Text',
|
||||
'type' => 'Text',
|
||||
));
|
||||
$def->addElement('track', 'Block', 'Empty', 'Common', array(
|
||||
'src' => 'URI',
|
||||
'srclang' => 'Text',
|
||||
'label' => 'Text',
|
||||
'kind' => 'Enum#captions,chapters,descriptions,metadata,subtitles',
|
||||
'default' => 'Bool',
|
||||
));
|
||||
|
||||
// Support additional properties.
|
||||
$def->addAttribute('img', 'srcset', 'Text');
|
||||
$def->addAttribute('iframe', 'allowfullscreen', 'Bool');
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch HTMLPurifier to support more CSS2 and some CSS3 properties.
|
||||
*
|
||||
* These changes are based on:
|
||||
* - https://github.com/mattiaswelander/htmlpurifier
|
||||
*
|
||||
* @param object $config
|
||||
* @return void
|
||||
*/
|
||||
protected static function _supportCSS3($config)
|
||||
{
|
||||
// Initialize $info.
|
||||
$info = array();
|
||||
|
||||
// min-width, max-width, etc.
|
||||
$info['min-width'] = $info['max-width'] = $info['min-height'] =
|
||||
$info['max-height'] = new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new \HTMLPurifier_AttrDef_CSS_Length(0),
|
||||
new \HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit')),
|
||||
));
|
||||
|
||||
// border-radius, etc.
|
||||
$border_radius = $info['border-top-left-radius'] =
|
||||
$info['border-top-right-radius'] = $info['border-bottom-left-radius'] =
|
||||
$info['border-bottom-right-radius'] = new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new \HTMLPurifier_AttrDef_CSS_Length(0),
|
||||
new \HTMLPurifier_AttrDef_CSS_Percentage(true),
|
||||
new \HTMLPurifier_AttrDef_Enum(array('initial', 'inherit')),
|
||||
));
|
||||
$info['border-radius'] = new \HTMLPurifier_AttrDef_CSS_Multiple($border_radius);
|
||||
|
||||
// word-break word-wrap, etc.
|
||||
$info['word-break'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'normal', 'break-all', 'keep-all', 'initial', 'inherit',
|
||||
));
|
||||
$info['word-wrap'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'normal', 'break-word', 'initial', 'inherit',
|
||||
));
|
||||
$info['text-overflow'] = new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new \HTMLPurifier_AttrDef_Enum(array('clip', 'ellipsis', 'initial', 'inherit')),
|
||||
));
|
||||
|
||||
// text-shadow
|
||||
$info['text-shadow'] = new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new \HTMLPurifier_AttrDef_CSS_Multiple(new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new \HTMLPurifier_AttrDef_CSS_Length(),
|
||||
new \HTMLPurifier_AttrDef_CSS_Color(),
|
||||
))),
|
||||
new \HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit')),
|
||||
));
|
||||
|
||||
// box-shadow and box-sizing
|
||||
$info['box-shadow'] = new \HTMLPurifier_AttrDef_CSS_Multiple(new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new \HTMLPurifier_AttrDef_CSS_Length(),
|
||||
new \HTMLPurifier_AttrDef_CSS_Percentage(),
|
||||
new \HTMLPurifier_AttrDef_CSS_Color(),
|
||||
new \HTMLPurifier_AttrDef_Enum(array('none', 'inset', 'initial', 'inherit')),
|
||||
)));
|
||||
$info['box-sizing'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'content-box', 'border-box', 'initial', 'inherit',
|
||||
));
|
||||
|
||||
// outline
|
||||
$info['outline-color'] = new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new \HTMLPurifier_AttrDef_CSS_Color(),
|
||||
new \HTMLPurifier_AttrDef_Enum(array('invert', 'initial', 'inherit')),
|
||||
));
|
||||
$info['outline-offset'] = new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new \HTMLPurifier_AttrDef_CSS_Length(),
|
||||
new \HTMLPurifier_AttrDef_Enum(array('initial', 'inherit')),
|
||||
));
|
||||
$info['outline-style'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset', 'initial', 'inherit',
|
||||
));
|
||||
$info['outline-width'] = new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new \HTMLPurifier_AttrDef_CSS_Length(),
|
||||
new \HTMLPurifier_AttrDef_Enum(array('medium', 'thin', 'thick', 'initial', 'inherit')),
|
||||
));
|
||||
$info['outline'] = new \HTMLPurifier_AttrDef_CSS_Multiple(new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
$info['outline-color'], $info['outline-style'], $info['outline-width'],
|
||||
new \HTMLPurifier_AttrDef_Enum(array('initial', 'inherit')),
|
||||
)));
|
||||
|
||||
// flexbox
|
||||
$info['display'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'block', 'flex', '-webkit-flex', 'inline', 'inline-block', 'inline-flex', '-webkit-inline-flex', 'inline-table',
|
||||
'list-item', 'run-in', 'compact', 'marker', 'table', 'table-row-group', 'table-header-group', 'table-footer-group',
|
||||
'table-row', 'table-column-group', 'table-column', 'table-cell', 'table-caption',
|
||||
'none', 'initial', 'inherit',
|
||||
));
|
||||
$info['order'] = new \HTMLPurifier_AttrDef_CSS_Number();
|
||||
$info['align-content'] = $info['justify-content'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'stretch', 'center', 'flex-start', 'flex-end', 'space-between', 'space-around', 'initial', 'inherit',
|
||||
));
|
||||
$info['align-items'] = $info['align-self'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'stretch', 'center', 'flex-start', 'flex-end', 'baseline', 'initial', 'inherit',
|
||||
));
|
||||
$info['flex-basis'] = new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new \HTMLPurifier_AttrDef_CSS_Length(),
|
||||
new \HTMLPurifier_AttrDef_CSS_Percentage(),
|
||||
new \HTMLPurifier_AttrDef_Enum(array('auto', 'initial', 'inherit')),
|
||||
));
|
||||
$info['flex-direction'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'row', 'row-reverse', 'column', 'column-reverse', 'initial', 'inherit',
|
||||
));
|
||||
$info['flex-wrap'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'nowrap', 'wrap', 'wrap-reverse', 'initial', 'inherit',
|
||||
));
|
||||
$info['flex-flow'] = new \HTMLPurifier_AttrDef_CSS_Multiple(new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
$info['flex-direction'], $info['flex-wrap'],
|
||||
)));
|
||||
$info['flex-grow'] = new \HTMLPurifier_AttrDef_CSS_Number();
|
||||
$info['flex-shrink'] = new \HTMLPurifier_AttrDef_CSS_Number();
|
||||
$info['flex'] = new \HTMLPurifier_AttrDef_CSS_Multiple(new \HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
$info['flex-grow'], $info['flex-shrink'], $info['flex-basis'],
|
||||
new \HTMLPurifier_AttrDef_Enum(array('auto', 'none', 'initial', 'inherit')),
|
||||
)));
|
||||
|
||||
// misc
|
||||
$info['caption-side'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'top', 'bottom', 'initial', 'inherit',
|
||||
));
|
||||
$info['empty-cells'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'show', 'hide', 'initial', 'inherit',
|
||||
));
|
||||
$info['hanging-punctuation'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'none', 'first', 'last', 'allow-end', 'force-end', 'initial', 'inherit',
|
||||
));
|
||||
$info['overflow'] = $info['overflow-x'] = $info['overflow-y'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'visible', 'hidden', 'scroll', 'auto', 'initial', 'inherit',
|
||||
));
|
||||
$info['resize'] = new \HTMLPurifier_AttrDef_Enum(array(
|
||||
'none', 'both', 'horizontal', 'vertical', 'initial', 'inherit',
|
||||
));
|
||||
|
||||
// Wrap all new properties with a decorator that handles !important.
|
||||
$allow_important = $config->get('CSS.AllowImportant');
|
||||
$css_definition = $config->getCSSDefinition();
|
||||
foreach ($info as $key => $val)
|
||||
{
|
||||
$css_definition->info[$key] = new \HTMLPurifier_AttrDef_CSS_ImportantDecorator($val, $allow_important);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rhymix-specific preprocessing method.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected static function _preprocess($content)
|
||||
{
|
||||
// Encode widget and editor component properties so that they are not removed by HTMLPurifier.
|
||||
$content = self::_encodeWidgetsAndEditorComponents($content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rhymix-specific postprocessing method.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected static function _postprocess($content)
|
||||
{
|
||||
// Define acts to allow and deny.
|
||||
$allow_acts = array('procFileDownload');
|
||||
$deny_acts = array('dispMemberLogout', 'dispLayoutPreview');
|
||||
|
||||
// Remove tags not supported in Rhymix. Some of these may also have been removed by HTMLPurifier.
|
||||
$content = preg_replace_callback('!</?(?:html|body|head|title|meta|base|link|script|style|applet)\b[^>]*>!i', function($matches) {
|
||||
return htmlspecialchars($matches[0], ENT_QUOTES, 'UTF-8');
|
||||
}, $content);
|
||||
|
||||
// Remove object and embed URLs that are not allowed.
|
||||
$whitelist = MediaFilter::getObjectWhitelistRegex();
|
||||
$content = preg_replace_callback('!<(object|embed|param|audio|video|source|track)([^>]+)>!i', function($matches) use($whitelist) {
|
||||
return preg_replace_callback('!([a-zA-Z0-9_-]+)="([^"]+)"!', function($attr) use($whitelist) {
|
||||
if (in_array($attr[1], array('data', 'src', 'href', 'url', 'movie', 'source')))
|
||||
{
|
||||
$url = trim(htmlspecialchars_decode($attr[2]));
|
||||
if (preg_match('!^(https?:)?//!i', $url) && !preg_match($whitelist, $url))
|
||||
{
|
||||
return $attr[1] . '=""';
|
||||
}
|
||||
}
|
||||
return $attr[0];
|
||||
}, $matches[0]);
|
||||
}, $content);
|
||||
|
||||
// Remove link URLs that may be CSRF attempts.
|
||||
$content = preg_replace_callback('!\b(src|href|data|value)="([^"]+)"!i', function($matches) use($allow_acts, $deny_acts) {
|
||||
$url = preg_replace('!\s+!', '', htmlspecialchars_decode(rawurldecode($matches[2])));
|
||||
if (preg_match('!\bact=((disp|proc)[^&]+)!i', $url, $urlmatches))
|
||||
{
|
||||
$act = $urlmatches[1];
|
||||
if (!in_array($act, $allow_acts) && (in_array($act, $deny_acts) || $urlmatches[2] === 'proc'))
|
||||
{
|
||||
return $matches[1] . '=""';
|
||||
}
|
||||
}
|
||||
return $matches[0];
|
||||
}, $content);
|
||||
|
||||
// Restore widget and editor component properties.
|
||||
$content = self::_decodeWidgetsAndEditorComponents($content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode widgets and editor components before processing.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected static function _encodeWidgetsAndEditorComponents($content)
|
||||
{
|
||||
return preg_replace_callback('!<(div|img)([^>]*)(editor_component="[^"]+"|class="zbxe_widget_output")([^>]*)>!i', function($match) {
|
||||
$tag = strtolower($match[1]);
|
||||
$attrs = array();
|
||||
$html = preg_replace_callback('!([a-zA-Z0-9_-]+)="([^"]+)"!', function($attr) use($tag, &$attrs) {
|
||||
$attrkey = strtolower($attr[1]);
|
||||
if ($tag === 'img' && preg_match('/^(?:width|height|src|alt|ismap|usemap)$/', $attrkey))
|
||||
{
|
||||
return $attr[0];
|
||||
}
|
||||
if (preg_match('/^(?:on|data-|(?:accesskey|class|contextmenu|contenteditable|dir|draggable|dropzone|editor_component|hidden|id|lang|name|style|tabindex|title)$)/', $attrkey))
|
||||
{
|
||||
return $attr[0];
|
||||
}
|
||||
$attrs[$attrkey] = htmlspecialchars_decode($attr[2]);
|
||||
return '';
|
||||
}, $match[0]);
|
||||
if ($tag === 'img' && !preg_match('/\ssrc="/', $html))
|
||||
{
|
||||
$html = substr($html, 0, 4) . ' src=""' . substr($html, 4);
|
||||
}
|
||||
$encoded_properties = Security::encrypt(json_encode($attrs));
|
||||
return substr($html, 0, 4) . ' rx_encoded_properties="' . $encoded_properties . '"' . substr($html, 4);
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode widgets and editor components after processing.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected static function _decodeWidgetsAndEditorComponents($content)
|
||||
{
|
||||
return preg_replace_callback('!<(div|img)([^>]*)(\srx_encoded_properties="([^"]+)")!i', function($match) {
|
||||
$attrs = array();
|
||||
$decoded_properties = Security::decrypt($match[4]);
|
||||
if (!$decoded_properties)
|
||||
{
|
||||
return str_replace($match[3], '', $match[0]);
|
||||
}
|
||||
$decoded_properties = json_decode($decoded_properties);
|
||||
if (!$decoded_properties)
|
||||
{
|
||||
return str_replace($match[3], '', $match[0]);
|
||||
}
|
||||
foreach ($decoded_properties as $key => $val)
|
||||
{
|
||||
$attrs[] = $key . '="' . htmlspecialchars($val) . '"';
|
||||
}
|
||||
return str_replace($match[3], ' ' . implode(' ', $attrs), $match[0]);
|
||||
}, $content);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework;
|
||||
namespace Rhymix\Framework\Filters;
|
||||
|
||||
/**
|
||||
* The IP filter class.
|
||||
|
|
@ -66,6 +66,25 @@ class IpFilter
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given IP address belongs to a set of ranges.
|
||||
*
|
||||
* @param string $ip
|
||||
* @param array $ranges
|
||||
* @return bool
|
||||
*/
|
||||
public static function inRanges($ip, array $ranges)
|
||||
{
|
||||
foreach ($ranges as $range)
|
||||
{
|
||||
if (self::inRange($ip, $range))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a range definition is valid.
|
||||
*
|
||||
|
|
@ -91,6 +110,24 @@ class IpFilter
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a set of range definitions is valid.
|
||||
*
|
||||
* @param array $ranges
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateRanges(array $ranges)
|
||||
{
|
||||
foreach ($ranges as $range)
|
||||
{
|
||||
if (!self::validateRange($range))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get real IP from CloudFlare headers.
|
||||
*
|
||||
254
common/framework/filters/mediafilter.php
Normal file
254
common/framework/filters/mediafilter.php
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework\Filters;
|
||||
|
||||
use Rhymix\Framework\Config;
|
||||
|
||||
/**
|
||||
* The media filter class.
|
||||
*/
|
||||
class MediaFilter
|
||||
{
|
||||
/**
|
||||
* Whitelists are cached here.
|
||||
*/
|
||||
protected static $_iframe_whitelist;
|
||||
protected static $_object_whitelist;
|
||||
|
||||
/**
|
||||
* Add a prefix to the iframe whitelist.
|
||||
*
|
||||
* @param string $prefix
|
||||
* @parsm bool $permanently
|
||||
* @return void
|
||||
*/
|
||||
public static function addIframePrefix($prefix, $permanently = false)
|
||||
{
|
||||
if (!count(self::$_iframe_whitelist))
|
||||
{
|
||||
self::_loadWhitelists();
|
||||
}
|
||||
|
||||
$prefix = self::formatPrefix($prefix);
|
||||
if (!in_array($prefix, self::$_iframe_whitelist))
|
||||
{
|
||||
self::$_iframe_whitelist[] = $prefix;
|
||||
natcasesort(self::$_iframe_whitelist);
|
||||
|
||||
if ($permanently)
|
||||
{
|
||||
Config::set('mediafilter.iframe', self::$_iframe_whitelist);
|
||||
Config::save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a prefix to the object whitelist.
|
||||
*
|
||||
* @param string $prefix
|
||||
* @parsm bool $permanently
|
||||
* @return void
|
||||
*/
|
||||
public static function addObjectPrefix($prefix, $permanently = false)
|
||||
{
|
||||
if (!count(self::$_object_whitelist))
|
||||
{
|
||||
self::_loadWhitelists();
|
||||
}
|
||||
|
||||
$prefix = self::formatPrefix($prefix);
|
||||
if (!in_array($prefix, self::$_object_whitelist))
|
||||
{
|
||||
self::$_object_whitelist[] = $prefix;
|
||||
natcasesort(self::$_object_whitelist);
|
||||
|
||||
if ($permanently)
|
||||
{
|
||||
Config::set('mediafilter.object', self::$_object_whitelist);
|
||||
Config::save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a prefix for standardization.
|
||||
*
|
||||
* @param string $prefix
|
||||
* @return string
|
||||
*/
|
||||
public static function formatPrefix($prefix)
|
||||
{
|
||||
$prefix = preg_match('@^https?://(.*)$@i', $prefix, $matches) ? $matches[1] : $prefix;
|
||||
if (strpos($prefix, '/') === false)
|
||||
{
|
||||
$prefix .= '/';
|
||||
}
|
||||
return $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the iframe whitelist.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getIframeWhitelist()
|
||||
{
|
||||
if (!count(self::$_iframe_whitelist))
|
||||
{
|
||||
self::_loadWhitelists();
|
||||
}
|
||||
return self::$_iframe_whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the iframe whitelist as a regular expression.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getIframeWhitelistRegex()
|
||||
{
|
||||
if (!count(self::$_iframe_whitelist))
|
||||
{
|
||||
self::_loadWhitelists();
|
||||
}
|
||||
$result = array();
|
||||
foreach(self::$_iframe_whitelist as $domain)
|
||||
{
|
||||
$result[] = str_replace('\*\.', '[a-z0-9-]+\.', preg_quote($domain, '%'));
|
||||
}
|
||||
return '%^https?://(' . implode('|', $result) . ')%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object whitelist.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getObjectWhitelist()
|
||||
{
|
||||
if (!count(self::$_object_whitelist))
|
||||
{
|
||||
self::_loadWhitelists();
|
||||
}
|
||||
return self::$_object_whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object whitelist as a regular expression.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getObjectWhitelistRegex()
|
||||
{
|
||||
if (!count(self::$_object_whitelist))
|
||||
{
|
||||
self::_loadWhitelists();
|
||||
}
|
||||
$result = array();
|
||||
foreach(self::$_object_whitelist as $domain)
|
||||
{
|
||||
$result[] = str_replace('\*\.', '[a-z0-9-]+\.', preg_quote($domain, '%'));
|
||||
}
|
||||
return '%^https?://(' . implode('|', $result) . ')%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a URL matches the iframe whitelist.
|
||||
*
|
||||
* @param string $url
|
||||
* @return bool
|
||||
*/
|
||||
public static function matchIframeWhitelist($url)
|
||||
{
|
||||
return preg_match(self::getIframeWhitelistRegex(), $url) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a URL matches the iframe whitelist.
|
||||
*
|
||||
* @param string $url
|
||||
* @return bool
|
||||
*/
|
||||
public static function matchObjectWhitelist($url)
|
||||
{
|
||||
return preg_match(self::getObjectWhitelistRegex(), $url) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove embedded media from HTML content.
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $replacement
|
||||
* @return string
|
||||
*/
|
||||
public static function removeEmbeddedMedia($input, $replacement = '')
|
||||
{
|
||||
$input = preg_replace('!<object[^>]*>(.*?</object>)?!is', $replacement, $input);
|
||||
$input = preg_replace('!<embed[^>]*>(.*?</embed>)?!is', $replacement, $input);
|
||||
$input = preg_replace('!<img[^>]*editor_component="multimedia_link"[^>]*>(.*?</img>)?!is', $replacement, $input);
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load whitelists.
|
||||
*
|
||||
* @param array $custom_whitelist
|
||||
* @return void
|
||||
*/
|
||||
protected static function _loadWhitelists($custom_whitelist = array())
|
||||
{
|
||||
$default_whitelist = (include RX_BASEDIR . 'common/defaults/whitelist.php');
|
||||
self::$_object_whitelist = array();
|
||||
self::$_iframe_whitelist = array();
|
||||
|
||||
if(count($custom_whitelist))
|
||||
{
|
||||
if(!is_array($custom_whitelist) || !isset($custom_whitelist['iframe']) || !isset($custom_whitelist['object']))
|
||||
{
|
||||
$whitelist = array(
|
||||
'iframe' => isset($whitelist->iframe) ? $whitelist->iframe : array(),
|
||||
'object' => isset($whitelist->object) ? $whitelist->object : array(),
|
||||
);
|
||||
}
|
||||
foreach ($custom_whitelist['iframe'] as $prefix)
|
||||
{
|
||||
self::$_iframe_whitelist[] = self::formatPrefix($prefix);
|
||||
}
|
||||
foreach ($custom_whitelist['object'] as $prefix)
|
||||
{
|
||||
self::$_object_whitelist[] = self::formatPrefix($prefix);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ($default_whitelist['iframe'] as $prefix)
|
||||
{
|
||||
self::$_iframe_whitelist[] = $prefix;
|
||||
}
|
||||
foreach ($default_whitelist['object'] as $prefix)
|
||||
{
|
||||
self::$_object_whitelist[] = $prefix;
|
||||
}
|
||||
if ($iframe_whitelist = config('mediafilter.iframe') ?: config('embedfilter.iframe'))
|
||||
{
|
||||
foreach ($iframe_whitelist as $prefix)
|
||||
{
|
||||
self::$_iframe_whitelist[] = self::formatPrefix($prefix);
|
||||
}
|
||||
}
|
||||
if ($object_whitelist = config('mediafilter.object') ?: config('embedfilter.object'))
|
||||
{
|
||||
foreach ($object_whitelist as $prefix)
|
||||
{
|
||||
self::$_object_whitelist[] = self::formatPrefix($prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::$_object_whitelist = array_unique(self::$_object_whitelist);
|
||||
self::$_iframe_whitelist = array_unique(self::$_iframe_whitelist);
|
||||
natcasesort(self::$_object_whitelist);
|
||||
natcasesort(self::$_iframe_whitelist);
|
||||
}
|
||||
}
|
||||
|
|
@ -156,7 +156,7 @@ class Lang
|
|||
}
|
||||
elseif (file_exists("$dir/lang.xml"))
|
||||
{
|
||||
$filename = Compat\LangParser::compileXMLtoPHP("$dir/lang.xml", $language === 'ja' ? 'jp' : $language);
|
||||
$filename = Parsers\LangParser::compileXMLtoPHP("$dir/lang.xml", $language === 'ja' ? 'jp' : $language);
|
||||
}
|
||||
elseif (file_exists($dir . '/' . ($language === 'ja' ? 'jp' : $language) . '.lang.php'))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework\Compat;
|
||||
namespace Rhymix\Framework\Parsers;
|
||||
|
||||
use Rhymix\Framework\Config;
|
||||
use Rhymix\Framework\DateTime;
|
||||
use Rhymix\Framework\Security;
|
||||
|
||||
/**
|
||||
* Config parser class for XE compatibility.
|
||||
|
|
@ -151,9 +152,9 @@ class ConfigParser
|
|||
}
|
||||
|
||||
// Create new crypto keys.
|
||||
$config['crypto']['encryption_key'] = \Password::createSecureSalt(64, 'alnum');
|
||||
$config['crypto']['authentication_key'] = \Password::createSecureSalt(64, 'alnum');
|
||||
$config['crypto']['session_key'] = \Password::createSecureSalt(64, 'alnum');
|
||||
$config['crypto']['encryption_key'] = Security::getRandom(64, 'alnum');
|
||||
$config['crypto']['authentication_key'] = Security::getRandom(64, 'alnum');
|
||||
$config['crypto']['session_key'] = Security::getRandom(64, 'alnum');
|
||||
|
||||
// Convert language configuration.
|
||||
if (isset($db_info->lang_type))
|
||||
|
|
@ -216,14 +217,14 @@ class ConfigParser
|
|||
}
|
||||
$config['lock']['allow'] = array_values($db_info->sitelock_whitelist);
|
||||
|
||||
// Convert embed filter configuration.
|
||||
// Convert media filter configuration.
|
||||
if (is_array($db_info->embed_white_iframe))
|
||||
{
|
||||
$whitelist = array_unique(array_map(function($item) {
|
||||
return preg_match('@^https?://(.*)$@i', $item, $matches) ? $matches[1] : $item;
|
||||
}, $db_info->embed_white_iframe));
|
||||
natcasesort($whitelist);
|
||||
$config['embedfilter']['iframe'] = $whitelist;
|
||||
$config['mediafilter']['iframe'] = $whitelist;
|
||||
}
|
||||
if (is_array($db_info->embed_white_object))
|
||||
{
|
||||
|
|
@ -231,7 +232,7 @@ class ConfigParser
|
|||
return preg_match('@^https?://(.*)$@i', $item, $matches) ? $matches[1] : $item;
|
||||
}, $db_info->embed_white_object));
|
||||
natcasesort($whitelist);
|
||||
$config['embedfilter']['object'] = $whitelist;
|
||||
$config['mediafilter']['object'] = $whitelist;
|
||||
}
|
||||
|
||||
// Convert miscellaneous configuration.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework\Compat;
|
||||
namespace Rhymix\Framework\Parsers;
|
||||
|
||||
use Rhymix\Framework\Lang;
|
||||
|
||||
480
common/framework/password.php
Normal file
480
common/framework/password.php
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework;
|
||||
|
||||
/**
|
||||
* The password class.
|
||||
*/
|
||||
class Password
|
||||
{
|
||||
/**
|
||||
* Regular expressions to detect various hashing algorithms.
|
||||
*/
|
||||
protected static $_algorithm_callbacks = array();
|
||||
protected static $_algorithm_signatures = array(
|
||||
'bcrypt' => '/^\$2[a-z]\$[0-9]{2}\$/',
|
||||
'pbkdf2' => '/^[a-z0-9]+:[0-9]+:/',
|
||||
'md5' => '/^[0-9a-f]{32}$/',
|
||||
'md5,sha1,md5' => '/^[0-9a-f]{32}$/',
|
||||
'sha1' => '/^[0-9a-f]{40}$/',
|
||||
'sha256' => '/^[0-9a-f]{64}$/',
|
||||
'sha384' => '/^[0-9a-f]{96}$/',
|
||||
'sha512' => '/^[0-9a-f]{128}$/',
|
||||
'ripemd160' => '/^[0-9a-f]{40}$/',
|
||||
'whirlpool' => '/^[0-9a-f]{128}$/',
|
||||
'mssql_pwdencrypt' => '/^0x0100[0-9A-F]{48}$/',
|
||||
'mysql_old_password' => '/^[0-9a-f]{16}$/',
|
||||
'mysql_new_password' => '/^\*[0-9A-F]{40}$/',
|
||||
'portable' => '/^\$P\$/',
|
||||
'drupal' => '/^\$S\$/',
|
||||
'joomla' => '/^[0-9a-f]{32}:[0-9a-zA-Z\.\+\/\=]{32}$/',
|
||||
'kimsqrb' => '/\$[1-4]\$[0-9]{14}$/',
|
||||
'crypt' => '/^([0-9a-zA-Z\.\/]{13}$|_[0-9a-zA-Z\.\/]{19}$|\$[156]\$)/',
|
||||
);
|
||||
|
||||
/**
|
||||
* Add a custom algorithm.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $signature
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function addAlgorithm($name, $signature, $callback)
|
||||
{
|
||||
self::$_algorithm_signatures[$name] = $signature;
|
||||
self::$_algorithm_callbacks[$name] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given sequence of algorithms is valid.
|
||||
*
|
||||
* @param array|string $algos
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidAlgorithm($algos)
|
||||
{
|
||||
$hash_algos = hash_algos();
|
||||
$algos = is_array($algos) ? $algos : explode(',', $algos);
|
||||
foreach ($algos as $algo)
|
||||
{
|
||||
if (array_key_exists($algo, self::$_algorithm_signatures))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (in_array($algo, $hash_algos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of hashing algorithms supported by this server.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getSupportedAlgorithms()
|
||||
{
|
||||
$retval = array();
|
||||
if (version_compare(PHP_VERSION, '5.3.7', '>=') && defined('\CRYPT_BLOWFISH'))
|
||||
{
|
||||
$retval['bcrypt'] = 'bcrypt';
|
||||
}
|
||||
if (function_exists('hash_hmac') && in_array('sha512', hash_algos()))
|
||||
{
|
||||
$retval['pbkdf2'] = 'pbkdf2';
|
||||
}
|
||||
$retval['sha512'] = 'sha512';
|
||||
$retval['sha256'] = 'sha256';
|
||||
$retval['sha1'] = 'sha1';
|
||||
$retval['md5'] = 'md5';
|
||||
return $retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best hashing algorithm supported by this server.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getBestSupportedAlgorithm()
|
||||
{
|
||||
$algos = self::getSupportedAlgorithms();
|
||||
return key($algos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current default hashing algorithm.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDefaultAlgorithm()
|
||||
{
|
||||
if (function_exists('getModel'))
|
||||
{
|
||||
$config = getModel('member')->getMemberConfig();
|
||||
$algorithm = $config->password_hashing_algorithm;
|
||||
if (strval($algorithm) === '')
|
||||
{
|
||||
$algorithm = 'md5';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$algorithm = 'md5';
|
||||
}
|
||||
return $algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently configured work factor for bcrypt and other adjustable algorithms.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getWorkFactor()
|
||||
{
|
||||
if (function_exists('getModel'))
|
||||
{
|
||||
$config = getModel('member')->getMemberConfig();
|
||||
$work_factor = $config->password_hashing_work_factor;
|
||||
if (!$work_factor || $work_factor < 4 || $work_factor > 31)
|
||||
{
|
||||
$work_factor = 9;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$work_factor = 9;
|
||||
}
|
||||
|
||||
return $work_factor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a reasonably strong random password.
|
||||
*
|
||||
* @param int $length
|
||||
* @return string
|
||||
*/
|
||||
public static function getRandomPassword($length = 16)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
$source = base64_encode(Security::getRandom(64, 'binary'));
|
||||
$source = strtr($source, 'iIoOjl10/', '@#$%&*-!?');
|
||||
$source_length = strlen($source);
|
||||
for($i = 0; $i < $source_length - $length; $i++)
|
||||
{
|
||||
$candidate = substr($source, $i, $length);
|
||||
if(preg_match('/[a-z]/', $candidate) && preg_match('/[A-Z]/', $candidate) &&
|
||||
preg_match('/[0-9]/', $candidate) && preg_match('/[^a-zA-Z0-9]/', $candidate))
|
||||
{
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password.
|
||||
*
|
||||
* To use multiple algorithms in series, provide them as an array.
|
||||
* Salted algorithms such as bcrypt, pbkdf2, or portable must be used last.
|
||||
* On error, false will be returned.
|
||||
*
|
||||
* @param string $password
|
||||
* @param string|array $algos (optional)
|
||||
* @param string $salt (optional)
|
||||
* @return string|false
|
||||
*/
|
||||
public static function hashPassword($password, $algos = null, $salt = null)
|
||||
{
|
||||
// If the algorithm is null, use the default algorithm.
|
||||
if ($algos === null)
|
||||
{
|
||||
$algos = self::getDefaultAlgorithm();
|
||||
}
|
||||
|
||||
// Initialize the chain of hashes.
|
||||
$algos = array_map('strtolower', array_map('trim', is_array($algos) ? $algos : explode(',', $algos)));
|
||||
$hashchain = preg_replace('/\\s+/', ' ', trim($password));
|
||||
|
||||
// Apply the given algorithms one by one.
|
||||
foreach ($algos as $algo)
|
||||
{
|
||||
switch ($algo)
|
||||
{
|
||||
// bcrypt (must be used last)
|
||||
case 'bcrypt':
|
||||
$hashchain = self::bcrypt($hashchain, $salt, self::getWorkFactor());
|
||||
if ($hashchain[0] === '*') return false;
|
||||
return $hashchain;
|
||||
|
||||
// PBKDF2 (must be used last)
|
||||
case 'pbkdf2':
|
||||
if ($salt === null)
|
||||
{
|
||||
$salt = Security::getRandom(12, 'alnum');
|
||||
$hash_algorithm = 'sha512';
|
||||
$iterations = pow(2, self::getWorkFactor() + 5);
|
||||
$key_length = 24;
|
||||
}
|
||||
else
|
||||
{
|
||||
$parts = explode(':', $salt);
|
||||
$salt = $parts[2];
|
||||
$hash_algorithm = $parts[0];
|
||||
$iterations = $parts[1];
|
||||
$key_length = strlen(base64_decode($parts[3]));
|
||||
}
|
||||
return self::pbkdf2($hashchain, $salt, $hash_algorithm, $iterations, $key_length);
|
||||
|
||||
// phpass portable algorithm (must be used last)
|
||||
case 'portable':
|
||||
$phpass = new \Hautelook\Phpass\PasswordHash(self::getWorkFactor(), true);
|
||||
if ($salt === null)
|
||||
{
|
||||
$hashchain = $phpass->HashPassword($hashchain);
|
||||
return $hashchain;
|
||||
}
|
||||
else
|
||||
{
|
||||
$match = $phpass->CheckPassword($hashchain, $salt);
|
||||
return $match ? $salt : false;
|
||||
}
|
||||
|
||||
// Drupal's SHA-512 based algorithm (must be used last)
|
||||
case 'drupal':
|
||||
$hashchain = \VendorPass::drupal($password, $salt);
|
||||
return $hashchain;
|
||||
|
||||
// Joomla's MD5 based algorithm (must be used last)
|
||||
case 'joomla':
|
||||
$hashchain = \VendorPass::joomla($password, $salt);
|
||||
return $hashchain;
|
||||
|
||||
// KimsQ Rb algorithms (must be used last)
|
||||
case 'kimsqrb':
|
||||
$hashchain = \VendorPass::kimsqrb($password, $salt);
|
||||
return $hashchain;
|
||||
|
||||
// crypt() function (must be used last)
|
||||
case 'crypt':
|
||||
if ($salt === null) $salt = Security::getRandom(2, 'alnum');
|
||||
$hashchain = crypt($hashchain, $salt);
|
||||
return $hashchain;
|
||||
|
||||
// MS SQL's PWDENCRYPT() function (must be used last)
|
||||
case 'mssql_pwdencrypt':
|
||||
$hashchain = \VendorPass::mssql_pwdencrypt($hashchain, $salt);
|
||||
return $hashchain;
|
||||
|
||||
// MySQL's old PASSWORD() function.
|
||||
case 'mysql_old_password':
|
||||
$hashchain = \VendorPass::mysql_old_password($hashchain);
|
||||
break;
|
||||
|
||||
// MySQL's new PASSWORD() function.
|
||||
case 'mysql_new_password':
|
||||
$hashchain = \VendorPass::mysql_new_password($hashchain);
|
||||
break;
|
||||
|
||||
// A dummy algorithm that does nothing.
|
||||
case 'null':
|
||||
break;
|
||||
|
||||
// All other algorithms will be passed to hash() or treated as a function name.
|
||||
default:
|
||||
if (isset(self::$_algorithm_callbacks[$algo]))
|
||||
{
|
||||
$callback = self::$_algorithm_callbacks[$algo];
|
||||
$hashchain = $callback($hashchain, $salt);
|
||||
}
|
||||
elseif (in_array($algo, hash_algos()))
|
||||
{
|
||||
$hashchain = hash($algo, $hashchain);
|
||||
}
|
||||
elseif (function_exists($algo))
|
||||
{
|
||||
$hashchain = $algo($hashchain, $salt);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $hashchain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a password against a hash.
|
||||
*
|
||||
* This method returns true if the password is correct, and false otherwise.
|
||||
* If the algorithm is not specified, it will be guessed from the format of the hash.
|
||||
*
|
||||
* @param string $password
|
||||
* @param string $hash
|
||||
* @param array|string $algos
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkPassword($password, $hash, $algos = null)
|
||||
{
|
||||
if ($algos === null)
|
||||
{
|
||||
$algos = self::checkAlgorithm($hash);
|
||||
foreach ($algos as $algo)
|
||||
{
|
||||
if (Security::compareStrings($hash, self::hashPassword($password, $algo, $hash)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Security::compareStrings($hash, self::hashPassword($password, $algos, $hash));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess which algorithm(s) were used to generate the given hash.
|
||||
*
|
||||
* If there are multiple possibilities, all of them will be returned in an array.
|
||||
*
|
||||
* @param string $hash
|
||||
* @return array
|
||||
*/
|
||||
public static function checkAlgorithm($hash)
|
||||
{
|
||||
$candidates = array();
|
||||
foreach (self::$_algorithm_signatures as $name => $signature)
|
||||
{
|
||||
if (preg_match($signature, $hash)) $candidates[] = $name;
|
||||
}
|
||||
return $candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the work factor of a hash.
|
||||
*
|
||||
* @param string $hash
|
||||
* @return int
|
||||
*/
|
||||
public static function checkWorkFactor($hash)
|
||||
{
|
||||
if(preg_match('/^\$2[axy]\$([0-9]{2})\$/', $hash, $matches))
|
||||
{
|
||||
return intval($matches[1], 10);
|
||||
}
|
||||
elseif(preg_match('/^sha[0-9]+:([0-9]+):/', $hash, $matches))
|
||||
{
|
||||
return max(0, round(log($matches[1], 2)) - 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the bcrypt hash of a string.
|
||||
*
|
||||
* @param string $password
|
||||
* @param string $salt (optional)
|
||||
* @param int $work_factor (optional)
|
||||
* @return string
|
||||
*/
|
||||
public static function bcrypt($password, $salt = null, $work_factor = 10)
|
||||
{
|
||||
if ($salt === null)
|
||||
{
|
||||
$salt = '$2y$' . sprintf('%02d', $work_factor) . '$' . Security::getRandom(22, 'alnum');
|
||||
}
|
||||
|
||||
return crypt($password, $salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the PBKDF2 hash of a string.
|
||||
*
|
||||
* @param string $password
|
||||
* @param string $salt (optional)
|
||||
* @param string $algorithm (optional)
|
||||
* @param int $iterations (optional)
|
||||
* @param int $length (optional)
|
||||
* @return string
|
||||
*/
|
||||
public static function pbkdf2($password, $salt = null, $algorithm = 'sha512', $iterations = 16384, $length = 24)
|
||||
{
|
||||
if ($salt === null)
|
||||
{
|
||||
$salt = Security::getRandom(12, 'alnum');
|
||||
}
|
||||
|
||||
if (function_exists('hash_pbkdf2'))
|
||||
{
|
||||
$hash = hash_pbkdf2($algorithm, $password, $salt, $iterations, $length, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$output = '';
|
||||
$block_count = ceil($length / strlen(hash($algorithm, '', true))); // key length divided by the length of one hash
|
||||
for ($i = 1; $i <= $block_count; $i++)
|
||||
{
|
||||
$last = $salt . pack('N', $i); // $i encoded as 4 bytes, big endian
|
||||
$last = $xorsum = hash_hmac($algorithm, $last, $password, true); // first iteration
|
||||
for ($j = 1; $j < $iterations; $j++) // The other $count - 1 iterations
|
||||
{
|
||||
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
|
||||
}
|
||||
$output .= $xorsum;
|
||||
}
|
||||
$hash = substr($output, 0, $length);
|
||||
}
|
||||
|
||||
return $algorithm . ':' . sprintf('%07d', $iterations) . ':' . $salt . ':' . base64_encode($hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the amount of entropy that a password contains.
|
||||
*
|
||||
* @param string $password
|
||||
* @return int
|
||||
*/
|
||||
public static function countEntropyBits($password)
|
||||
{
|
||||
// An empty string has no entropy.
|
||||
|
||||
if ($password === '') return 0;
|
||||
|
||||
// Common character sets and the number of possible mutations.
|
||||
|
||||
static $entropy_per_char = array(
|
||||
'/^[0-9]+$/' => 10,
|
||||
'/^[a-z]+$/' => 26,
|
||||
'/^[A-Z]+$/' => 26,
|
||||
'/^[a-z0-9]+$/' => 36,
|
||||
'/^[A-Z0-9]+$/' => 36,
|
||||
'/^[a-zA-Z]+$/' => 52,
|
||||
'/^[a-zA-Z0-9]+$/' => 62,
|
||||
'/^[a-zA-Z0-9_-]+$/' => 64,
|
||||
'/^[\\x20-\\x7e]+$/' => 95,
|
||||
'/^[\\x00-\\x7f]+$/' => 128,
|
||||
);
|
||||
|
||||
foreach ($entropy_per_char as $regex => $entropy)
|
||||
{
|
||||
if (preg_match($regex, $password))
|
||||
{
|
||||
return log(pow($entropy, strlen($password)), 2);
|
||||
}
|
||||
}
|
||||
|
||||
return strlen($password) * 8;
|
||||
}
|
||||
}
|
||||
339
common/framework/security.php
Normal file
339
common/framework/security.php
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework;
|
||||
|
||||
/**
|
||||
* The security class.
|
||||
*/
|
||||
class Security
|
||||
{
|
||||
/**
|
||||
* Sanitize a variable.
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $type
|
||||
* @return string|false
|
||||
*/
|
||||
public static function sanitize($input, $type)
|
||||
{
|
||||
switch ($type)
|
||||
{
|
||||
// Escape HTML special characters.
|
||||
case 'escape':
|
||||
if (!utf8_check($input)) return false;
|
||||
return escape($input);
|
||||
|
||||
// Strip all HTML tags.
|
||||
case 'strip':
|
||||
if (!utf8_check($input)) return false;
|
||||
return escape(strip_tags($input));
|
||||
|
||||
// Clean up HTML content to prevent XSS attacks.
|
||||
case 'html':
|
||||
if (!utf8_check($input)) return false;
|
||||
return Filters\HTMLFilter::clean($input);
|
||||
|
||||
// Clean up the input to be used as a safe filename.
|
||||
case 'filename':
|
||||
if (!utf8_check($input)) return false;
|
||||
return Filters\FilenameFilter::clean($input);
|
||||
|
||||
// Unknown filters return false.
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a string using AES.
|
||||
*
|
||||
* @param string $plaintext
|
||||
* @param string $key (optional)
|
||||
* @param bool $force_compat (optional)
|
||||
* @return string|false
|
||||
*/
|
||||
public static function encrypt($plaintext, $key = null, $force_compat = false)
|
||||
{
|
||||
// Get the encryption key.
|
||||
$key = $key ?: config('crypto.encryption_key');
|
||||
$key = substr(hash('sha256', $key, true), 0, 16);
|
||||
|
||||
// Use defuse/php-encryption if possible.
|
||||
if (!$force_compat && version_compare(\PHP_VERSION, '5.4.0', '>=') && function_exists('openssl_encrypt'))
|
||||
{
|
||||
try
|
||||
{
|
||||
return base64_encode(\Crypto::Encrypt($plaintext, $key));
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, use the CryptoCompat class.
|
||||
return base64_encode(\CryptoCompat::encrypt($plaintext, $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a string using AES.
|
||||
*
|
||||
* @param string $plaintext
|
||||
* @param string $key (optional)
|
||||
* @param bool $force_compat (optional)
|
||||
* @return string|false
|
||||
*/
|
||||
public static function decrypt($ciphertext, $key = null, $force_compat = false)
|
||||
{
|
||||
// Get the encryption key.
|
||||
$key = $key ?: config('crypto.encryption_key');
|
||||
$key = substr(hash('sha256', $key, true), 0, 16);
|
||||
|
||||
// Check whether the ciphertext is valid.
|
||||
$ciphertext = @base64_decode($ciphertext);
|
||||
if (strlen($ciphertext) < 48)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use defuse/php-encryption if possible.
|
||||
if (!$force_compat && version_compare(\PHP_VERSION, '5.4.0', '>=') && function_exists('openssl_decrypt'))
|
||||
{
|
||||
try
|
||||
{
|
||||
return \Crypto::Decrypt($ciphertext, $key);
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, use the CryptoCompat class.
|
||||
return \CryptoCompat::decrypt($ciphertext, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a cryptographically secure random string.
|
||||
*
|
||||
* @param int $length
|
||||
* @param string $format
|
||||
* @return string
|
||||
*/
|
||||
public function getRandom($length = 32, $format = 'alnum')
|
||||
{
|
||||
// Find out how many bytes of entropy we really need.
|
||||
switch($format)
|
||||
{
|
||||
case 'binary':
|
||||
$entropy_required_bytes = $length;
|
||||
break;
|
||||
case 'hex':
|
||||
$entropy_required_bytes = ceil($length / 2);
|
||||
break;
|
||||
case 'alnum':
|
||||
case 'printable':
|
||||
default:
|
||||
$entropy_required_bytes = ceil($length * 3 / 4);
|
||||
break;
|
||||
}
|
||||
|
||||
// Cap entropy to 256 bits from any one source, because anything more is meaningless.
|
||||
$entropy_capped_bytes = min(32, $entropy_required_bytes);
|
||||
|
||||
// Find and use the most secure way to generate a random string.
|
||||
$entropy = false;
|
||||
$is_windows = (defined('\PHP_OS') && strtoupper(substr(\PHP_OS, 0, 3)) === 'WIN');
|
||||
if(function_exists('random_bytes')) // PHP 7
|
||||
{
|
||||
$entropy = random_bytes($entropy_capped_bytes);
|
||||
}
|
||||
elseif(function_exists('openssl_random_pseudo_bytes') && (!$is_windows || version_compare(\PHP_VERSION, '5.4', '>=')))
|
||||
{
|
||||
$entropy = openssl_random_pseudo_bytes($entropy_capped_bytes);
|
||||
}
|
||||
elseif(function_exists('mcrypt_create_iv') && (!$is_windows || version_compare(\PHP_VERSION, '5.3.7', '>=')))
|
||||
{
|
||||
$entropy = mcrypt_create_iv($entropy_capped_bytes, \MCRYPT_DEV_URANDOM);
|
||||
}
|
||||
elseif(function_exists('mcrypt_create_iv') && $is_windows)
|
||||
{
|
||||
$entropy = mcrypt_create_iv($entropy_capped_bytes, \MCRYPT_RAND);
|
||||
}
|
||||
elseif(!$is_windows && @is_readable('/dev/urandom'))
|
||||
{
|
||||
$fp = fopen('/dev/urandom', 'rb');
|
||||
if (function_exists('stream_set_read_buffer')) // This function does not exist in HHVM.
|
||||
{
|
||||
stream_set_read_buffer($fp, 0); // Prevent reading several KB of unnecessary data from urandom.
|
||||
}
|
||||
$entropy = fread($fp, $entropy_capped_bytes);
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
// Use built-in source of entropy if an error occurs while using other functions.
|
||||
if($entropy === false || strlen($entropy) < $entropy_capped_bytes)
|
||||
{
|
||||
$entropy = '';
|
||||
for($i = 0; $i < $entropy_capped_bytes; $i += 2)
|
||||
{
|
||||
$entropy .= pack('S', rand(0, 65536) ^ mt_rand(0, 65535));
|
||||
}
|
||||
}
|
||||
|
||||
// Mixing (see RFC 4086 section 5)
|
||||
$output = '';
|
||||
for($i = 0; $i < $entropy_required_bytes; $i += 32)
|
||||
{
|
||||
$output .= hash('sha256', $entropy . $i . rand(), true);
|
||||
}
|
||||
|
||||
// Encode and return the random string.
|
||||
switch($format)
|
||||
{
|
||||
case 'binary':
|
||||
return substr($output, 0, $length);
|
||||
case 'printable':
|
||||
$salt = '';
|
||||
for($i = 0; $i < $length; $i++)
|
||||
{
|
||||
$salt .= chr(33 + (crc32(sha1($i . $output)) % 94));
|
||||
}
|
||||
return $salt;
|
||||
case 'hex':
|
||||
return substr(bin2hex($output), 0, $length);
|
||||
case 'alnum':
|
||||
default:
|
||||
$salt = substr(base64_encode($output), 0, $length);
|
||||
$replacements = chr(rand(65, 90)) . chr(rand(97, 122)) . rand(0, 9);
|
||||
return strtr($salt, '+/=', $replacements);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a cryptographically secure random number between $min and $max.
|
||||
*
|
||||
* @param int $min
|
||||
* @param int $max
|
||||
* @return int
|
||||
*/
|
||||
public static function getRandomNumber($min = 0, $max = 0x7fffffff)
|
||||
{
|
||||
if (function_exists('random_int'))
|
||||
{
|
||||
return random_int($min, $max);
|
||||
}
|
||||
else
|
||||
{
|
||||
$bytes_required = min(4, ceil(log($max - $min, 2) / 8) + 1);
|
||||
$bytes = self::getRandom($bytes_required, 'binary');
|
||||
$offset = abs(hexdec(bin2hex($bytes)) % ($max - $min + 1));
|
||||
return intval($min + $offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random UUID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getRandomUUID()
|
||||
{
|
||||
$randpool = self::getRandom(16, 'binary');
|
||||
$randpool[6] = chr(ord($randpool[6]) & 0x0f | 0x40);
|
||||
$randpool[8] = chr(ord($randpool[8]) & 0x3f | 0x80);
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($randpool), 4));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two strings in constant time.
|
||||
*
|
||||
* @param string $a
|
||||
* @param string $b
|
||||
* @return bool
|
||||
*/
|
||||
public static function compareStrings($a, $b)
|
||||
{
|
||||
$diff = strlen($a) ^ strlen($b);
|
||||
$maxlen = min(strlen($a), strlen($b));
|
||||
for($i = 0; $i < $maxlen; $i++)
|
||||
{
|
||||
$diff |= ord($a[$i]) ^ ord($b[$i]);
|
||||
}
|
||||
return $diff === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current request seems to be a CSRF attack.
|
||||
*
|
||||
* This method returns true if the request seems to be innocent,
|
||||
* and false if it seems to be a CSRF attack.
|
||||
*
|
||||
* @param string $referer (optional)
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkCSRF($referer = null)
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$referer)
|
||||
{
|
||||
$referer = strval($_SERVER['HTTP_REFERER']);
|
||||
if ($referer === '')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return URL::isInternalURL($referer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current request seems to be an XEE attack.
|
||||
*
|
||||
* This method returns true if the request seems to be innocent,
|
||||
* and false if it seems to be an XEE attack.
|
||||
* This is the opposite of XE's Security::detectXEE() method.
|
||||
*
|
||||
* @param string $xml (optional)
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkXEE($xml = null)
|
||||
{
|
||||
// Stop if there is no XML content.
|
||||
if (!$xml)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reject entity tags.
|
||||
if (strpos($xml, '<!ENTITY') !== false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there is no content after the xml tag.
|
||||
$header = preg_replace('/<\?xml.*?\?'.'>/s', '', substr($xml, 0, 100), 1);
|
||||
if (($xml = trim(substr_replace($xml, $header, 0, 100))) === '')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there is no content after the DTD.
|
||||
$header = preg_replace('/^<!DOCTYPE[^>]*+>/i', '', substr($xml, 0, 200), 1);
|
||||
if (($xml = trim(substr_replace($xml, $header, 0, 200))) === '')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the root tag is valid.
|
||||
if (!preg_match('/^<(methodCall|methodResponse|fault)/', $xml))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
165
common/framework/url.php
Normal file
165
common/framework/url.php
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework;
|
||||
|
||||
/**
|
||||
* The URL class.
|
||||
*/
|
||||
class URL
|
||||
{
|
||||
/**
|
||||
* Get the current URL.
|
||||
*
|
||||
* If $changes are given, they will be appended to the current URL as a query string.
|
||||
* To delete an existing query string, set its value to null.
|
||||
*
|
||||
* @param array $changes
|
||||
* @return string
|
||||
*/
|
||||
public static function getCurrentURL(array $changes = array())
|
||||
{
|
||||
$proto = \RX_SSL ? 'https://' : 'http://';
|
||||
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
|
||||
$local = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
|
||||
$url = $proto . $host . $local;
|
||||
if (count($changes))
|
||||
{
|
||||
return self::modifyURL($url, $changes);
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::getCanonicalURL($url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a URL to its canonical format.
|
||||
*
|
||||
* @param string $url
|
||||
* @return string
|
||||
*/
|
||||
public static function getCanonicalURL($url)
|
||||
{
|
||||
if (preg_match('#^\.?/([^/]|$)#', $url) || !preg_match('#^(https?:|/)#', $url))
|
||||
{
|
||||
$proto = \RX_SSL ? 'https://' : 'http://';
|
||||
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
|
||||
$url = $proto . $host . \RX_BASEURL . ltrim($url, './');
|
||||
}
|
||||
return preg_replace_callback('#^(https?:|)//([^/]+)#i', function($matches) {
|
||||
if ($matches[1] === '') $matches[1] = \RX_SSL ? 'https:' : 'http:';
|
||||
return $matches[1] . '//' . self::decodeIdna($matches[2]);
|
||||
}, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the domain from a URL.
|
||||
*
|
||||
* @param string $url
|
||||
* @return string|false
|
||||
*/
|
||||
public static function getDomainFromURL($url)
|
||||
{
|
||||
$domain = @parse_url($url, \PHP_URL_HOST);
|
||||
if ($domain === false || $domain === null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::decodeIdna($domain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a URL is internal to this site.
|
||||
*
|
||||
* @param string $url
|
||||
* @return bool
|
||||
*/
|
||||
public static function isInternalURL($url)
|
||||
{
|
||||
$domain = self::getDomainFromURL($url);
|
||||
if ($domain === false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($domain === self::getDomainFromURL('http://' . $_SERVER['HTTP_HOST']))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($domain === self::getDomainFromURL(\Context::getDefaultUrl()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify a URL.
|
||||
*
|
||||
* If $changes are given, they will be appended to the current URL as a query string.
|
||||
* To delete an existing query string, set its value to null.
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $changes
|
||||
* @return string
|
||||
*/
|
||||
public static function modifyURL($url, array $changes = array())
|
||||
{
|
||||
$url = parse_url(self::getCanonicalURL($url));
|
||||
$prefix = sprintf('%s://%s%s%s', $url['scheme'], $url['host'], ($url['port'] ? (':' . $url['port']) : ''), $url['path']);
|
||||
parse_str($url['query'], $args);
|
||||
$changes = array_merge($args, $changes);
|
||||
$changes = array_filter($changes, function($val) { return $val !== null; });
|
||||
if (count($changes))
|
||||
{
|
||||
return $prefix . '?' . http_build_query($changes);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $prefix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode UTF-8 domain into IDNA (punycode)
|
||||
*
|
||||
* @param string $domain
|
||||
* @return string
|
||||
*/
|
||||
public static function encodeIdna($domain)
|
||||
{
|
||||
if (function_exists('idn_to_ascii'))
|
||||
{
|
||||
return idn_to_ascii($domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
$encoder = new \TrueBV\Punycode();
|
||||
return $encoder->encode($domain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert IDNA (punycode) domain into UTF-8
|
||||
*
|
||||
* @param string $domain
|
||||
* @return string
|
||||
*/
|
||||
public static function decodeIdna($domain)
|
||||
{
|
||||
if (function_exists('idn_to_utf8'))
|
||||
{
|
||||
return idn_to_utf8($domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
$decoder = new \TrueBV\Punycode();
|
||||
return $decoder->decode($domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -407,9 +407,10 @@ function getFullSiteUrl()
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
function getCurrentPageUrl()
|
||||
function getCurrentPageUrl($escape = true)
|
||||
{
|
||||
return escape((RX_SSL ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
|
||||
$url = Rhymix\Framework\URL::getCurrentURL();
|
||||
return $escape ? escape($url) : $url;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -813,197 +814,70 @@ function url_decode($str)
|
|||
return htmlspecialchars(utf8RawUrlDecode($str), null, 'UTF-8');
|
||||
}
|
||||
|
||||
function purifierHtml(&$content)
|
||||
{
|
||||
$oPurifier = Purifier::getInstance();
|
||||
$oPurifier->purify($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-block the codes which may be hacking attempts
|
||||
* Sanitize HTML content.
|
||||
*
|
||||
* @param string $content Taget content
|
||||
* @param string $content Target content
|
||||
* @return string
|
||||
*/
|
||||
function removeHackTag($content)
|
||||
{
|
||||
$oEmbedFilter = EmbedFilter::getInstance();
|
||||
$oEmbedFilter->check($content);
|
||||
|
||||
purifierHtml($content);
|
||||
|
||||
// change the specific tags to the common texts
|
||||
$content = preg_replace('@<(\/?(?:html|body|head|title|meta|base|link|script|style|applet)(/*).*?>)@i', '<$1', $content);
|
||||
|
||||
/**
|
||||
* Remove codes to abuse the admin session in src by tags of imaages and video postings
|
||||
* - Issue reported by Sangwon Kim
|
||||
*/
|
||||
$content = preg_replace_callback('@<(/?)([a-z]+[0-9]?)((?>"[^"]*"|\'[^\']*\'|[^>])*?\b(?:on[a-z]+|data|style|background|href|(?:dyn|low)?src)\s*=[\s\S]*?)(/?)($|>|<)@i', 'removeSrcHack', $content);
|
||||
|
||||
$content = checkXmpTag($content);
|
||||
$content = blockWidgetCode($content);
|
||||
|
||||
return $content;
|
||||
return Rhymix\Framework\Filters\HTMLFilter::clean($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* blocking widget code
|
||||
* HTMLPurifier wrapper (Deprecated)
|
||||
*
|
||||
* @param string $content Taget content
|
||||
* @param string &$content Target content
|
||||
* @return string
|
||||
**/
|
||||
function blockWidgetCode($content)
|
||||
{
|
||||
$content = preg_replace('/(<(?:img|div)(?:[^>]*))(widget)(?:(=([^>]*?)>))/is', '$1blocked-widget$3', $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* check uploaded file which may be hacking attempts
|
||||
*
|
||||
* @param string $file Taget file path
|
||||
* @return bool
|
||||
*/
|
||||
function checkUploadedFile($file)
|
||||
function purifierHtml(&$content)
|
||||
{
|
||||
return UploadFileFilter::check($file);
|
||||
$content = Rhymix\Framework\Filters\HTMLFilter::clean($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check xmp tag, close it.
|
||||
* Check xmp tag (Deprecated)
|
||||
*
|
||||
* @param string $content Target content
|
||||
* @return string
|
||||
*/
|
||||
function checkXmpTag($content)
|
||||
{
|
||||
$content = preg_replace('@<(/?)xmp.*?>@i', '<\1xmp>', $content);
|
||||
|
||||
if(($start_xmp = strrpos($content, '<xmp>')) !== FALSE)
|
||||
{
|
||||
if(($close_xmp = strrpos($content, '</xmp>')) === FALSE)
|
||||
{
|
||||
$content .= '</xmp>';
|
||||
}
|
||||
else if($close_xmp < $start_xmp)
|
||||
{
|
||||
$content .= '</xmp>';
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove src hack(preg_replace_callback)
|
||||
* Block widget code (Deprecated)
|
||||
*
|
||||
* @param string $content Taget content
|
||||
* @return string
|
||||
**/
|
||||
function blockWidgetCode($content)
|
||||
{
|
||||
return preg_replace('/(<(?:img|div)(?:[^>]*))(widget)(?:(=([^>]*?)>))/is', '$1blocked-widget$3', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove src hack (Deprecated)
|
||||
*
|
||||
* @param array $match
|
||||
* @return string
|
||||
*/
|
||||
function removeSrcHack($match)
|
||||
{
|
||||
$tag = strtolower($match[2]);
|
||||
return $match[0];
|
||||
}
|
||||
|
||||
// xmp tag ?뺣━
|
||||
if($tag == 'xmp')
|
||||
{
|
||||
return "<{$match[1]}xmp>";
|
||||
}
|
||||
if($match[1])
|
||||
{
|
||||
return $match[0];
|
||||
}
|
||||
if($match[4])
|
||||
{
|
||||
$match[4] = ' ' . $match[4];
|
||||
}
|
||||
|
||||
$attrs = array();
|
||||
if(preg_match_all('/([\w:-]+)\s*=(?:\s*(["\']))?(?(2)(.*?)\2|([^ ]+))/s', $match[3], $m))
|
||||
{
|
||||
foreach($m[1] as $idx => $name)
|
||||
{
|
||||
if(strlen($name) >= 2 && substr_compare($name, 'on', 0, 2) === 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$val = preg_replace_callback('/&#(?:x([a-fA-F0-9]+)|0*(\d+));/', function($n) {return chr($n[1] ? ('0x00' . $n[1]) : ($n[2] + 0)); }, $m[3][$idx] . $m[4][$idx]);
|
||||
$val = preg_replace('/^\s+|[\t\n\r]+/', '', $val);
|
||||
|
||||
if(preg_match('/^[a-z]+script:/i', $val))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$attrs[$name] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
//Remove ACT URL (CSRF)
|
||||
$except_act = array('procFileDownload');
|
||||
$block_act = array('dispMemberLogout', 'dispLayoutPreview');
|
||||
|
||||
$filter_arrts = array('style', 'src', 'href');
|
||||
if($tag === 'object') array_push($filter_arrts, 'data');
|
||||
if($tag === 'param') array_push($filter_arrts, 'value');
|
||||
|
||||
foreach($filter_arrts as $attr)
|
||||
{
|
||||
if(!isset($attrs[$attr])) continue;
|
||||
|
||||
$attr_value = rawurldecode($attrs[$attr]);
|
||||
$attr_value = htmlspecialchars_decode($attr_value, ENT_COMPAT);
|
||||
$attr_value = preg_replace('/\s+|[\t\n\r]+/', '', $attr_value);
|
||||
|
||||
preg_match('@(\?|&|;)act=(disp|proc)([^&]*)@i', $attr_value, $actmatch);
|
||||
$url_action = $actmatch[2].$actmatch[3];
|
||||
|
||||
if(!empty($url_action) && !in_array($url_action, $except_act))
|
||||
{
|
||||
if($actmatch[2] == 'proc' || in_array($url_action, $block_act))
|
||||
{
|
||||
unset($attrs[$attr]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($attrs['style']) && preg_match('@(?:/\*|\*/|\n|:\s*expression\s*\()@i', $attrs['style']))
|
||||
{
|
||||
unset($attrs['style']);
|
||||
}
|
||||
|
||||
$attr = array();
|
||||
foreach($attrs as $name => $val)
|
||||
{
|
||||
if($tag == 'object' || $tag == 'embed' || $tag == 'a')
|
||||
{
|
||||
$attribute = strtolower(trim($name));
|
||||
if($attribute == 'data' || $attribute == 'src' || $attribute == 'href')
|
||||
{
|
||||
if(stripos($val, 'data:') === 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($tag == 'img')
|
||||
{
|
||||
$attribute = strtolower(trim($name));
|
||||
if(stripos($val, 'data:') === 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$val = str_replace('"', '"', $val);
|
||||
$attr[] = $name . "=\"{$val}\"";
|
||||
}
|
||||
$attr = count($attr) ? ' ' . implode(' ', $attr) : '';
|
||||
|
||||
return "<{$match[1]}{$tag}{$attr}{$match[4]}>";
|
||||
/**
|
||||
* Check uploaded file (Deprecated)
|
||||
*
|
||||
* @param string $file Taget file path
|
||||
* @return bool
|
||||
*/
|
||||
function checkUploadedFile($file)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1050,7 +924,7 @@ function getScriptPath()
|
|||
*/
|
||||
function getRequestUriByServerEnviroment()
|
||||
{
|
||||
return str_replace('<', '<', $_SERVER['REQUEST_URI']);
|
||||
return escape($_SERVER['REQUEST_URI']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1138,29 +1012,31 @@ function isCrawler($agent = NULL)
|
|||
*/
|
||||
function stripEmbedTagForAdmin(&$content, $writer_member_srl)
|
||||
{
|
||||
if(!Context::get('is_logged'))
|
||||
if (!Context::get('is_logged'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$oModuleModel = getModel('module');
|
||||
|
||||
$logged_info = Context::get('logged_info');
|
||||
|
||||
if($writer_member_srl != $logged_info->member_srl && ($logged_info->is_admin == "Y" || $oModuleModel->isSiteAdmin($logged_info)))
|
||||
if ($logged_info->member_srl == $writer_member_srl)
|
||||
{
|
||||
if($writer_member_srl)
|
||||
return;
|
||||
}
|
||||
|
||||
if ($logged_info->is_admin === 'Y' || getModel('module')->isSiteAdmin($logged_info))
|
||||
{
|
||||
if ($writer_member_srl)
|
||||
{
|
||||
$oMemberModel = getModel('member');
|
||||
$member_info = $oMemberModel->getMemberInfoByMemberSrl($writer_member_srl);
|
||||
if($member_info->is_admin == "Y")
|
||||
$member_info = getModel('member')->getMemberInfoByMemberSrl($writer_member_srl);
|
||||
if ($member_info && $member_info->is_admin === 'Y')
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
$security_msg = "<div style='border: 1px solid #DDD; background: #FAFAFA; text-align:center; margin: 1em 0;'><p style='margin: 1em;'>" . lang('security_warning_embed') . "</p></div>";
|
||||
$content = preg_replace('/<object[^>]+>(.*?<\/object>)?/is', $security_msg, $content);
|
||||
$content = preg_replace('/<embed[^>]+>(\s*<\/embed>)?/is', $security_msg, $content);
|
||||
$content = preg_replace('/<img[^>]+editor_component="multimedia_link"[^>]*>(\s*<\/img>)?/is', $security_msg, $content);
|
||||
|
||||
$security_msg = '<div style="border: 1px solid #DDD; background: #FAFAFA; text-align:center; margin: 1em 0;">' .
|
||||
'<p style="margin: 1em;">' . lang('security_warning_embed') . '</p></div>';
|
||||
$content = Rhymix\Framework\Filters\MediaFilter::removeEmbeddedMedia($content, $security_msg);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -1183,42 +1059,8 @@ function requirePear()
|
|||
*/
|
||||
function checkCSRF()
|
||||
{
|
||||
// If this is not a POST request, FAIL.
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'POST')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the referer. If the referer is empty, PASS.
|
||||
$referer = strval($_SERVER['HTTP_REFERER']);
|
||||
if ($referer === '')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (strpos($referer, 'xn--') !== false)
|
||||
{
|
||||
$referer = Context::decodeIdna($referer);
|
||||
}
|
||||
$referer_host = parse_url($referer, PHP_URL_HOST);
|
||||
|
||||
// If the referer is the same domain as the current host, PASS.
|
||||
$current_host = $_SERVER['HTTP_HOST'];
|
||||
if (strpos($current_host, 'xn--') !== false)
|
||||
{
|
||||
$current_host = Context::decodeIdna($current_host);
|
||||
}
|
||||
if ($referer_host === $current_host)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the referer is the same domain as the default URL, PASS.
|
||||
$default_url = Context::getDefaultUrl();
|
||||
if (strpos($default_url, 'xn--') !== false)
|
||||
{
|
||||
$default_url = Context::decodeIdna($default_url);
|
||||
}
|
||||
if ($referer_host === parse_url($default_url, PHP_URL_HOST))
|
||||
// Use Rhymix Security class first.
|
||||
if (Rhymix\Framework\Security::checkCSRF())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class CryptoCompat
|
|||
// Validate MAC
|
||||
$mac_key = self::_defuseCompatibleHKDF($key, self::ENCRYPTION_MAC_INFO);
|
||||
$mac_compare = hash_hmac(self::ENCRYPTION_MAC_ALGO, ($iv . $ciphertext), $mac_key, true);
|
||||
if (!Password::strcmpConstantTime($mac, $mac_compare))
|
||||
if (!Rhymix\Framework\Security::compareStrings($mac, $mac_compare))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ class CryptoCompat
|
|||
*/
|
||||
protected static function _createIV()
|
||||
{
|
||||
return hex2bin(Password::createSecureSalt(self::ENCRYPTION_BLOCK_SIZE * 2, 'hex'));
|
||||
return Rhymix\Framework\Security::getRandom(self::ENCRYPTION_BLOCK_SIZE, 'binary');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class VendorPass
|
|||
else
|
||||
{
|
||||
$iterations = 15;
|
||||
$salt = Password::createSecureSalt(8, 'hex');
|
||||
$salt = Rhymix\Framework\Security::getRandom(8, 'hex');
|
||||
}
|
||||
$count = 1 << $iterations;
|
||||
$hash = hash('sha512', $salt . $password, true);
|
||||
|
|
@ -104,7 +104,7 @@ class VendorPass
|
|||
}
|
||||
else
|
||||
{
|
||||
$salt = Password::createSecureSalt(32, 'hex');
|
||||
$salt = Rhymix\Framework\Security::getRandom(32, 'hex');
|
||||
}
|
||||
return md5($password . $salt) . ':' . $salt;
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ class VendorPass
|
|||
{
|
||||
if (!isset($options['salt']) || !preg_match('/^[0-9a-zA-Z\.\/]{22,}$/', $options['salt']))
|
||||
{
|
||||
$options['salt'] = Password::createSecureSalt(22, 'alnum');
|
||||
$options['salt'] = Rhymix\Framework\Security::getRandom(22, 'alnum');
|
||||
}
|
||||
if (!isset($options['cost']) || $options['cost'] < 4 || $options['cost'] > 31)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
{ "name": "NAVER", "email": "developers@xpressengine.com" }
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"php": ">=5.3.3",
|
||||
"ext-curl": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-iconv": "*",
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
"defuse/php-encryption": "1.2.1",
|
||||
"ezyang/htmlpurifier": "4.7.*",
|
||||
"firephp/firephp-core": "0.4.0",
|
||||
"hautelook/phpass": "0.3.*",
|
||||
"matthiasmullie/minify": "1.3.*",
|
||||
"michelf/php-markdown": "1.5.*",
|
||||
"rmccue/requests": "1.6.*",
|
||||
|
|
|
|||
67
composer.lock
generated
67
composer.lock
generated
|
|
@ -4,8 +4,8 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "a35061b94658a2dbdafe943dddcdc898",
|
||||
"content-hash": "1a71937e1cbc600f8a237c6445d6a69f",
|
||||
"hash": "9b062f27815a9b2ef1e700b4664ee864",
|
||||
"content-hash": "69345a9112733f99c09fa298ba72fa22",
|
||||
"packages": [
|
||||
{
|
||||
"name": "defuse/php-encryption",
|
||||
|
|
@ -133,17 +133,61 @@
|
|||
"time": "2013-04-23 15:28:20"
|
||||
},
|
||||
{
|
||||
"name": "matthiasmullie/minify",
|
||||
"version": "1.3.32",
|
||||
"name": "hautelook/phpass",
|
||||
"version": "0.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matthiasmullie/minify.git",
|
||||
"reference": "140c714688908afcecde87338c8309233bdc2519"
|
||||
"url": "https://github.com/hautelook/phpass.git",
|
||||
"reference": "f0217d804225822f9bdb0d392839029b0fcb0914"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/140c714688908afcecde87338c8309233bdc2519",
|
||||
"reference": "140c714688908afcecde87338c8309233bdc2519",
|
||||
"url": "https://api.github.com/repos/hautelook/phpass/zipball/f0217d804225822f9bdb0d392839029b0fcb0914",
|
||||
"reference": "f0217d804225822f9bdb0d392839029b0fcb0914",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Hautelook": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Public Domain"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Solar Designer",
|
||||
"email": "solar@openwall.com",
|
||||
"homepage": "http://openwall.com/phpass/"
|
||||
}
|
||||
],
|
||||
"description": "Portable PHP password hashing framework",
|
||||
"homepage": "http://github.com/hautelook/phpass/",
|
||||
"keywords": [
|
||||
"blowfish",
|
||||
"crypt",
|
||||
"password",
|
||||
"security"
|
||||
],
|
||||
"time": "2012-08-31 00:00:00"
|
||||
},
|
||||
{
|
||||
"name": "matthiasmullie/minify",
|
||||
"version": "1.3.34",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matthiasmullie/minify.git",
|
||||
"reference": "272e46113404f66ced256659552a0cc074a7810f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/272e46113404f66ced256659552a0cc074a7810f",
|
||||
"reference": "272e46113404f66ced256659552a0cc074a7810f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -153,8 +197,7 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"matthiasmullie/scrapbook": "~1.0",
|
||||
"phpunit/phpunit": "~4.8",
|
||||
"satooshi/php-coveralls": "~1.0"
|
||||
"phpunit/phpunit": "~4.8"
|
||||
},
|
||||
"bin": [
|
||||
"bin/minifycss",
|
||||
|
|
@ -187,7 +230,7 @@
|
|||
"minifier",
|
||||
"minify"
|
||||
],
|
||||
"time": "2016-01-11 02:10:11"
|
||||
"time": "2016-03-01 08:00:27"
|
||||
},
|
||||
{
|
||||
"name": "matthiasmullie/path-converter",
|
||||
|
|
@ -489,7 +532,7 @@
|
|||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=5.3.0",
|
||||
"php": ">=5.3.3",
|
||||
"ext-curl": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-iconv": "*",
|
||||
|
|
|
|||
|
|
@ -556,33 +556,38 @@ class adminAdminController extends admin
|
|||
$vars = Context::getRequestVars();
|
||||
|
||||
// iframe filter
|
||||
$embed_iframe = $vars->embedfilter_iframe;
|
||||
$embed_iframe = array_filter(array_map('trim', preg_split('/[\r\n]/', $embed_iframe)), function($item) {
|
||||
$iframe_whitelist = $vars->mediafilter_iframe;
|
||||
$iframe_whitelist = array_filter(array_map('trim', preg_split('/[\r\n]/', $iframe_whitelist)), function($item) {
|
||||
return $item !== '';
|
||||
});
|
||||
$embed_iframe = array_unique(array_map(function($item) {
|
||||
return preg_match('@^https?://(.*)$@i', $item, $matches) ? $matches[1] : $item;
|
||||
}, $embed_iframe));
|
||||
natcasesort($embed_iframe);
|
||||
Rhymix\Framework\Config::set('embedfilter.iframe', array_values($embed_iframe));
|
||||
$iframe_whitelist = array_unique(array_map(function($item) {
|
||||
return Rhymix\Framework\Filters\MediaFilter::formatPrefix($item);
|
||||
}, $iframe_whitelist));
|
||||
natcasesort($iframe_whitelist);
|
||||
Rhymix\Framework\Config::set('mediafilter.iframe', array_values($iframe_whitelist));
|
||||
|
||||
// object filter
|
||||
$embed_object = $vars->embedfilter_object;
|
||||
$embed_object = array_filter(array_map('trim', preg_split('/[\r\n]/', $embed_object)), function($item) {
|
||||
$object_whitelist = $vars->mediafilter_object;
|
||||
$object_whitelist = array_filter(array_map('trim', preg_split('/[\r\n]/', $object_whitelist)), function($item) {
|
||||
return $item !== '';
|
||||
});
|
||||
$embed_object = array_unique(array_map(function($item) {
|
||||
return preg_match('@^https?://(.*)$@i', $item, $matches) ? $matches[1] : $item;
|
||||
}, $embed_object));
|
||||
natcasesort($embed_object);
|
||||
Rhymix\Framework\Config::set('embedfilter.object', array_values($embed_object));
|
||||
$object_whitelist = array_unique(array_map(function($item) {
|
||||
return Rhymix\Framework\Filters\MediaFilter::formatPrefix($item);
|
||||
}, $object_whitelist));
|
||||
natcasesort($object_whitelist);
|
||||
Rhymix\Framework\Config::set('mediafilter.object', array_values($object_whitelist));
|
||||
|
||||
// Remove old embed filter
|
||||
$config = Rhymix\Framework\Config::getAll();
|
||||
unset($config['embedfilter']);
|
||||
Rhymix\Framework\Config::setAll($config);
|
||||
|
||||
// Admin IP access control
|
||||
$allowed_ip = array_map('trim', preg_split('/[\r\n]/', $vars->admin_allowed_ip));
|
||||
$allowed_ip = array_unique(array_filter($allowed_ip, function($item) {
|
||||
return $item !== '';
|
||||
}));
|
||||
if (!IpFilter::validate($whitelist)) {
|
||||
if (!Rhymix\Framework\Filters\IpFilter::validateRanges($allowed_ip)) {
|
||||
return new Object(-1, 'msg_invalid_ip');
|
||||
}
|
||||
|
||||
|
|
@ -590,7 +595,7 @@ class adminAdminController extends admin
|
|||
$denied_ip = array_unique(array_filter($denied_ip, function($item) {
|
||||
return $item !== '';
|
||||
}));
|
||||
if (!IpFilter::validate($whitelist)) {
|
||||
if (!Rhymix\Framework\Filters\IpFilter::validateRanges($denied_ip)) {
|
||||
return new Object(-1, 'msg_invalid_ip');
|
||||
}
|
||||
|
||||
|
|
@ -740,7 +745,7 @@ class adminAdminController extends admin
|
|||
$allowed_ip = array_unique(array_filter($allowed_ip, function($item) {
|
||||
return $item !== '';
|
||||
}));
|
||||
if (!IpFilter::validate($whitelist)) {
|
||||
if (!Rhymix\Framework\Filters\IpFilter::validate($allowed_ip)) {
|
||||
return new Object(-1, 'msg_invalid_ip');
|
||||
}
|
||||
Rhymix\Framework\Config::set('debug.allow', array_values($allowed_ip));
|
||||
|
|
@ -766,30 +771,17 @@ class adminAdminController extends admin
|
|||
|
||||
if ($vars->sitelock_locked === 'Y')
|
||||
{
|
||||
$allowed_localhost = false;
|
||||
$allowed_current = false;
|
||||
foreach ($allowed_ip as $range)
|
||||
{
|
||||
if (Rhymix\Framework\IpFilter::inRange('127.0.0.1', $range))
|
||||
{
|
||||
$allowed_localhost = true;
|
||||
}
|
||||
if (Rhymix\Framework\IpFilter::inRange(RX_CLIENT_IP, $range))
|
||||
{
|
||||
$allowed_current = true;
|
||||
}
|
||||
}
|
||||
if (!$allowed_localhost)
|
||||
if (!Rhymix\Framework\Filters\IpFilter::inRanges('127.0.0.1', $allowed_ip))
|
||||
{
|
||||
array_unshift($allowed_ip, '127.0.0.1');
|
||||
}
|
||||
if (!$allowed_current)
|
||||
if (!Rhymix\Framework\Filters\IpFilter::inRanges(RX_CLIENT_IP, $allowed_ip))
|
||||
{
|
||||
array_unshift($allowed_ip, RX_CLIENT_IP);
|
||||
}
|
||||
}
|
||||
|
||||
if (!IpFilter::validate($whitelist))
|
||||
if (!Rhymix\Framework\Filters\IpFilter::validateRanges($allowed_ip))
|
||||
{
|
||||
return new Object(-1, 'msg_invalid_ip');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -418,9 +418,8 @@ class adminAdminView extends admin
|
|||
function dispAdminConfigSecurity()
|
||||
{
|
||||
// Load embed filter.
|
||||
$oEmbedFilter = EmbedFilter::getInstance();
|
||||
context::set('embedfilter_iframe', implode(PHP_EOL, $oEmbedFilter->whiteIframeUrlList));
|
||||
context::set('embedfilter_object', implode(PHP_EOL, $oEmbedFilter->whiteUrlList));
|
||||
context::set('mediafilter_iframe', implode(PHP_EOL, Rhymix\Framework\Filters\MediaFilter::getIframeWhitelist()));
|
||||
context::set('mediafilter_object', implode(PHP_EOL, Rhymix\Framework\Filters\MediaFilter::getObjectWhitelist()));
|
||||
|
||||
// Admin IP access control
|
||||
$allowed_ip = Rhymix\Framework\Config::get('admin.allow');
|
||||
|
|
@ -519,24 +518,11 @@ class adminAdminView extends admin
|
|||
Context::set('sitelock_message', escape(Rhymix\Framework\Config::get('lock.message')));
|
||||
|
||||
$allowed_ip = Rhymix\Framework\Config::get('lock.allow') ?: array();
|
||||
$allowed_localhost = false;
|
||||
$allowed_current = false;
|
||||
foreach ($allowed_ip as $range)
|
||||
{
|
||||
if (Rhymix\Framework\IpFilter::inRange('127.0.0.1', $range))
|
||||
{
|
||||
$allowed_localhost = true;
|
||||
}
|
||||
if (Rhymix\Framework\IpFilter::inRange(RX_CLIENT_IP, $range))
|
||||
{
|
||||
$allowed_current = true;
|
||||
}
|
||||
}
|
||||
if (!$allowed_localhost)
|
||||
if (!Rhymix\Framework\Filters\IpFilter::inRanges('127.0.0.1', $allowed_ip))
|
||||
{
|
||||
array_unshift($allowed_ip, '127.0.0.1');
|
||||
}
|
||||
if (!$allowed_current)
|
||||
if (!Rhymix\Framework\Filters\IpFilter::inRanges(RX_CLIENT_IP, $allowed_ip))
|
||||
{
|
||||
array_unshift($allowed_ip, RX_CLIENT_IP);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@
|
|||
<input type="hidden" name="act" value="procAdminUpdateSecurity" />
|
||||
<input type="hidden" name="xe_validator_id" value="modules/admin/tpl/config_security/1" />
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label" for="embedfilter_iframe">iframe</label>
|
||||
<label class="x_control-label" for="mediafilter_iframe">iframe</label>
|
||||
<div class="x_controls" style="margin-right:14px">
|
||||
<textarea name="embedfilter_iframe" id="embedfilter_iframe" rows="8" style="width:100%;">{$embedfilter_iframe}</textarea>
|
||||
<textarea name="mediafilter_iframe" id="mediafilter_iframe" rows="8" style="width:100%;">{$mediafilter_iframe}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label" for="embedfilter_object">object/embed</label>
|
||||
<label class="x_control-label" for="mediafilter_object">object/embed</label>
|
||||
<div class="x_controls" style="margin-right:14px">
|
||||
<textarea name="embedfilter_object" id="embedfilter_object" rows="8" style="width:100%;">{$embedfilter_object}</textarea>
|
||||
<textarea name="mediafilter_object" id="mediafilter_object" rows="8" style="width:100%;">{$mediafilter_object}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
|
|
|
|||
|
|
@ -285,8 +285,7 @@ class fileController extends file
|
|||
// Redirect to procFileOutput using file key
|
||||
if(!isset($_SESSION['__XE_FILE_KEY__']) || !is_string($_SESSION['__XE_FILE_KEY__']) || strlen($_SESSION['__XE_FILE_KEY__']) != 32)
|
||||
{
|
||||
$random = new Password();
|
||||
$_SESSION['__XE_FILE_KEY__'] = $random->createSecureSalt(32, 'hex');
|
||||
$_SESSION['__XE_FILE_KEY__'] = Rhymix\Framework\Security::getRandom(32, 'hex');
|
||||
}
|
||||
$file_key_data = $file_obj->file_srl . $file_obj->file_size . $file_obj->uploaded_filename . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'];
|
||||
$file_key = substr(hash_hmac('sha256', $file_key_data, $_SESSION['__XE_FILE_KEY__']), 0, 32);
|
||||
|
|
@ -732,13 +731,8 @@ class fileController extends file
|
|||
}
|
||||
}
|
||||
|
||||
// https://github.com/xpressengine/xe-core/issues/1713
|
||||
$file_info['name'] = preg_replace('/\.(php|phtm|phar|html?|cgi|pl|exe|jsp|asp|inc)/i', '$0-x',$file_info['name']);
|
||||
$file_info['name'] = removeHackTag($file_info['name']);
|
||||
$file_info['name'] = str_replace(array('<','>'),array('%3C','%3E'),$file_info['name']);
|
||||
|
||||
// Get random number generator
|
||||
$random = new Password();
|
||||
// Sanitize filename
|
||||
$file_info['name'] = Rhymix\Framework\Filters\FilenameFilter::clean($file_info['name']);
|
||||
|
||||
// Set upload path by checking if the attachement is an image or other kinds of file
|
||||
if(preg_match("/\.(jpe?g|gif|png|wm[va]|mpe?g|avi|swf|flv|mp[1-4]|as[fx]|wav|midi?|moo?v|qt|r[am]{1,2}|m4v)$/i", $file_info['name']))
|
||||
|
|
@ -749,7 +743,7 @@ class fileController extends file
|
|||
// change to random file name. because window php bug. window php is not recognize unicode character file name - by cherryfilter
|
||||
$ext = substr(strrchr($file_info['name'],'.'),1);
|
||||
//$_filename = preg_replace('/[#$&*?+%"\']/', '_', $file_info['name']);
|
||||
$_filename = $random->createSecureSalt(32, 'hex').'.'.$ext;
|
||||
$_filename = Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext;
|
||||
$filename = $path.$_filename;
|
||||
$idx = 1;
|
||||
while(file_exists($filename))
|
||||
|
|
@ -762,17 +756,12 @@ class fileController extends file
|
|||
else
|
||||
{
|
||||
$path = sprintf("./files/attach/binaries/%s/%s", $module_srl, getNumberingPath($upload_target_srl,3));
|
||||
$filename = $path.$random->createSecureSalt(32, 'hex');
|
||||
$filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex');
|
||||
$direct_download = 'N';
|
||||
}
|
||||
|
||||
// Create a directory
|
||||
if(!FileHandler::makeDir($path)) return new Object(-1,'msg_not_permitted_create');
|
||||
|
||||
// Check uploaded file
|
||||
if(!checkUploadedFile($file_info['tmp_name'])) return new Object(-1,'msg_file_upload_error');
|
||||
|
||||
// Get random number generator
|
||||
$random = new Password();
|
||||
|
||||
// Move the file
|
||||
if($manual_insert)
|
||||
|
|
@ -780,7 +769,7 @@ class fileController extends file
|
|||
@copy($file_info['tmp_name'], $filename);
|
||||
if(!file_exists($filename))
|
||||
{
|
||||
$filename = $path.$random->createSecureSalt(32, 'hex').'.'.$ext;
|
||||
$filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext;
|
||||
@copy($file_info['tmp_name'], $filename);
|
||||
}
|
||||
}
|
||||
|
|
@ -788,7 +777,7 @@ class fileController extends file
|
|||
{
|
||||
if(!@move_uploaded_file($file_info['tmp_name'], $filename))
|
||||
{
|
||||
$filename = $path.$random->createSecureSalt(32, 'hex').'.'.$ext;
|
||||
$filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext;
|
||||
if(!@move_uploaded_file($file_info['tmp_name'], $filename)) return new Object(-1,'msg_file_upload_error');
|
||||
}
|
||||
}
|
||||
|
|
@ -807,7 +796,7 @@ class fileController extends file
|
|||
$args->file_size = @filesize($filename);
|
||||
$args->comment = NULL;
|
||||
$args->member_srl = $member_srl;
|
||||
$args->sid = $random->createSecureSalt(32, 'hex');
|
||||
$args->sid = Rhymix\Framework\Security::getRandom(32, 'hex');
|
||||
|
||||
$output = executeQuery('file.insertFile', $args);
|
||||
if(!$output->toBool()) return $output;
|
||||
|
|
@ -982,13 +971,12 @@ class fileController extends file
|
|||
if(preg_match("/\.(jpg|jpeg|gif|png|wmv|wma|mpg|mpeg|avi|swf|flv|mp1|mp2|mp3|mp4|asf|wav|asx|mid|midi|asf|mov|moov|qt|rm|ram|ra|rmm|m4v)$/i", $file_info->source_filename))
|
||||
{
|
||||
$path = sprintf("./files/attach/images/%s/%s/", $target_module_srl,$target_srl);
|
||||
$new_file = $path.$file_info->source_filename;
|
||||
$new_file = $path . $file_info->source_filename;
|
||||
}
|
||||
else
|
||||
{
|
||||
$path = sprintf("./files/attach/binaries/%s/%s/", $target_module_srl, $target_srl);
|
||||
$random = new Password();
|
||||
$new_file = $path.$random->createSecureSalt(32, 'hex');
|
||||
$new_file = $path . Rhymix\Framework\Security::getRandom(32, 'hex');
|
||||
}
|
||||
// Pass if a target document to move is same
|
||||
if($old_file == $new_file) continue;
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class installView extends install
|
|||
function dispInstallCheckEnv()
|
||||
{
|
||||
// Create a temporary file for mod_rewrite check.
|
||||
self::$rewriteCheckString = Password::createSecureSalt(32);
|
||||
self::$rewriteCheckString = Rhymix\Framework\Security::getRandom(32);
|
||||
FileHandler::writeFile(_XE_PATH_ . self::$rewriteCheckFilePath, self::$rewriteCheckString);;
|
||||
|
||||
// Check if the web server is nginx.
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class integration_searchAdminController extends integration_search
|
|||
continue;
|
||||
}
|
||||
// Ignore if the file is not successfully uploaded, and check uploaded file
|
||||
if(!is_uploaded_file($image_obj['tmp_name']) || !checkUploadedFile($image_obj['tmp_name']))
|
||||
if(!is_uploaded_file($image_obj['tmp_name']))
|
||||
{
|
||||
unset($obj->{$vars->name});
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -435,9 +435,6 @@ class layoutAdminController extends layout
|
|||
$ext = substr(strrchr($filename,'.'),1);
|
||||
$filename = sprintf('%s.%s', md5($filename), $ext);
|
||||
}
|
||||
|
||||
// Check uploaded file
|
||||
if(!checkUploadedFile($source['tmp_name'])) return false;
|
||||
|
||||
if(file_exists($path .'/'. $filename)) @unlink($path . $filename);
|
||||
if(!move_uploaded_file($source['tmp_name'], $path . $filename )) return false;
|
||||
|
|
@ -690,7 +687,7 @@ class layoutAdminController extends layout
|
|||
// check upload
|
||||
if(!Context::isUploaded()) exit();
|
||||
$file = Context::get('file');
|
||||
if(!is_uploaded_file($file['tmp_name']) || !checkUploadedFile($file['tmp_name'])) exit();
|
||||
if(!is_uploaded_file($file['tmp_name'])) exit();
|
||||
|
||||
if(substr_compare($file['name'], '.tar', -4) !== 0) exit();
|
||||
|
||||
|
|
@ -925,7 +922,7 @@ class layoutAdminController extends layout
|
|||
$this->setTemplatePath($this->module_path.'tpl');
|
||||
$this->setTemplateFile("after_upload_config_image.html");
|
||||
|
||||
if(!$img['tmp_name'] || !is_uploaded_file($img['tmp_name']) || !checkUploadedFile($img['tmp_name']))
|
||||
if(!$img['tmp_name'] || !is_uploaded_file($img['tmp_name']))
|
||||
{
|
||||
Context::set('msg', lang('upload failed'));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -166,8 +166,7 @@ class memberAdminController extends member
|
|||
'update_nickname_log'
|
||||
);
|
||||
|
||||
$oPassword = new Password();
|
||||
if(!array_key_exists($args->password_hashing_algorithm, $oPassword->getSupportedAlgorithms()))
|
||||
if(!array_key_exists($args->password_hashing_algorithm, Rhymix\Framework\Password::getSupportedAlgorithms()))
|
||||
{
|
||||
$args->password_hashing_algorithm = 'md5';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -298,26 +298,12 @@ class memberAdminModel extends member
|
|||
{
|
||||
if ($allow_list = ($allow_list === null) ? config('admin.allow') : $allow_list)
|
||||
{
|
||||
foreach ($allow_list as $range)
|
||||
{
|
||||
if (Rhymix\Framework\IpFilter::inRange(RX_CLIENT_IP, $range))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Rhymix\Framework\Filters\IpFilter::inRanges(RX_CLIENT_IP, $allow_list);
|
||||
}
|
||||
|
||||
if ($deny_list = ($deny_list === null) ? config('admin.deny') : $deny_list)
|
||||
{
|
||||
foreach ($deny_list as $range)
|
||||
{
|
||||
if (Rhymix\Framework\IpFilter::inRange(RX_CLIENT_IP, $range))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return !Rhymix\Framework\Filters\IpFilter::inRanges(RX_CLIENT_IP, $deny_list);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -129,8 +129,7 @@ class memberAdminView extends member
|
|||
*/
|
||||
public function dispMemberAdminConfig()
|
||||
{
|
||||
$oPassword = new Password();
|
||||
Context::set('password_hashing_algos', $oPassword->getSupportedAlgorithms());
|
||||
Context::set('password_hashing_algos', Rhymix\Framework\Password::getSupportedAlgorithms());
|
||||
|
||||
$this->setTemplateFile('default_config');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,8 +73,7 @@ class member extends ModuleObject {
|
|||
|
||||
if(!$config->password_hashing_algorithm)
|
||||
{
|
||||
$oPassword = new Password();
|
||||
$config->password_hashing_algorithm = $oPassword->getBestAlgorithm();
|
||||
$config->password_hashing_algorithm = Rhymix\Framework\Password::getBestSupportedAlgorithm();
|
||||
}
|
||||
if(!$config->password_hashing_work_factor)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -748,10 +748,6 @@ class memberController extends member
|
|||
*/
|
||||
function insertProfileImage($member_srl, $target_file)
|
||||
{
|
||||
|
||||
// Check uploaded file
|
||||
if(!checkUploadedFile($target_file)) return;
|
||||
|
||||
$oMemberModel = getModel('member');
|
||||
$config = $oMemberModel->getMemberConfig();
|
||||
|
||||
|
|
@ -827,9 +823,6 @@ class memberController extends member
|
|||
*/
|
||||
function insertImageName($member_srl, $target_file)
|
||||
{
|
||||
// Check uploaded file
|
||||
if(!checkUploadedFile($target_file)) return;
|
||||
|
||||
$oModuleModel = getModel('module');
|
||||
$config = $oModuleModel->getModuleConfig('member');
|
||||
// Get an image size
|
||||
|
|
@ -936,9 +929,6 @@ class memberController extends member
|
|||
*/
|
||||
function insertImageMark($member_srl, $target_file)
|
||||
{
|
||||
// Check uploaded file
|
||||
if(!checkUploadedFile($target_file)) return;
|
||||
|
||||
$oModuleModel = getModel('module');
|
||||
$config = $oModuleModel->getModuleConfig('member');
|
||||
// Get an image size
|
||||
|
|
@ -1013,12 +1003,11 @@ class memberController extends member
|
|||
}
|
||||
|
||||
// Insert data into the authentication DB
|
||||
$oPassword = new Password();
|
||||
$args = new stdClass();
|
||||
$args->user_id = $member_info->user_id;
|
||||
$args->member_srl = $member_info->member_srl;
|
||||
$args->new_password = $oPassword->createTemporaryPassword(8);
|
||||
$args->auth_key = $oPassword->createSecureSalt(40);
|
||||
$args->new_password = Rhymix\Framework\Password::getRandomPassword(8);
|
||||
$args->auth_key = Rhymix\Framework\Security::getRandom(40, 'hex');
|
||||
$args->is_register = 'N';
|
||||
|
||||
$output = executeQuery('member.insertAuthMail', $args);
|
||||
|
|
@ -1122,8 +1111,7 @@ class memberController extends member
|
|||
}
|
||||
|
||||
// Update to a temporary password and set change_password_date to 1
|
||||
$oPassword = new Password();
|
||||
$temp_password = $oPassword->createTemporaryPassword(8);
|
||||
$temp_password = Rhymix\Framework\Password::getRandomPassword(8);
|
||||
|
||||
$args = new stdClass();
|
||||
$args->member_srl = $member_srl;
|
||||
|
|
@ -1352,12 +1340,11 @@ class memberController extends member
|
|||
$this->_clearMemberCache($args->member_srl);
|
||||
|
||||
// generate new auth key
|
||||
$oPassword = new Password();
|
||||
$auth_args = new stdClass();
|
||||
$auth_args->user_id = $memberInfo->user_id;
|
||||
$auth_args->member_srl = $memberInfo->member_srl;
|
||||
$auth_args->new_password = $memberInfo->password;
|
||||
$auth_args->auth_key = $oPassword->createSecureSalt(40);
|
||||
$auth_args->auth_key = Rhymix\Framework\Security::getRandom(40, 'hex');
|
||||
$auth_args->is_register = 'Y';
|
||||
|
||||
$output = executeQuery('member.insertAuthMail', $auth_args);
|
||||
|
|
@ -1842,8 +1829,7 @@ class memberController extends member
|
|||
if($keep_signed)
|
||||
{
|
||||
// Key generate for auto login
|
||||
$oPassword = new Password();
|
||||
$random_key = $oPassword->createSecureSalt(32, 'hex');
|
||||
$random_key = Rhymix\Framework\Security::getRandom(32, 'hex');
|
||||
$extra_key = strtolower($user_id).$this->memberInfo->password.$_SERVER['HTTP_USER_AGENT'];
|
||||
$extra_key = substr(hash_hmac('sha256', $extra_key, $random_key), 0, 32);
|
||||
$autologin_args = new stdClass;
|
||||
|
|
@ -2175,12 +2161,11 @@ class memberController extends member
|
|||
if($args->denied == 'Y')
|
||||
{
|
||||
// Insert data into the authentication DB
|
||||
$oPassword = new Password();
|
||||
$auth_args = new stdClass();
|
||||
$auth_args->user_id = $args->user_id;
|
||||
$auth_args->member_srl = $args->member_srl;
|
||||
$auth_args->new_password = $args->password;
|
||||
$auth_args->auth_key = $oPassword->createSecureSalt(40);
|
||||
$auth_args->auth_key = Rhymix\Framework\Security::getRandom(40, 'hex');
|
||||
$auth_args->is_register = 'Y';
|
||||
|
||||
$output = executeQuery('member.insertAuthMail', $auth_args);
|
||||
|
|
@ -2692,11 +2677,10 @@ class memberController extends member
|
|||
}
|
||||
unset($_SESSION['rechecked_password_step']);
|
||||
|
||||
$oPassword = new Password();
|
||||
$auth_args = new stdClass();
|
||||
$auth_args->user_id = $newEmail;
|
||||
$auth_args->member_srl = $member_info->member_srl;
|
||||
$auth_args->auth_key = $oPassword->createSecureSalt(40);
|
||||
$auth_args->auth_key = Rhymix\Framework\Security::getRandom(40, 'hex');
|
||||
$auth_args->new_password = 'XE_change_emaill_address';
|
||||
|
||||
$oDB = &DB::getInstance();
|
||||
|
|
|
|||
|
|
@ -1107,10 +1107,19 @@ class memberModel extends member
|
|||
}
|
||||
|
||||
// Check the password
|
||||
$oPassword = new Password();
|
||||
$current_algorithm = $oPassword->checkAlgorithm($hashed_password);
|
||||
$match = $oPassword->checkPassword($password_text, $hashed_password, $current_algorithm);
|
||||
if(!$match)
|
||||
$password_match = false;
|
||||
$current_algorithm = false;
|
||||
$possible_algorithms = Rhymix\Framework\Password::checkAlgorithm($hashed_password);
|
||||
foreach ($possible_algorithms as $algorithm)
|
||||
{
|
||||
if (Rhymix\Framework\Password::checkPassword($password_text, $hashed_password, $algorithm))
|
||||
{
|
||||
$password_match = true;
|
||||
$current_algorithm = $algorithm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$password_match)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1119,22 +1128,26 @@ class memberModel extends member
|
|||
$config = $this->getMemberConfig();
|
||||
if($member_srl > 0 && $config->password_hashing_auto_upgrade != 'N')
|
||||
{
|
||||
$need_upgrade = false;
|
||||
|
||||
if(!$need_upgrade)
|
||||
$required_algorithm = Rhymix\Framework\Password::getDefaultAlgorithm();
|
||||
if ($required_algorithm !== $current_algorithm)
|
||||
{
|
||||
$required_algorithm = $oPassword->getCurrentlySelectedAlgorithm();
|
||||
if($required_algorithm !== $current_algorithm) $need_upgrade = true;
|
||||
$need_upgrade = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$required_work_factor = Rhymix\Framework\Password::getWorkFactor();
|
||||
$current_work_factor = Rhymix\Framework\Password::checkWorkFactor($hashed_password);
|
||||
if ($current_work_factor !== false && $required_work_factor > $current_work_factor)
|
||||
{
|
||||
$need_upgrade = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$need_upgrade = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$need_upgrade)
|
||||
{
|
||||
$required_work_factor = $oPassword->getWorkFactor();
|
||||
$current_work_factor = $oPassword->checkWorkFactor($hashed_password);
|
||||
if($current_work_factor !== false && $required_work_factor > $current_work_factor) $need_upgrade = true;
|
||||
}
|
||||
|
||||
if($need_upgrade === true)
|
||||
if ($need_upgrade)
|
||||
{
|
||||
$args = new stdClass();
|
||||
$args->member_srl = $member_srl;
|
||||
|
|
@ -1155,8 +1168,7 @@ class memberModel extends member
|
|||
*/
|
||||
function hashPassword($password_text, $algorithm = null)
|
||||
{
|
||||
$oPassword = new Password();
|
||||
return $oPassword->createHash($password_text, $algorithm);
|
||||
return Rhymix\Framework\Password::hashPassword($password_text, $algorithm);
|
||||
}
|
||||
|
||||
function checkPasswordStrength($password, $strength)
|
||||
|
|
|
|||
|
|
@ -1520,7 +1520,7 @@ class menuAdminController extends menu
|
|||
Context::set('error_messge', lang('msg_invalid_request'));
|
||||
|
||||
}
|
||||
else if(!$target_file || !is_uploaded_file($target_file['tmp_name']) || !preg_match('/\.(gif|jpeg|jpg|png)$/i',$target_file['name']) || !checkUploadedFile($target_file['tmp_name']))
|
||||
else if(!$target_file || !is_uploaded_file($target_file['tmp_name']) || !preg_match('/\.(gif|jpeg|jpg|png)$/i',$target_file['name']))
|
||||
{
|
||||
Context::set('error_messge', lang('msg_invalid_request'));
|
||||
}
|
||||
|
|
@ -2132,19 +2132,15 @@ class menuAdminController extends menu
|
|||
|
||||
$returnArray = array();
|
||||
$date = date('YmdHis');
|
||||
|
||||
// normal button
|
||||
if($args->menu_normal_btn)
|
||||
{
|
||||
$tmp_arr = explode('.',$args->menu_normal_btn['name']);
|
||||
$ext = $tmp_arr[count($tmp_arr)-1];
|
||||
|
||||
$filename = sprintf('%s%d.%s.%s.%s', $path, $args->menu_item_srl, $date, 'menu_normal_btn', $ext);
|
||||
|
||||
if(checkUploadedFile($args->menu_normal_btn['tmp_name']))
|
||||
{
|
||||
move_uploaded_file ( $args->menu_normal_btn ['tmp_name'], $filename );
|
||||
$returnArray ['normal_btn'] = $filename;
|
||||
}
|
||||
move_uploaded_file($args->menu_normal_btn['tmp_name'], $filename);
|
||||
$returnArray['normal_btn'] = $filename;
|
||||
}
|
||||
|
||||
// hover button
|
||||
|
|
@ -2152,14 +2148,9 @@ class menuAdminController extends menu
|
|||
{
|
||||
$tmp_arr = explode('.',$args->menu_hover_btn['name']);
|
||||
$ext = $tmp_arr[count($tmp_arr)-1];
|
||||
|
||||
$filename = sprintf('%s%d.%s.%s.%s', $path, $args->menu_item_srl, $date, 'menu_hover_btn', $ext);
|
||||
|
||||
if(checkUploadedFile($args->menu_hover_btn['tmp_name']))
|
||||
{
|
||||
move_uploaded_file($args->menu_hover_btn['tmp_name'], $filename);
|
||||
$returnArray['hover_btn'] = $filename;
|
||||
}
|
||||
move_uploaded_file($args->menu_hover_btn['tmp_name'], $filename);
|
||||
$returnArray['hover_btn'] = $filename;
|
||||
}
|
||||
|
||||
// active button
|
||||
|
|
@ -2167,15 +2158,9 @@ class menuAdminController extends menu
|
|||
{
|
||||
$tmp_arr = explode('.',$args->menu_active_btn['name']);
|
||||
$ext = $tmp_arr[count($tmp_arr)-1];
|
||||
|
||||
$filename = sprintf('%s%d.%s.%s.%s', $path, $args->menu_item_srl, $date, 'menu_active_btn', $ext);
|
||||
|
||||
if(checkUploadedFile($args->menu_active_btn['tmp_name']))
|
||||
{
|
||||
move_uploaded_file($args->menu_active_btn['tmp_name'], $filename);
|
||||
$returnArray['active_btn'] = $filename;
|
||||
}
|
||||
|
||||
move_uploaded_file($args->menu_active_btn['tmp_name'], $filename);
|
||||
$returnArray['active_btn'] = $filename;
|
||||
}
|
||||
return $returnArray;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -439,7 +439,7 @@ class moduleAdminController extends module
|
|||
continue;
|
||||
}
|
||||
// Ignore if the file is not successfully uploaded
|
||||
if(!is_uploaded_file($image_obj['tmp_name']) || !checkUploadedFile($image_obj['tmp_name']))
|
||||
if(!is_uploaded_file($image_obj['tmp_name']))
|
||||
{
|
||||
unset($obj->{$vars->name});
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -1303,9 +1303,6 @@ class moduleController extends module
|
|||
$save_filename = sprintf('%s%s.%s',$path, $vars->module_filebox_srl, $ext);
|
||||
$tmp = $vars->addfile['tmp_name'];
|
||||
|
||||
// Check uploaded file
|
||||
if(!checkUploadedFile($tmp)) return false;
|
||||
|
||||
if(!@move_uploaded_file($tmp, $save_filename))
|
||||
{
|
||||
return false;
|
||||
|
|
@ -1340,9 +1337,6 @@ class moduleController extends module
|
|||
$save_filename = sprintf('%s%s.%s',$path, $vars->module_filebox_srl, $vars->ext);
|
||||
$tmp = $vars->addfile['tmp_name'];
|
||||
|
||||
// Check uploaded file
|
||||
if(!checkUploadedFile($tmp)) return false;
|
||||
|
||||
// upload
|
||||
if(!@move_uploaded_file($tmp, $save_filename))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class rssAdminController extends rss
|
|||
$total_config->image = '';
|
||||
}
|
||||
// Ignore if the file is not the one which has been successfully uploaded
|
||||
if($image_obj['tmp_name'] && is_uploaded_file($image_obj['tmp_name']) && checkUploadedFile($image_obj['tmp_name']))
|
||||
if($image_obj['tmp_name'] && is_uploaded_file($image_obj['tmp_name']))
|
||||
{
|
||||
// Ignore if the file is not an image (swf is accepted ~)
|
||||
$image_obj['name'] = Context::convertEncodingStr($image_obj['name']);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
require_once _XE_PATH_.'classes/security/Security.class.php';
|
||||
|
||||
class SecurityTest extends \Codeception\TestCase\Test
|
||||
class OldSecurityTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
public function _before()
|
||||
{
|
||||
|
|
@ -45,10 +45,11 @@ class DebugTest extends \Codeception\TestCase\Test
|
|||
'backtrace' => array(),
|
||||
));
|
||||
$queries = Rhymix\Framework\Debug::getQueries();
|
||||
$this->assertEquals(1, count($queries));
|
||||
$this->assertEquals('SELECT foo FROM bar', $queries[0]->query_string);
|
||||
$this->assertEquals('This is a unit test', $queries[0]->message);
|
||||
$this->assertEquals(1234, $queries[0]->error_code);
|
||||
$this->assertGreaterThanOrEqual(1, count($queries));
|
||||
$query = array_pop($queries);
|
||||
$this->assertEquals('SELECT foo FROM bar', $query->query_string);
|
||||
$this->assertEquals('This is a unit test', $query->message);
|
||||
$this->assertEquals(1234, $query->error_code);
|
||||
}
|
||||
|
||||
public function testDebugTranslateFilename()
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
|
||||
class IpFilterTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
public function testIPv4CIDR()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('10.0.127.191', '10.0.127.191'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('10.1.131.177', '10.1.131.178'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('127.0.0.1', '127.0.0.0/8'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('172.34.0.0', '172.16.0.0/12'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('192.168.18.214', '192.168.16.0/22'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('192.168.18.214', '192.168.16.0/23'));
|
||||
}
|
||||
|
||||
public function testIPv6CIDR()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('::1', '::1/128'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('::1', '::2'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('2400:cb00::1234', '2400:cb00::/32'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('2405:8100::1234', '2400:cb00::/32'));
|
||||
}
|
||||
|
||||
public function testIPv4Wildcard()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.134.*'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.*.*'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.136.*'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.172.*.*'));
|
||||
}
|
||||
|
||||
public function testIPv4Hyphen()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.134.0-192.168.134.255'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.128.16-192.168.145.0'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.134.242-192.168.244.7'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.100.255-192.168.133.19'));
|
||||
}
|
||||
|
||||
public function testValidator()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('192.168.0.1'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('192.168.0.0/16'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('192.168.*.*'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('192.168.*'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('192.168.0.0-192.168.255.255'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('2400:cb00::/32'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::validateRange('192.168.0.0~192.168.255.255'));
|
||||
}
|
||||
|
||||
public function testLegacy()
|
||||
{
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.134.241'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.134.0-192.168.134.255'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('127.0.0.1', '192.168.134.241'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.134.*'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.*'), '192.168.134.241'));
|
||||
$this->assertFalse(\IpFilter::filter(array('127.0.0.1'), '192.168.134.241'));
|
||||
}
|
||||
|
||||
public function testCloudFlareRealIP()
|
||||
{
|
||||
$_SERVER['HTTP_CF_CONNECTING_IP'] = '192.168.134.241';
|
||||
|
||||
$_SERVER['REMOTE_ADDR'] = '192.168.10.1';
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::getCloudFlareRealIP());
|
||||
$this->assertEquals('192.168.10.1', $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
$_SERVER['REMOTE_ADDR'] = '108.162.192.121';
|
||||
$this->assertEquals('192.168.134.241', Rhymix\Framework\IpFilter::getCloudFlareRealIP());
|
||||
$this->assertEquals('192.168.134.241', $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
unset($_SERVER['HTTP_CF_CONNECTING_IP']);
|
||||
$_SERVER['REMOTE_ADDR'] = '192.168.10.1';
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::getCloudFlareRealIP());
|
||||
$this->assertEquals('192.168.10.1', $_SERVER['REMOTE_ADDR']);
|
||||
}
|
||||
}
|
||||
143
tests/unit/framework/PasswordTest.php
Normal file
143
tests/unit/framework/PasswordTest.php
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
class PasswordTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
public function testIsValidAlgorithm()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\Password::isValidAlgorithm('bcrypt'));
|
||||
$this->assertTrue(Rhymix\Framework\Password::isValidAlgorithm('whirlpool,pbkdf2'));
|
||||
$this->assertTrue(Rhymix\Framework\Password::isValidAlgorithm(array('md5', 'sha1', 'md5')));
|
||||
|
||||
$this->assertFalse(Rhymix\Framework\Password::isValidAlgorithm('bunga_bunga'));
|
||||
Rhymix\Framework\Password::addAlgorithm('bunga_bunga', '/bunga_bunga/', function($hash) { return 'bunga_bunga'; });
|
||||
$this->assertTrue(Rhymix\Framework\Password::isValidAlgorithm('bunga_bunga'));
|
||||
}
|
||||
|
||||
public function testGetSupportedAlgorithms()
|
||||
{
|
||||
$algos = Rhymix\Framework\Password::getSupportedAlgorithms();
|
||||
$this->assertTrue(in_array('bcrypt', $algos));
|
||||
$this->assertTrue(in_array('pbkdf2', $algos));
|
||||
$this->assertTrue(in_array('md5', $algos));
|
||||
}
|
||||
|
||||
public function testGetBestSupportedAlgorithm()
|
||||
{
|
||||
$algo = Rhymix\Framework\Password::getBestSupportedAlgorithm();
|
||||
$this->assertTrue($algo === 'bcrypt' || $algo === 'pbkdf2');
|
||||
}
|
||||
|
||||
public function testGetDefaultAlgorithm()
|
||||
{
|
||||
$algo = Rhymix\Framework\Password::getDefaultAlgorithm();
|
||||
$this->assertTrue($algo === 'bcrypt' || $algo === 'pbkdf2' || $algo === 'md5');
|
||||
}
|
||||
|
||||
public function testGetWorkFactor()
|
||||
{
|
||||
$work_factor = $algo = Rhymix\Framework\Password::getWorkFactor();
|
||||
$this->assertTrue($work_factor >= 4);
|
||||
$this->assertTrue($work_factor <= 31);
|
||||
}
|
||||
|
||||
public function testGetRandomPassword()
|
||||
{
|
||||
$password = Rhymix\Framework\Password::getRandomPassword(16);
|
||||
$this->assertEquals(16, strlen($password));
|
||||
$this->assertRegexp('/[a-z]/', $password);
|
||||
$this->assertRegexp('/[A-Z]/', $password);
|
||||
$this->assertRegexp('/[0-9]/', $password);
|
||||
$this->assertRegexp('/[^a-zA-Z0-9]/', $password);
|
||||
}
|
||||
|
||||
public function testHashPassword()
|
||||
{
|
||||
$password = Rhymix\Framework\Security::getRandom(32);
|
||||
$this->assertEquals(md5($password), Rhymix\Framework\Password::hashPassword($password, 'md5'));
|
||||
$this->assertEquals(md5(sha1(md5($password))), Rhymix\Framework\Password::hashPassword($password, 'md5,sha1,md5'));
|
||||
$this->assertEquals(hash('whirlpool', $password), Rhymix\Framework\Password::hashPassword($password, 'whirlpool'));
|
||||
$this->assertEquals('5d2e19393cc5ef67', Rhymix\Framework\Password::hashPassword('password', 'mysql_old_password'));
|
||||
}
|
||||
|
||||
public function testCheckPassword()
|
||||
{
|
||||
$password = Rhymix\Framework\Security::getRandom(32);
|
||||
|
||||
$algos = array('whirlpool', 'ripemd160', 'bcrypt');
|
||||
$hash = Rhymix\Framework\Password::hashPassword($password, $algos);
|
||||
$this->assertRegExp('/^\$2y\$/', $hash);
|
||||
$this->assertEquals(60, strlen($hash));
|
||||
$this->assertTrue(Rhymix\Framework\Password::checkPassword($password, $hash, $algos));
|
||||
|
||||
$algos = array('sha384', 'pbkdf2');
|
||||
$hash = Rhymix\Framework\Password::hashPassword($password, $algos);
|
||||
$this->assertRegExp('/^(sha256|sha512):[0-9]+:/', $hash);
|
||||
$this->assertEquals(60, strlen($hash));
|
||||
$this->assertTrue(Rhymix\Framework\Password::checkPassword($password, $hash, $algos));
|
||||
|
||||
$algos = array('sha1', 'portable');
|
||||
$hash = Rhymix\Framework\Password::hashPassword($password, $algos);
|
||||
$this->assertRegExp('/^\$P\$/', $hash);
|
||||
$this->assertEquals(34, strlen($hash));
|
||||
$this->assertTrue(Rhymix\Framework\Password::checkPassword($password, $hash, $algos));
|
||||
|
||||
foreach (array('drupal', 'joomla', 'kimsqrb', 'mysql_old_password', 'mysql_new_password', 'mssql_pwdencrypt') as $algo)
|
||||
{
|
||||
$hash = Rhymix\Framework\Password::hashPassword($password, $algo);
|
||||
$this->assertTrue(Rhymix\Framework\Password::checkPassword($password, $hash, $algo));
|
||||
$this->assertFalse(Rhymix\Framework\Password::checkPassword($password, $hash . 'x', $algo));
|
||||
}
|
||||
}
|
||||
|
||||
public function testCheckAlgorithm()
|
||||
{
|
||||
$password = Rhymix\Framework\Security::getRandom(32, 'hex');
|
||||
|
||||
$this->assertEquals(array('md5', 'md5,sha1,md5'), Rhymix\Framework\Password::checkAlgorithm($password));
|
||||
$this->assertEquals(array('sha512', 'whirlpool'), Rhymix\Framework\Password::checkAlgorithm(hash('sha512', $password)));
|
||||
|
||||
$hash = '$2y$10$VkxBdEBTZ1HyLluZPjXCjuFffw0a6alZlbb733CF/zA22HDpBNsMm';
|
||||
$this->assertEquals(array('bcrypt'), Rhymix\Framework\Password::checkAlgorithm($hash));
|
||||
|
||||
$hash = 'sha512:0008192:hoXcLXQzIiIJ:ElokybdRf+i512M4/4PIdEiSDgZ8f0uL';
|
||||
$this->assertEquals(array('pbkdf2'), Rhymix\Framework\Password::checkAlgorithm($hash));
|
||||
}
|
||||
|
||||
public function testCheckWorkFactor()
|
||||
{
|
||||
$hash = '$2y$10$VkxBdEBTZ1HyLluZPjXCjuFffw0a6alZlbb733CF/zA22HDpBNsMm';
|
||||
$this->assertEquals(10, Rhymix\Framework\Password::checkWorkFactor($hash));
|
||||
|
||||
$hash = 'sha512:0008192:hoXcLXQzIiIJ:ElokybdRf+i512M4/4PIdEiSDgZ8f0uL';
|
||||
$this->assertEquals(8, Rhymix\Framework\Password::checkWorkFactor($hash));
|
||||
|
||||
$hash = '5f4dcc3b5aa765d61d8327deb882cf99';
|
||||
$this->assertEquals(0, Rhymix\Framework\Password::checkWorkFactor($hash));
|
||||
}
|
||||
|
||||
public function testBcrypt()
|
||||
{
|
||||
$password = 'password';
|
||||
$hash = '$2y$10$VkxBdEBTZ1HyLluZPjXCjuFffw0a6alZlbb733CF/zA22HDpBNsMm';
|
||||
$this->assertEquals($hash, Rhymix\Framework\Password::bcrypt($password, $hash));
|
||||
}
|
||||
|
||||
public function testPBKDF2()
|
||||
{
|
||||
$password = 'password';
|
||||
$salt = 'rtmIxdEUoWUk';
|
||||
$hash = 'sha512:0016384:rtmIxdEUoWUk:1hrwGP3ScWvxslnqNFqyhM6Ddn4iYrwf';
|
||||
$this->assertEquals($hash, Rhymix\Framework\Password::pbkdf2($password, $salt, 'sha512', 16384, 24));
|
||||
}
|
||||
|
||||
public function testCountEntropyBits()
|
||||
{
|
||||
$this->assertEquals(0, Rhymix\Framework\Password::countEntropyBits(''));
|
||||
$this->assertEquals(13, round(Rhymix\Framework\Password::countEntropyBits('1234')));
|
||||
$this->assertEquals(20, round(Rhymix\Framework\Password::countEntropyBits('123456')));
|
||||
$this->assertEquals(28, round(Rhymix\Framework\Password::countEntropyBits('rhymix')));
|
||||
$this->assertEquals(52, round(Rhymix\Framework\Password::countEntropyBits('rhymix1234')));
|
||||
$this->assertEquals(60, round(Rhymix\Framework\Password::countEntropyBits('RhymiX1234')));
|
||||
$this->assertEquals(125, round(Rhymix\Framework\Password::countEntropyBits('Rhymix_is*the%Best!')));
|
||||
}
|
||||
}
|
||||
134
tests/unit/framework/SecurityTest.php
Normal file
134
tests/unit/framework/SecurityTest.php
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
class SecurityTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
public function testSanitize()
|
||||
{
|
||||
// Escape
|
||||
$this->assertEquals('foo<bar>', Rhymix\Framework\Security::sanitize('foo<bar>', 'escape'));
|
||||
|
||||
// Strip
|
||||
$this->assertEquals('foobar', Rhymix\Framework\Security::sanitize('foo<p>bar</p>', 'strip'));
|
||||
|
||||
// HTML (more thorough tests in HTMLFilterTest)
|
||||
$this->assertEquals('<p>safe</p>', Rhymix\Framework\Security::sanitize('<p>safe<script></script></p>', 'html'));
|
||||
|
||||
// Filename (more thorough tests in FilenameFilterTest)
|
||||
$this->assertEquals('foo(bar).xls', Rhymix\Framework\Security::sanitize('foo<bar>.xls', 'filename'));
|
||||
}
|
||||
|
||||
public function testEncryption()
|
||||
{
|
||||
$plaintext = Rhymix\Framework\Security::getRandom();
|
||||
|
||||
// Encryption with default key.
|
||||
$encrypted = Rhymix\Framework\Security::encrypt($plaintext);
|
||||
$this->assertNotEquals(false, $encrypted);
|
||||
$decrypted = Rhymix\Framework\Security::decrypt($encrypted);
|
||||
$this->assertEquals($plaintext, $decrypted);
|
||||
|
||||
// Encryption with custom key.
|
||||
$key = Rhymix\Framework\Security::getRandom();
|
||||
$encrypted = Rhymix\Framework\Security::encrypt($plaintext, $key);
|
||||
$this->assertNotEquals(false, $encrypted);
|
||||
$decrypted = Rhymix\Framework\Security::decrypt($encrypted, $key);
|
||||
$this->assertEquals($plaintext, $decrypted);
|
||||
|
||||
// Encryption with defuse/php-encryption and decryption with CryptoCompat.
|
||||
$encrypted = Rhymix\Framework\Security::encrypt($plaintext);
|
||||
$this->assertNotEquals(false, $encrypted);
|
||||
$decrypted = Rhymix\Framework\Security::decrypt($encrypted, null, true);
|
||||
$this->assertEquals($plaintext, $decrypted);
|
||||
|
||||
// Encryption with CryptoCompat and decryption with defuse/php-encryption.
|
||||
$encrypted = Rhymix\Framework\Security::encrypt($plaintext, null, true);
|
||||
$this->assertNotEquals(false, $encrypted);
|
||||
$decrypted = Rhymix\Framework\Security::decrypt($encrypted);
|
||||
$this->assertEquals($plaintext, $decrypted);
|
||||
|
||||
// Test invalid ciphertext.
|
||||
$decrypted = Rhymix\Framework\Security::decrypt('1234' . substr($encrypted, 4));
|
||||
$this->assertEquals(false, $decrypted);
|
||||
$decrypted = Rhymix\Framework\Security::decrypt(substr($encrypted, strlen($encrypted) - 4) . 'abcd');
|
||||
$this->assertEquals(false, $decrypted);
|
||||
$decrypted = Rhymix\Framework\Security::decrypt($plaintext);
|
||||
$this->assertEquals(false, $decrypted);
|
||||
}
|
||||
|
||||
public function testGetRandom()
|
||||
{
|
||||
$this->assertRegExp('/^[0-9a-zA-Z]{32}$/', Rhymix\Framework\Security::getRandom());
|
||||
$this->assertRegExp('/^[0-9a-zA-Z]{256}$/', Rhymix\Framework\Security::getRandom(256));
|
||||
$this->assertRegExp('/^[0-9a-zA-Z]{16}$/', Rhymix\Framework\Security::getRandom(16, 'alnum'));
|
||||
$this->assertRegExp('/^[0-9a-f]{16}$/', Rhymix\Framework\Security::getRandom(16, 'hex'));
|
||||
$this->assertRegExp('/^[\x21-\x7e]{16}$/', Rhymix\Framework\Security::getRandom(16, 'printable'));
|
||||
}
|
||||
|
||||
public function testGetRandomNumber()
|
||||
{
|
||||
for ($i = 0; $i < 10; $i++)
|
||||
{
|
||||
$min = mt_rand(0, 10000);
|
||||
$max = $min + mt_rand(0, 10000);
|
||||
$random = Rhymix\Framework\Security::getRandomNumber($min, $max);
|
||||
$this->assertTrue($random >= $min && $random <= $max);
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetRandomUUID()
|
||||
{
|
||||
$regex = '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/';
|
||||
for ($i = 0; $i < 10; $i++)
|
||||
{
|
||||
$this->assertRegExp($regex, Rhymix\Framework\Security::getRandomUUID());
|
||||
}
|
||||
}
|
||||
|
||||
public function testCompareStrings()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\Security::compareStrings('foobar', 'foobar'));
|
||||
$this->assertFalse(Rhymix\Framework\Security::compareStrings('foobar', 'foobar*'));
|
||||
$this->assertFalse(Rhymix\Framework\Security::compareStrings('foo', 'bar'));
|
||||
}
|
||||
|
||||
public function testCheckCSRF()
|
||||
{
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
$_SERVER['HTTP_REFERER'] = '';
|
||||
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
|
||||
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
|
||||
|
||||
$_SERVER['HTTP_HOST'] = 'www.rhymix.org';
|
||||
$_SERVER['HTTP_REFERER'] = 'http://www.foobar.com/';
|
||||
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
|
||||
|
||||
$_SERVER['HTTP_HOST'] = 'www.rhymix.org';
|
||||
$this->assertTrue(Rhymix\Framework\Security::checkCSRF('http://www.rhymix.org/'));
|
||||
}
|
||||
|
||||
public function testCheckXEE()
|
||||
{
|
||||
$xml = '<methodCall></methodCall>';
|
||||
$this->assertTrue(Rhymix\Framework\Security::checkXEE($xml));
|
||||
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?><methodCall></methodCall>';
|
||||
$this->assertTrue(Rhymix\Framework\Security::checkXEE($xml));
|
||||
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo><methodCall attr="value"></methodCall>';
|
||||
$this->assertTrue(Rhymix\Framework\Security::checkXEE($xml));
|
||||
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo><whatever></whatever>';
|
||||
$this->assertFalse(Rhymix\Framework\Security::checkXEE($xml));
|
||||
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo>';
|
||||
$this->assertFalse(Rhymix\Framework\Security::checkXEE($xml));
|
||||
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?><!ENTITY xxe SYSTEM "http://www.attacker.com/text.txt"><methodCall></methodCall>';
|
||||
$this->assertFalse(Rhymix\Framework\Security::checkXEE($xml));
|
||||
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [<!ELEMENT foo ANY><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><fault></fault>';
|
||||
$this->assertFalse(Rhymix\Framework\Security::checkXEE($xml));
|
||||
}
|
||||
}
|
||||
94
tests/unit/framework/URLTest.php
Normal file
94
tests/unit/framework/URLTest.php
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
class URLTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
public function testGetCurrentURL()
|
||||
{
|
||||
$protocol = \RX_SSL ? 'https://' : 'http://';
|
||||
$_SERVER['HTTP_HOST'] = 'www.rhymix.org';
|
||||
$_SERVER['REQUEST_URI'] = '/index.php?foo=bar&xe=sucks';
|
||||
$full_url = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
||||
|
||||
// Getting the current URL
|
||||
$this->assertEquals($full_url, Rhymix\Framework\URL::getCurrentURL());
|
||||
|
||||
// Adding items to the query string
|
||||
$this->assertEquals($full_url . '&var=1&arr%5B0%5D=2&arr%5B1%5D=3', Rhymix\Framework\URL::getCurrentURL(array('var' => '1', 'arr' => array(2, 3))));
|
||||
|
||||
// Removing item from the query string
|
||||
$this->assertEquals($protocol . $_SERVER['HTTP_HOST'] . '/index.php?xe=sucks', Rhymix\Framework\URL::getCurrentURL(array('foo' => null)));
|
||||
|
||||
// Removing all items from the query string
|
||||
$this->assertEquals($protocol . $_SERVER['HTTP_HOST'] . '/index.php', Rhymix\Framework\URL::getCurrentURL(array('foo' => null, 'xe' => null)));
|
||||
|
||||
// Adding and removing parameters at the same time
|
||||
$this->assertEquals($protocol . $_SERVER['HTTP_HOST'] . '/index.php?xe=sucks&l=ko', Rhymix\Framework\URL::getCurrentURL(array('l' => 'ko', 'foo' => null)));
|
||||
}
|
||||
|
||||
public function testGetCanonicalURL()
|
||||
{
|
||||
$protocol = \RX_SSL ? 'https://' : 'http://';
|
||||
$_SERVER['HTTP_HOST'] = 'www.rhymix.org';
|
||||
|
||||
$tests = array(
|
||||
'foo/bar' => $protocol . $_SERVER['HTTP_HOST'] . \RX_BASEURL . 'foo/bar',
|
||||
'./foo/bar' => $protocol . $_SERVER['HTTP_HOST'] . \RX_BASEURL . 'foo/bar',
|
||||
'/foo/bar' => $protocol . $_SERVER['HTTP_HOST'] . \RX_BASEURL . 'foo/bar',
|
||||
'//www.example.com/foo' => $protocol . 'www.example.com/foo',
|
||||
'http://xn--cg4bkiv2oina.com/' => 'http://삼성전자.com/',
|
||||
);
|
||||
|
||||
foreach ($tests as $from => $to)
|
||||
{
|
||||
$this->assertEquals($to, Rhymix\Framework\URL::getCanonicalURL($from));
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetDomainFromURL()
|
||||
{
|
||||
$tests = array(
|
||||
'https://www.rhymix.org/foo/bar' => 'www.rhymix.org',
|
||||
'https://www.rhymix.org:8080/foo/bar' => 'www.rhymix.org',
|
||||
'http://xn--cg4bkiv2oina.com/' => '삼성전자.com',
|
||||
);
|
||||
|
||||
foreach ($tests as $from => $to)
|
||||
{
|
||||
$this->assertEquals($to, Rhymix\Framework\URL::getDomainFromURL($from));
|
||||
}
|
||||
}
|
||||
|
||||
public function testModifyURL()
|
||||
{
|
||||
$protocol = \RX_SSL ? 'https://' : 'http://';
|
||||
$_SERVER['HTTP_HOST'] = 'www.rhymix.org';
|
||||
$url = $protocol . $_SERVER['HTTP_HOST'] . \RX_BASEURL . 'index.php?foo=bar';
|
||||
|
||||
// Conversion to absolute
|
||||
$this->assertEquals($url, Rhymix\Framework\URL::modifyURL('./index.php?foo=bar'));
|
||||
|
||||
// Adding items to the query string
|
||||
$this->assertEquals($url . '&var=1&arr%5B0%5D=2&arr%5B1%5D=3', Rhymix\Framework\URL::modifyURL($url, array('var' => '1', 'arr' => array(2, 3))));
|
||||
|
||||
// Removing item from the query string
|
||||
$this->assertEquals($protocol . $_SERVER['HTTP_HOST'] . \RX_BASEURL . 'index.php', Rhymix\Framework\URL::modifyURL($url, array('foo' => null)));
|
||||
|
||||
// Adding and removing parameters at the same time
|
||||
$this->assertEquals($protocol . $_SERVER['HTTP_HOST'] . \RX_BASEURL . 'index.php?l=ko', Rhymix\Framework\URL::modifyURL($url, array('l' => 'ko', 'foo' => null)));
|
||||
}
|
||||
|
||||
public function testIsInternalURL()
|
||||
{
|
||||
// This function is checked in Security::checkCSRF()
|
||||
}
|
||||
|
||||
public function testEncodeIdna()
|
||||
{
|
||||
$this->assertEquals('xn--9i1bl3b186bf9e.xn--3e0b707e', Rhymix\Framework\URL::encodeIdna('퓨니코드.한국'));
|
||||
}
|
||||
|
||||
public function testDecodeIdna()
|
||||
{
|
||||
$this->assertEquals('퓨니코드.한국', Rhymix\Framework\URL::decodeIdna('xn--9i1bl3b186bf9e.xn--3e0b707e'));
|
||||
}
|
||||
}
|
||||
42
tests/unit/framework/filters/FilenameFilterTest.php
Normal file
42
tests/unit/framework/filters/FilenameFilterTest.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
class FilenameFilterTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
public function testFilenameFilterClean()
|
||||
{
|
||||
$tests = array(
|
||||
|
||||
// Illegal characters
|
||||
'foo*\.bar' => 'foo.bar',
|
||||
'foobar{baz}.jpg' => 'foobar(baz).jpg',
|
||||
'foobar^%.docx' => 'foobar_.docx',
|
||||
|
||||
// Control characters
|
||||
'foobar' . chr(127) . '.gif' => 'foobar.gif',
|
||||
'foobar' . "\t\r\n" . '.gif' => 'foobar.gif',
|
||||
|
||||
// Unicode whitepace characters
|
||||
'foobar' . html_entity_decode(' ') . ' space.gif' => 'foobar space.gif',
|
||||
'hello world.png' => 'hello world.png',
|
||||
|
||||
// Extra symbols
|
||||
'_foobar.jpg-' => 'foobar.jpg',
|
||||
'.htaccess' => 'htaccess',
|
||||
|
||||
// PHP extension
|
||||
'foobar.php' => 'foobar.phps',
|
||||
'foobar.php.jpg' => 'foobar.php.jpg',
|
||||
|
||||
// Overlong filenames
|
||||
str_repeat('f', 200) . '.' . str_repeat('b', 30) => str_repeat('f', 111) . '.' . str_repeat('b', 15),
|
||||
str_repeat('한글', 100) . '.hwp' => str_repeat('한글', 61) . '한.hwp',
|
||||
|
||||
);
|
||||
|
||||
foreach ($tests as $from => $to)
|
||||
{
|
||||
$result = Rhymix\Framework\Filters\FilenameFilter::clean($from);
|
||||
$this->assertEquals($to, $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
155
tests/unit/framework/filters/HTMLFilterTest.php
Normal file
155
tests/unit/framework/filters/HTMLFilterTest.php
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
class HTMLFilterTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
public function testHTMLFilterClean()
|
||||
{
|
||||
$tests = array(
|
||||
// remove iframe
|
||||
array(
|
||||
'<div class="frame"><iframe src="path/to/file.html"></iframe><p><a href="#iframe">IFrame</a></p></div>',
|
||||
'<div class="frame"><iframe></iframe><p><a href="#iframe">IFrame</a></p></div>'
|
||||
),
|
||||
// expression
|
||||
array(
|
||||
'<div class="dummy" style="xss:expr/*XSS*/ession(alert(\'XSS\'))">',
|
||||
'<div class="dummy"></div>'
|
||||
),
|
||||
// no quotes and no semicolon - http://ha.ckers.org/xss.html
|
||||
array(
|
||||
'<img src=javascript:alert(\'xss\')>',
|
||||
''
|
||||
),
|
||||
// embedded encoded tab to break up XSS - http://ha.ckers.org/xss.html
|
||||
array(
|
||||
'<IMG SRC="jav	ascript:alert(\'XSS\');">',
|
||||
'<img src="jav%20ascript%3Aalert(\'XSS\');" alt="" />'
|
||||
),
|
||||
// issue 178
|
||||
array(
|
||||
'<img src="invalid.jpg"\nonerror="alert(1)" />',
|
||||
'<img src="invalid.jpg" alt="" />'
|
||||
),
|
||||
// issue 534
|
||||
array(
|
||||
'<img src=\'as"df dummy=\'"1234\'" 4321\' asdf/*/>*/" onerror="console.log(\'Yet another XSS\')">',
|
||||
'<img src="as" alt="" />*/" onerror="console.log(\'Yet another XSS\')">'
|
||||
),
|
||||
// issue 602
|
||||
array(
|
||||
'<img alt="test" src="(http://static.naver.com/www/u/2010/0611/nmms_215646753.gif" onload="eval(String.fromCharCode(105,61,49,48,48,59,119,104,105,108,101, 40,105,62,48,41,97,108,101,114,116,40,40,105,45,45,41,43,39,48264,47564,32, 45908,32,53364,47533,54616,49464,50836,39,41,59));">',
|
||||
'<img alt="test" src="(http%3A//static.naver.com/www/u/2010/0611/nmms_215646753.gif" />'
|
||||
),
|
||||
// issue #1813 https://github.com/xpressengine/xe-core/issues/1813
|
||||
array(
|
||||
'<img src="?act=dispLayoutPreview" alt="dummy" />',
|
||||
'<img src="" alt="dummy" />'
|
||||
),
|
||||
array(
|
||||
'<img src="?act =dispLayoutPreview" alt="dummy" />',
|
||||
'<img src="" alt="dummy" />'
|
||||
),
|
||||
array(
|
||||
"<img src=\"?act\n=dispLayoutPreview\" alt=\"dummy\" />",
|
||||
'<img src="" alt="dummy" />'
|
||||
),
|
||||
array(
|
||||
"<img src=\"?pam=act&a\nct =\r\n\tdispLayoutPreview\" alt=\"dummy\" />",
|
||||
'<img src="" alt="dummy" />'
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($tests as $test)
|
||||
{
|
||||
$this->assertEquals($test[1], Rhymix\Framework\Filters\HTMLFilter::clean($test[0]));
|
||||
}
|
||||
}
|
||||
|
||||
public function testHTMLFilterHTML5()
|
||||
{
|
||||
$source = '<div><audio autoplay="autoplay" src="./foo/bar.mp3"></audio></div>';
|
||||
$target = '<div><audio src="./foo/bar.mp3"></audio></div>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
|
||||
$source = '<video autoplay="autoplay" width="320" height="240"><source src="./foo/bar.mp4" type="video/mp4" /></video>';
|
||||
$target = '<video width="320" height="240"><source src="./foo/bar.mp4" type="video/mp4" /></video>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
|
||||
$source = '<nav>123</nav><section>456</section><article>789</article><aside>0</aside>';
|
||||
$target = '<nav>123</nav><section>456</section><article>789</article><aside>0</aside>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
}
|
||||
|
||||
public function testHTMLFilterCSS3()
|
||||
{
|
||||
$source = '<div style="display:flex;border-radius:1px 2px 3px 4px;"></div>';
|
||||
$target = '<div style="display:flex;border-radius:1px 2px 3px 4px;"></div>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
|
||||
$source = '<div style="box-sizing:border-box;box-shadow:5px 5px 2px #123456;"></div>';
|
||||
$target = '<div style="box-sizing:border-box;box-shadow:5px 5px 2px #123456;"></div>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
|
||||
$source = '<div style="overflow-x:auto;overflow-y:scroll;left:-500px;"></div>';
|
||||
$target = '<div style="overflow-x:auto;overflow-y:scroll;"></div>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
}
|
||||
|
||||
public function testHTMLFilterEmbeddedMedia()
|
||||
{
|
||||
$source = '<iframe title="Video Test" width="640" height="360" src="http://videofarm.daum.net/controller/video/viewer/Video.html?vid=s474b7BR2zzREo0g7OT7EKo&play_loc=undefined&alert=true" frameborder="0" scrolling="no"></iframe>';
|
||||
$target = '<iframe title="Video Test" width="640" height="360" src="http://videofarm.daum.net/controller/video/viewer/Video.html?vid=s474b7BR2zzREo0g7OT7EKo&play_loc=undefined&alert=true" frameborder="0" scrolling="no"></iframe>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
|
||||
$source = '<iframe title="Video Test" width="640" height="360" src="http://not-allowed.com/whatever-video.mp4" frameborder="0" scrolling="no"></iframe>';
|
||||
$target = '<iframe title="Video Test" width="640" height="360" frameborder="0" scrolling="no"></iframe>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
|
||||
$source = '<object type="application/x-shockwave-flash" id="DaumVodPlayer_s474b7BR2zzREo0g7OT7EKo" width="640px" height="360px" align="middle" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=10,3,0,0">' .
|
||||
'<param name="movie" value="http://videofarm.daum.net/controller/player/VodPlayer.swf" />' .
|
||||
'<param name="allowScriptAccess" value="always" />' .
|
||||
'<param name="allowFullScreen" value="true" />' .
|
||||
'<param name="bgcolor" value="#000000" />' .
|
||||
'<param name="wmode" value="window" />' .
|
||||
'<param name="flashvars" value="vid=s474b7BR2zzREo0g7OT7EKo&playLoc=undefined&alert=true" />' .
|
||||
'<embed src="http://videofarm.daum.net/controller/player/VodPlayer.swf" width="640px" height="360px" allowScriptAccess="always" type="application/x-shockwave-flash" allowFullScreen="true" bgcolor="#000000" flashvars="vid=s474b7BR2zzREo0g7OT7EKo&playLoc=undefined&alert=true"></embed>' .
|
||||
'</object>';
|
||||
$target = '<object type="application/x-shockwave-flash" width="640" height="360" data="http://videofarm.daum.net/controller/player/VodPlayer.swf">' .
|
||||
'<param name="allowScriptAccess" value="never" />' .
|
||||
'<param name="allowNetworking" value="internal" />' .
|
||||
'<param name="movie" value="http://videofarm.daum.net/controller/player/VodPlayer.swf" />' .
|
||||
'<param name="allowFullScreen" value="true" />' .
|
||||
'<param name="wmode" value="window" />' .
|
||||
'<param name="flashvars" value="vid=s474b7BR2zzREo0g7OT7EKo&playLoc=undefined&alert=true" />' .
|
||||
'<embed src="http://videofarm.daum.net/controller/player/VodPlayer.swf" width="640" height="360" type="application/x-shockwave-flash" flashvars="vid=s474b7BR2zzREo0g7OT7EKo&playLoc=undefined&alert=true" allowscriptaccess="never" allownetworking="internal" />' .
|
||||
'</object>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
|
||||
$source = '<audio src="https://www.youtube.com/whatever"></audio>';
|
||||
$target = '<audio src="https://www.youtube.com/whatever"></audio>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
|
||||
$source = '<audio src="https://www-youtube.com/whatever"></audio>';
|
||||
$target = '<audio src=""></audio>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
|
||||
$source = '<video width="320" height="240"><source src="http://api.v.daum.net/something" type="video/mp4" /></video>';
|
||||
$target = '<video width="320" height="240"><source src="http://api.v.daum.net/something" type="video/mp4" /></video>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
|
||||
$source = '<video width="320" height="240"><source src="http://wrong-site.net/" type="video/mp4" /></video>';
|
||||
$target = '<video width="320" height="240"><source src="" type="video/mp4" /></video>';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
}
|
||||
|
||||
public function testHTMLFilterEditorComponent()
|
||||
{
|
||||
$source = '<img somekey="somevalue" otherkey="othervalue" onmouseover="alert(\'xss\');" editor_component="component_name" src="./foo/bar.jpg" alt="My Picture" style="width:320px;height:240px;" width="320" height="240" />';
|
||||
$target = '<img somekey="somevalue" otherkey="othervalue" editor_component="component_name" src="./foo/bar.jpg" alt="My Picture" style="width:320px;height:240px;" width="320" height="240" />';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
|
||||
$source = '<img somekey="somevalue" otherkey="othervalue" onkeypress="alert(\'xss\');" editor_component="component_name" />';
|
||||
$target = '<img somekey="somevalue" otherkey="othervalue" src="" editor_component="component_name" alt="" />';
|
||||
$this->assertEquals($target, Rhymix\Framework\Filters\HTMLFilter::clean($source));
|
||||
}
|
||||
}
|
||||
77
tests/unit/framework/filters/IpFilterTest.php
Normal file
77
tests/unit/framework/filters/IpFilterTest.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
class IpFilterTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
public function testIPv4CIDR()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::inRange('10.0.127.191', '10.0.127.191'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::inRange('10.1.131.177', '10.1.131.178'));
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::inRange('127.0.0.1', '127.0.0.0/8'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::inRange('172.34.0.0', '172.16.0.0/12'));
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::inRange('192.168.18.214', '192.168.16.0/22'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::inRange('192.168.18.214', '192.168.16.0/23'));
|
||||
}
|
||||
|
||||
public function testIPv6CIDR()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::inRange('::1', '::1/128'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::inRange('::1', '::2'));
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::inRange('2400:cb00::1234', '2400:cb00::/32'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::inRange('2405:8100::1234', '2400:cb00::/32'));
|
||||
}
|
||||
|
||||
public function testIPv4Wildcard()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::inRange('192.168.134.241', '192.168.134.*'));
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::inRange('192.168.134.241', '192.168.*.*'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::inRange('192.168.134.241', '192.168.136.*'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::inRange('192.168.134.241', '192.172.*.*'));
|
||||
}
|
||||
|
||||
public function testIPv4Hyphen()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::inRange('192.168.134.241', '192.168.134.0-192.168.134.255'));
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::inRange('192.168.134.241', '192.168.128.16-192.168.145.0'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::inRange('192.168.134.241', '192.168.134.242-192.168.244.7'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::inRange('192.168.134.241', '192.168.100.255-192.168.133.19'));
|
||||
}
|
||||
|
||||
public function testValidator()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::validateRange('192.168.0.1'));
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::validateRange('192.168.0.0/16'));
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::validateRange('192.168.*.*'));
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::validateRange('192.168.*'));
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::validateRange('192.168.0.0-192.168.255.255'));
|
||||
$this->assertTrue(Rhymix\Framework\Filters\IpFilter::validateRange('2400:cb00::/32'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::validateRange('192.168.0.0~192.168.255.255'));
|
||||
}
|
||||
|
||||
public function testLegacy()
|
||||
{
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.134.241'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.134.0-192.168.134.255'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('127.0.0.1', '192.168.134.241'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.134.*'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.*'), '192.168.134.241'));
|
||||
$this->assertFalse(\IpFilter::filter(array('127.0.0.1'), '192.168.134.241'));
|
||||
}
|
||||
|
||||
public function testCloudFlareRealIP()
|
||||
{
|
||||
$_SERVER['HTTP_CF_CONNECTING_IP'] = '192.168.134.241';
|
||||
|
||||
$_SERVER['REMOTE_ADDR'] = '192.168.10.1';
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::getCloudFlareRealIP());
|
||||
$this->assertEquals('192.168.10.1', $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
$_SERVER['REMOTE_ADDR'] = '108.162.192.121';
|
||||
$this->assertEquals('192.168.134.241', Rhymix\Framework\Filters\IpFilter::getCloudFlareRealIP());
|
||||
$this->assertEquals('192.168.134.241', $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
unset($_SERVER['HTTP_CF_CONNECTING_IP']);
|
||||
$_SERVER['REMOTE_ADDR'] = '192.168.10.1';
|
||||
$this->assertFalse(Rhymix\Framework\Filters\IpFilter::getCloudFlareRealIP());
|
||||
$this->assertEquals('192.168.10.1', $_SERVER['REMOTE_ADDR']);
|
||||
}
|
||||
}
|
||||
58
tests/unit/framework/filters/MediaFilterTest.php
Normal file
58
tests/unit/framework/filters/MediaFilterTest.php
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
class MediaFilterTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
public function testWhitelists()
|
||||
{
|
||||
// iframe whitelist as array.
|
||||
$this->assertTrue(in_array('www.youtube.com/', Rhymix\Framework\Filters\MediaFilter::getIframeWhitelist()));
|
||||
$this->assertFalse(in_array('random-website.com/', Rhymix\Framework\Filters\MediaFilter::getIframeWhitelist()));
|
||||
|
||||
// iframe whitelist as regex.
|
||||
$this->assertTrue(strpos(Rhymix\Framework\Filters\MediaFilter::getIframeWhitelistRegex(), '|www\.youtube\.com/') !== false);
|
||||
$this->assertFalse(strpos(Rhymix\Framework\Filters\MediaFilter::getIframeWhitelistRegex(), 'www.youtube.com/') !== false);
|
||||
|
||||
// Match individual URL against iframe whitelist.
|
||||
$this->assertTrue(Rhymix\Framework\Filters\MediaFilter::matchIframeWhitelist('https://www.youtube.com/v'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\MediaFilter::matchIframeWhitelist('http://www-youtube.com/v'));
|
||||
|
||||
// object whitelist as array.
|
||||
$this->assertTrue(in_array('www.youtube.com/', Rhymix\Framework\Filters\MediaFilter::getObjectWhitelist()));
|
||||
$this->assertFalse(in_array('random-website.com/', Rhymix\Framework\Filters\MediaFilter::getObjectWhitelist()));
|
||||
|
||||
// object whitelist as regex.
|
||||
$this->assertTrue(strpos(Rhymix\Framework\Filters\MediaFilter::getObjectWhitelistRegex(), '|www\.youtube\.com/') !== false);
|
||||
$this->assertFalse(strpos(Rhymix\Framework\Filters\MediaFilter::getObjectWhitelistRegex(), 'www.youtube.com/') !== false);
|
||||
|
||||
// Match individual URL against object whitelist.
|
||||
$this->assertTrue(Rhymix\Framework\Filters\MediaFilter::matchObjectWhitelist('https://www.youtube.com/v'));
|
||||
$this->assertFalse(Rhymix\Framework\Filters\MediaFilter::matchObjectWhitelist('http://www-youtube.com/v'));
|
||||
}
|
||||
|
||||
public function testAddPrefix()
|
||||
{
|
||||
$this->assertFalse(Rhymix\Framework\Filters\MediaFilter::matchIframeWhitelist('http://some.custom.website.com/video.mp4'));
|
||||
Rhymix\Framework\Filters\MediaFilter::addIframePrefix('*.custom.website.com/');
|
||||
$this->assertTrue(Rhymix\Framework\Filters\MediaFilter::matchIframeWhitelist('http://some.custom.website.com/video.mp4'));
|
||||
|
||||
$this->assertFalse(Rhymix\Framework\Filters\MediaFilter::matchObjectWhitelist('http://some.custom.website.com/video.mp4'));
|
||||
Rhymix\Framework\Filters\MediaFilter::addObjectPrefix('*.custom.website.com/');
|
||||
$this->assertTrue(Rhymix\Framework\Filters\MediaFilter::matchObjectWhitelist('http://some.custom.website.com/video.mp4'));
|
||||
}
|
||||
|
||||
public function testRemoveEmbeddedMedia()
|
||||
{
|
||||
$tests = array(
|
||||
'<div><object></object></div>' => '<div></div>',
|
||||
'<div><object><embed></embed></object></div>' => '<div></div>',
|
||||
'<div><object><param /></object></div>' => '<div></div>',
|
||||
'<div><img class="foo" editor_component="multimedia_link" /></div>' => '<div></div>',
|
||||
'<div><img editor_component="multimedia_link"></img></div>' => '<div></div>',
|
||||
);
|
||||
|
||||
foreach ($tests as $from => $to)
|
||||
{
|
||||
$this->assertEquals($to, Rhymix\Framework\Filters\MediaFilter::removeEmbeddedMedia($from));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,68 +2,5 @@
|
|||
|
||||
class LegacyTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
public function testRemoveHackTag()
|
||||
{
|
||||
$tests = array(
|
||||
// remove iframe
|
||||
array(
|
||||
'<div class="frame"><iframe src="path/to/file.html"></iframe><p><a href="#iframe">IFrame</a></p></div>',
|
||||
// '<div class="frame"><iframe src="path/to/file.html"></iframe><p><a href="#iframe">IFrame</a></p></div>'
|
||||
'<div class="frame"><iframe></iframe><p><a href="#iframe">IFrame</a></p></div>'
|
||||
),
|
||||
// expression
|
||||
array(
|
||||
'<div class="dummy" style="xss:expr/*XSS*/ession(alert(\'XSS\'))">',
|
||||
'<div class="dummy"></div>'
|
||||
),
|
||||
// no quotes and no semicolon - http://ha.ckers.org/xss.html
|
||||
array(
|
||||
'<img src=javascript:alert(\'xss\')>',
|
||||
''
|
||||
),
|
||||
// embedded encoded tab to break up XSS - http://ha.ckers.org/xss.html
|
||||
array(
|
||||
'<IMG SRC="jav	ascript:alert(\'XSS\');">',
|
||||
'<img src="jav%20ascript%3Aalert(\'XSS\');" alt="jav ascript:alert(\'XSS\');" />'
|
||||
),
|
||||
// issue 178
|
||||
array(
|
||||
'<img src="invalid.jpg"\nonerror="alert(1)" />',
|
||||
'<img src="invalid.jpg" alt="invalid.jpg" />'
|
||||
),
|
||||
// issue 534
|
||||
array(
|
||||
'<img src=\'as"df dummy=\'"1234\'" 4321\' asdf/*/>*/" onerror="console.log(\'Yet another XSS\')">',
|
||||
'<img src="as" alt="as"df dummy=" />*/" onerror="console.log(\'Yet another XSS\')">'
|
||||
),
|
||||
// issue 602
|
||||
array(
|
||||
'<img alt="test" src="(http://static.naver.com/www/u/2010/0611/nmms_215646753.gif" onload="eval(String.fromCharCode(105,61,49,48,48,59,119,104,105,108,101, 40,105,62,48,41,97,108,101,114,116,40,40,105,45,45,41,43,39,48264,47564,32, 45908,32,53364,47533,54616,49464,50836,39,41,59));">',
|
||||
'<img alt="test" src="(http%3A//static.naver.com/www/u/2010/0611/nmms_215646753.gif" />'
|
||||
),
|
||||
// issue #1813 https://github.com/xpressengine/xe-core/issues/1813
|
||||
array(
|
||||
'<img src="?act=dispLayoutPreview" alt="dummy" />',
|
||||
'<img alt="dummy" />'
|
||||
),
|
||||
array(
|
||||
'<img src="?act =dispLayoutPreview" alt="dummy" />',
|
||||
'<img alt="dummy" />'
|
||||
),
|
||||
array(
|
||||
"<img src=\"?act\n=dispLayoutPreview\" alt=\"dummy\" />",
|
||||
'<img alt="dummy" />'
|
||||
),
|
||||
array(
|
||||
"<img src=\"?pam=act&a\nct =\r\n\tdispLayoutPreview\" alt=\"dummy\" />",
|
||||
'<img alt="dummy" />'
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($tests as $test)
|
||||
{
|
||||
$result = removeHackTag($test[0]);
|
||||
$this->assertEquals($test[1], $result);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
4
vendor/composer/autoload_classmap.php
vendored
4
vendor/composer/autoload_classmap.php
vendored
|
|
@ -237,8 +237,12 @@ return array(
|
|||
'HTMLPurifier_VarParser_Flexible' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php',
|
||||
'HTMLPurifier_VarParser_Native' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php',
|
||||
'HTMLPurifier_Zipper' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php',
|
||||
'Hautelook\\Phpass\\PasswordHash' => $vendorDir . '/hautelook/phpass/src/Hautelook/Phpass/PasswordHash.php',
|
||||
'MatthiasMullie\\Minify\\CSS' => $vendorDir . '/matthiasmullie/minify/src/CSS.php',
|
||||
'MatthiasMullie\\Minify\\Exception' => $vendorDir . '/matthiasmullie/minify/src/Exception.php',
|
||||
'MatthiasMullie\\Minify\\Exceptions\\BasicException' => $vendorDir . '/matthiasmullie/minify/src/Exceptions/BasicException.php',
|
||||
'MatthiasMullie\\Minify\\Exceptions\\FileImportException' => $vendorDir . '/matthiasmullie/minify/src/Exceptions/FileImportException.php',
|
||||
'MatthiasMullie\\Minify\\Exceptions\\IOException' => $vendorDir . '/matthiasmullie/minify/src/Exceptions/IOException.php',
|
||||
'MatthiasMullie\\Minify\\JS' => $vendorDir . '/matthiasmullie/minify/src/JS.php',
|
||||
'MatthiasMullie\\Minify\\Minify' => $vendorDir . '/matthiasmullie/minify/src/Minify.php',
|
||||
'MatthiasMullie\\PathConverter\\Converter' => $vendorDir . '/matthiasmullie/path-converter/src/Converter.php',
|
||||
|
|
|
|||
1
vendor/composer/autoload_namespaces.php
vendored
1
vendor/composer/autoload_namespaces.php
vendored
|
|
@ -9,5 +9,6 @@ return array(
|
|||
'Sunra\\PhpSimple\\HtmlDomParser' => array($vendorDir . '/sunra/php-simple-html-dom-parser/Src'),
|
||||
'Requests' => array($vendorDir . '/rmccue/requests/library'),
|
||||
'Michelf' => array($vendorDir . '/michelf/php-markdown'),
|
||||
'Hautelook' => array($vendorDir . '/hautelook/phpass/src'),
|
||||
'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'),
|
||||
);
|
||||
|
|
|
|||
163
vendor/composer/installed.json
vendored
163
vendor/composer/installed.json
vendored
|
|
@ -293,65 +293,6 @@
|
|||
"relative"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "matthiasmullie/minify",
|
||||
"version": "1.3.32",
|
||||
"version_normalized": "1.3.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matthiasmullie/minify.git",
|
||||
"reference": "140c714688908afcecde87338c8309233bdc2519"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/140c714688908afcecde87338c8309233bdc2519",
|
||||
"reference": "140c714688908afcecde87338c8309233bdc2519",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pcre": "*",
|
||||
"matthiasmullie/path-converter": "~1.0",
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"matthiasmullie/scrapbook": "~1.0",
|
||||
"phpunit/phpunit": "~4.8",
|
||||
"satooshi/php-coveralls": "~1.0"
|
||||
},
|
||||
"time": "2016-01-11 02:10:11",
|
||||
"bin": [
|
||||
"bin/minifycss",
|
||||
"bin/minifyjs"
|
||||
],
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MatthiasMullie\\Minify\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matthias Mullie",
|
||||
"email": "minify@mullie.eu",
|
||||
"homepage": "http://www.mullie.eu",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "CSS & JS minifier",
|
||||
"homepage": "http://www.minifier.org",
|
||||
"keywords": [
|
||||
"JS",
|
||||
"css",
|
||||
"javascript",
|
||||
"minifier",
|
||||
"minify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "swiftmailer/swiftmailer",
|
||||
"version": "v5.4.1",
|
||||
|
|
@ -492,5 +433,109 @@
|
|||
],
|
||||
"description": "Traditional FirePHPCore library for sending PHP variables to the browser.",
|
||||
"homepage": "https://github.com/firephp/firephp-core"
|
||||
},
|
||||
{
|
||||
"name": "matthiasmullie/minify",
|
||||
"version": "1.3.34",
|
||||
"version_normalized": "1.3.34.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matthiasmullie/minify.git",
|
||||
"reference": "272e46113404f66ced256659552a0cc074a7810f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/272e46113404f66ced256659552a0cc074a7810f",
|
||||
"reference": "272e46113404f66ced256659552a0cc074a7810f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pcre": "*",
|
||||
"matthiasmullie/path-converter": "~1.0",
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"matthiasmullie/scrapbook": "~1.0",
|
||||
"phpunit/phpunit": "~4.8"
|
||||
},
|
||||
"time": "2016-03-01 08:00:27",
|
||||
"bin": [
|
||||
"bin/minifycss",
|
||||
"bin/minifyjs"
|
||||
],
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MatthiasMullie\\Minify\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matthias Mullie",
|
||||
"email": "minify@mullie.eu",
|
||||
"homepage": "http://www.mullie.eu",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "CSS & JS minifier",
|
||||
"homepage": "http://www.minifier.org",
|
||||
"keywords": [
|
||||
"JS",
|
||||
"css",
|
||||
"javascript",
|
||||
"minifier",
|
||||
"minify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hautelook/phpass",
|
||||
"version": "0.3.4",
|
||||
"version_normalized": "0.3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hautelook/phpass.git",
|
||||
"reference": "f0217d804225822f9bdb0d392839029b0fcb0914"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/hautelook/phpass/zipball/f0217d804225822f9bdb0d392839029b0fcb0914",
|
||||
"reference": "f0217d804225822f9bdb0d392839029b0fcb0914",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"time": "2012-08-31 00:00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Hautelook": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Public Domain"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Solar Designer",
|
||||
"email": "solar@openwall.com",
|
||||
"homepage": "http://openwall.com/phpass/"
|
||||
}
|
||||
],
|
||||
"description": "Portable PHP password hashing framework",
|
||||
"homepage": "http://github.com/hautelook/phpass/",
|
||||
"keywords": [
|
||||
"blowfish",
|
||||
"crypt",
|
||||
"password",
|
||||
"security"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
2
vendor/hautelook/phpass/.gitignore
vendored
Normal file
2
vendor/hautelook/phpass/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
composer.lock
|
||||
vendor
|
||||
13
vendor/hautelook/phpass/.travis.yml
vendored
Normal file
13
vendor/hautelook/phpass/.travis.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
language: php
|
||||
|
||||
php:
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- hhvm
|
||||
|
||||
before_script:
|
||||
- composer install
|
||||
|
||||
script: cd Tests && phpunit --configuration phpunit.xml --coverage-text
|
||||
46
vendor/hautelook/phpass/README.md
vendored
Normal file
46
vendor/hautelook/phpass/README.md
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
Openwall Phpass, modernized
|
||||
===========================
|
||||
|
||||
[](http://travis-ci.org/hautelook/phpass)
|
||||
[](http://hhvm.h4cc.de/package/hautelook/phpass)
|
||||
|
||||
This is Openwall's [Phpass](http://openwall.com/phpass/), based on the 0.3 release, but modernized slightly:
|
||||
|
||||
- Namespaced
|
||||
- Composer support (Autoloading)
|
||||
- PHP 5 style
|
||||
- Unit Tested
|
||||
|
||||
The changes are minimal and only stylistic. The source code is in the public domain. We claim no ownership, but needed it for one of our projects, and wanted to make it available to other people as well.
|
||||
|
||||
## Installation ##
|
||||
|
||||
Add this requirement to your `composer.json` file and run `composer.phar install`:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"hautelook/phpass": "dev-master"
|
||||
}
|
||||
}
|
||||
|
||||
## Usage ##
|
||||
|
||||
The following example shows how to hash a password (to then store the hash in the database), and how to check whether a provided password is correct (hashes to the same value):
|
||||
|
||||
``` php
|
||||
<?php
|
||||
|
||||
namespace Your\Namespace;
|
||||
|
||||
use Hautelook\Phpass\PasswordHash;
|
||||
|
||||
require_once(__DIR__ . "/vendor/autoload.php");
|
||||
|
||||
$passwordHasher = new PasswordHash(8,false);
|
||||
|
||||
$password = $passwordHasher->HashPassword('secret');
|
||||
var_dump($password);
|
||||
|
||||
$passwordMatch = $passwordHasher->CheckPassword('secret', "$2a$08$0RK6Yw6j9kSIXrrEOc3dwuDPQuT78HgR0S3/ghOFDEpOGpOkARoSu");
|
||||
var_dump($passwordMatch);
|
||||
|
||||
52
vendor/hautelook/phpass/Tests/BasicTest.php
vendored
Normal file
52
vendor/hautelook/phpass/Tests/BasicTest.php
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
// namespace Hautelook\Phpass\Tests;
|
||||
|
||||
use Hautelook\Phpass\PasswordHash;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class BasicTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
const PORTABLE_HASH = '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0';
|
||||
public function testCorrectHash()
|
||||
{
|
||||
$hasher = new PasswordHash(8,false);
|
||||
$correct = 'test12345';
|
||||
$hash = $hasher->HashPassword($correct);
|
||||
|
||||
$this->assertTrue($hasher->CheckPassword($correct, $hash));
|
||||
}
|
||||
|
||||
public function testIncorrectHash()
|
||||
{
|
||||
$hasher = new PasswordHash(8,false);
|
||||
$correct = 'test12345';
|
||||
$hash = $hasher->HashPassword($correct);
|
||||
$wrong = 'test12346';
|
||||
|
||||
$this->assertFalse($hasher->CheckPassword($wrong, $hash));
|
||||
}
|
||||
|
||||
public function testWeakHashes()
|
||||
{
|
||||
$hasher = new PasswordHash(8, true);
|
||||
$correct = 'test12345';
|
||||
$hash = $hasher->HashPassword($correct);
|
||||
$wrong = 'test12346';
|
||||
|
||||
$this->assertTrue($hasher->CheckPassword($correct, $hash));
|
||||
$this->assertFalse($hasher->CheckPassword($wrong, $hash));
|
||||
}
|
||||
|
||||
public function testPortableHashes()
|
||||
{
|
||||
$hasher = new PasswordHash(8, true);
|
||||
$correct = 'test12345';
|
||||
$wrong = 'test12346';
|
||||
|
||||
$this->assertTrue($hasher->CheckPassword($correct, self::PORTABLE_HASH));
|
||||
$this->assertFalse($hasher->CheckPassword($wrong, self::PORTABLE_HASH));
|
||||
}
|
||||
}
|
||||
14
vendor/hautelook/phpass/Tests/bootstrap.php
vendored
Normal file
14
vendor/hautelook/phpass/Tests/bootstrap.php
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
function includeIfExists($file)
|
||||
{
|
||||
if (file_exists($file)) {
|
||||
return include $file;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!$loader = includeIfExists(__DIR__.'/../vendor/autoload.php'))) {
|
||||
die('You must set up the project dependencies, run the following commands:'.PHP_EOL.
|
||||
'curl -s http://getcomposer.org/installer | php'.PHP_EOL.
|
||||
'php composer.phar install'.PHP_EOL);
|
||||
}
|
||||
32
vendor/hautelook/phpass/Tests/phpunit.xml
vendored
Normal file
32
vendor/hautelook/phpass/Tests/phpunit.xml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<phpunit bootstrap="./bootstrap.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="true" >
|
||||
|
||||
<logging>
|
||||
<!-- <log type="coverage-text" target="php://stdout"/> -->
|
||||
<!--showUncoveredFiles="true"/>-->
|
||||
<!--<log type="coverage-html" target="/tmp/report" charset="UTF-8"-->
|
||||
<!--yui="true" highlight="false"-->
|
||||
<!--lowUpperBound="35" highLowerBound="70"/>-->
|
||||
<!--<log type="testdox-html" target="/tmp/testdox.html"/>-->
|
||||
</logging>
|
||||
<testsuite name="Phpass Test Suite">
|
||||
<directory>.</directory>
|
||||
</testsuite>
|
||||
<filter>
|
||||
<blacklist>
|
||||
<directory suffix=".php">../</directory>
|
||||
</blacklist>
|
||||
<whitelist>
|
||||
<directory suffix=".php">../src/Hautelook</directory>
|
||||
<exclude>
|
||||
<directory suffix=".phtml">../</directory>
|
||||
<file>./bootstrap.php</file>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
22
vendor/hautelook/phpass/composer.json
vendored
Normal file
22
vendor/hautelook/phpass/composer.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "hautelook/phpass",
|
||||
"type": "library",
|
||||
"time" : "2012-08-31",
|
||||
"license" : "Public Domain",
|
||||
"description": "Portable PHP password hashing framework",
|
||||
"keywords": ["Blowfish", "crypt", "password", "security"],
|
||||
"homepage": "http://github.com/hautelook/phpass/",
|
||||
"authors": [
|
||||
{
|
||||
"name" : "Solar Designer",
|
||||
"email": "solar@openwall.com",
|
||||
"homepage": "http://openwall.com/phpass/"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {"Hautelook": "src/"}
|
||||
}
|
||||
}
|
||||
21
vendor/hautelook/phpass/lib/Makefile
vendored
Normal file
21
vendor/hautelook/phpass/lib/Makefile
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#
|
||||
# Written by Solar Designer and placed in the public domain.
|
||||
# See crypt_private.c for more information.
|
||||
#
|
||||
CC = gcc
|
||||
LD = $(CC)
|
||||
RM = rm -f
|
||||
CFLAGS = -Wall -O2 -fomit-frame-pointer -funroll-loops
|
||||
LDFLAGS = -s
|
||||
LIBS = -lcrypto
|
||||
|
||||
all: crypt_private-test
|
||||
|
||||
crypt_private-test: crypt_private-test.o
|
||||
$(LD) $(LDFLAGS) $(LIBS) crypt_private-test.o -o $@
|
||||
|
||||
crypt_private-test.o: crypt_private.c
|
||||
$(CC) -c $(CFLAGS) crypt_private.c -DTEST -o $@
|
||||
|
||||
clean:
|
||||
$(RM) crypt_private-test*
|
||||
106
vendor/hautelook/phpass/lib/crypt_private.c
vendored
Normal file
106
vendor/hautelook/phpass/lib/crypt_private.c
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* This code exists for the sole purpose to serve as another implementation
|
||||
* of the "private" password hashing method implemened in PasswordHash.php
|
||||
* and thus to confirm that these password hashes are indeed calculated as
|
||||
* intended.
|
||||
*
|
||||
* Other uses of this code are discouraged. There are much better password
|
||||
* hashing algorithms available to C programmers; one of those is bcrypt:
|
||||
*
|
||||
* http://www.openwall.com/crypt/
|
||||
*
|
||||
* Written by Solar Designer <solar at openwall.com> in 2005 and placed in
|
||||
* the public domain.
|
||||
*
|
||||
* There's absolutely no warranty.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <openssl/md5.h>
|
||||
|
||||
#ifdef TEST
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
static char *itoa64 =
|
||||
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
static void encode64(char *dst, char *src, int count)
|
||||
{
|
||||
int i, value;
|
||||
|
||||
i = 0;
|
||||
do {
|
||||
value = (unsigned char)src[i++];
|
||||
*dst++ = itoa64[value & 0x3f];
|
||||
if (i < count)
|
||||
value |= (unsigned char)src[i] << 8;
|
||||
*dst++ = itoa64[(value >> 6) & 0x3f];
|
||||
if (i++ >= count)
|
||||
break;
|
||||
if (i < count)
|
||||
value |= (unsigned char)src[i] << 16;
|
||||
*dst++ = itoa64[(value >> 12) & 0x3f];
|
||||
if (i++ >= count)
|
||||
break;
|
||||
*dst++ = itoa64[(value >> 18) & 0x3f];
|
||||
} while (i < count);
|
||||
}
|
||||
|
||||
char *crypt_private(char *password, char *setting)
|
||||
{
|
||||
static char output[35];
|
||||
MD5_CTX ctx;
|
||||
char hash[MD5_DIGEST_LENGTH];
|
||||
char *p, *salt;
|
||||
int count_log2, length, count;
|
||||
|
||||
strcpy(output, "*0");
|
||||
if (!strncmp(setting, output, 2))
|
||||
output[1] = '1';
|
||||
|
||||
if (strncmp(setting, "$P$", 3))
|
||||
return output;
|
||||
|
||||
p = strchr(itoa64, setting[3]);
|
||||
if (!p)
|
||||
return output;
|
||||
count_log2 = p - itoa64;
|
||||
if (count_log2 < 7 || count_log2 > 30)
|
||||
return output;
|
||||
|
||||
salt = setting + 4;
|
||||
if (strlen(salt) < 8)
|
||||
return output;
|
||||
|
||||
length = strlen(password);
|
||||
|
||||
MD5_Init(&ctx);
|
||||
MD5_Update(&ctx, salt, 8);
|
||||
MD5_Update(&ctx, password, length);
|
||||
MD5_Final(hash, &ctx);
|
||||
|
||||
count = 1 << count_log2;
|
||||
do {
|
||||
MD5_Init(&ctx);
|
||||
MD5_Update(&ctx, hash, MD5_DIGEST_LENGTH);
|
||||
MD5_Update(&ctx, password, length);
|
||||
MD5_Final(hash, &ctx);
|
||||
} while (--count);
|
||||
|
||||
memcpy(output, setting, 12);
|
||||
encode64(&output[12], hash, MD5_DIGEST_LENGTH);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 3) return 1;
|
||||
|
||||
puts(crypt_private(argv[1], argv[2]));
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
314
vendor/hautelook/phpass/src/Hautelook/Phpass/PasswordHash.php
vendored
Normal file
314
vendor/hautelook/phpass/src/Hautelook/Phpass/PasswordHash.php
vendored
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
<?php
|
||||
|
||||
namespace Hautelook\Phpass;
|
||||
|
||||
/**
|
||||
*
|
||||
* Portable PHP password hashing framework.
|
||||
*
|
||||
* Version 0.3 / genuine.
|
||||
*
|
||||
* Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
|
||||
*
|
||||
* There's absolutely no warranty.
|
||||
*
|
||||
* The homepage URL for this framework is:
|
||||
*
|
||||
* http://www.openwall.com/phpass/
|
||||
*
|
||||
* Please be sure to update the Version line if you edit this file in any way.
|
||||
* It is suggested that you leave the main version number intact, but indicate
|
||||
* your project name (after the slash) and add your own revision information.
|
||||
*
|
||||
* Please do not change the "private" password hashing method implemented in
|
||||
* here, thereby making your hashes incompatible. However, if you must, please
|
||||
* change the hash type identifier (the "$P$") to something different.
|
||||
*
|
||||
* Obviously, since this code is in the public domain, the above are not
|
||||
* requirements (there can be none), but merely suggestions.
|
||||
*
|
||||
* @author Solar Designer <solar@openwall.com>
|
||||
*/
|
||||
class PasswordHash
|
||||
{
|
||||
private $itoa64;
|
||||
private $iteration_count_log2;
|
||||
private $portable_hashes;
|
||||
private $random_state;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $iteration_count_log2
|
||||
* @param boolean $portable_hashes
|
||||
*/
|
||||
public function __construct($iteration_count_log2, $portable_hashes)
|
||||
{
|
||||
$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) {
|
||||
$iteration_count_log2 = 8;
|
||||
}
|
||||
$this->iteration_count_log2 = $iteration_count_log2;
|
||||
|
||||
$this->portable_hashes = $portable_hashes;
|
||||
|
||||
$this->random_state = microtime();
|
||||
if (function_exists('getmypid')) {
|
||||
$this->random_state .= getmypid();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $count
|
||||
* @return String
|
||||
*/
|
||||
public function get_random_bytes($count)
|
||||
{
|
||||
$output = '';
|
||||
if (@is_readable('/dev/urandom') &&
|
||||
($fh = @fopen('/dev/urandom', 'rb'))) {
|
||||
$output = fread($fh, $count);
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
if (strlen($output) < $count) {
|
||||
$output = '';
|
||||
for ($i = 0; $i < $count; $i += 16) {
|
||||
$this->random_state =
|
||||
md5(microtime() . $this->random_state);
|
||||
$output .=
|
||||
pack('H*', md5($this->random_state));
|
||||
}
|
||||
$output = substr($output, 0, $count);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $input
|
||||
* @param int $count
|
||||
* @return String
|
||||
*/
|
||||
public function encode64($input, $count)
|
||||
{
|
||||
$output = '';
|
||||
$i = 0;
|
||||
do {
|
||||
$value = ord($input[$i++]);
|
||||
$output .= $this->itoa64[$value & 0x3f];
|
||||
if ($i < $count) {
|
||||
$value |= ord($input[$i]) << 8;
|
||||
}
|
||||
$output .= $this->itoa64[($value >> 6) & 0x3f];
|
||||
if ($i++ >= $count) {
|
||||
break;
|
||||
}
|
||||
if ($i < $count) {
|
||||
$value |= ord($input[$i]) << 16;
|
||||
}
|
||||
$output .= $this->itoa64[($value >> 12) & 0x3f];
|
||||
if ($i++ >= $count) {
|
||||
break;
|
||||
}
|
||||
$output .= $this->itoa64[($value >> 18) & 0x3f];
|
||||
} while ($i < $count);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $input
|
||||
* @return String
|
||||
*/
|
||||
public function gensalt_private($input)
|
||||
{
|
||||
$output = '$P$';
|
||||
$output .= $this->itoa64[min($this->iteration_count_log2 +
|
||||
((PHP_VERSION >= '5') ? 5 : 3), 30)];
|
||||
$output .= $this->encode64($input, 6);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $password
|
||||
* @param String $setting
|
||||
* @return String
|
||||
*/
|
||||
public function crypt_private($password, $setting)
|
||||
{
|
||||
$output = '*0';
|
||||
if (substr($setting, 0, 2) == $output) {
|
||||
$output = '*1';
|
||||
}
|
||||
|
||||
$id = substr($setting, 0, 3);
|
||||
# We use "$P$", phpBB3 uses "$H$" for the same thing
|
||||
if ($id != '$P$' && $id != '$H$') {
|
||||
return $output;
|
||||
}
|
||||
|
||||
$count_log2 = strpos($this->itoa64, $setting[3]);
|
||||
if ($count_log2 < 7 || $count_log2 > 30) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
$count = 1 << $count_log2;
|
||||
|
||||
$salt = substr($setting, 4, 8);
|
||||
if (strlen($salt) != 8) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
// We're kind of forced to use MD5 here since it's the only
|
||||
// cryptographic primitive available in all versions of PHP
|
||||
// currently in use. To implement our own low-level crypto
|
||||
// in PHP would result in much worse performance and
|
||||
// consequently in lower iteration counts and hashes that are
|
||||
// quicker to crack (by non-PHP code).
|
||||
if (PHP_VERSION >= '5') {
|
||||
$hash = md5($salt . $password, TRUE);
|
||||
do {
|
||||
$hash = md5($hash . $password, TRUE);
|
||||
} while (--$count);
|
||||
} else {
|
||||
$hash = pack('H*', md5($salt . $password));
|
||||
do {
|
||||
$hash = pack('H*', md5($hash . $password));
|
||||
} while (--$count);
|
||||
}
|
||||
|
||||
$output = substr($setting, 0, 12);
|
||||
$output .= $this->encode64($hash, 16);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $input
|
||||
* @return String
|
||||
*/
|
||||
public function gensalt_extended($input)
|
||||
{
|
||||
$count_log2 = min($this->iteration_count_log2 + 8, 24);
|
||||
// This should be odd to not reveal weak DES keys, and the
|
||||
// maximum valid value is (2**24 - 1) which is odd anyway.
|
||||
$count = (1 << $count_log2) - 1;
|
||||
|
||||
$output = '_';
|
||||
$output .= $this->itoa64[$count & 0x3f];
|
||||
$output .= $this->itoa64[($count >> 6) & 0x3f];
|
||||
$output .= $this->itoa64[($count >> 12) & 0x3f];
|
||||
$output .= $this->itoa64[($count >> 18) & 0x3f];
|
||||
|
||||
$output .= $this->encode64($input, 3);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $input
|
||||
* @return String
|
||||
*/
|
||||
public function gensalt_blowfish($input)
|
||||
{
|
||||
// This one needs to use a different order of characters and a
|
||||
// different encoding scheme from the one in encode64() above.
|
||||
// We care because the last character in our encoded string will
|
||||
// only represent 2 bits. While two known implementations of
|
||||
// bcrypt will happily accept and correct a salt string which
|
||||
// has the 4 unused bits set to non-zero, we do not want to take
|
||||
// chances and we also do not want to waste an additional byte
|
||||
// of entropy.
|
||||
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
$output = '$2a$';
|
||||
$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
|
||||
$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
|
||||
$output .= '$';
|
||||
|
||||
$i = 0;
|
||||
do {
|
||||
$c1 = ord($input[$i++]);
|
||||
$output .= $itoa64[$c1 >> 2];
|
||||
$c1 = ($c1 & 0x03) << 4;
|
||||
if ($i >= 16) {
|
||||
$output .= $itoa64[$c1];
|
||||
break;
|
||||
}
|
||||
|
||||
$c2 = ord($input[$i++]);
|
||||
$c1 |= $c2 >> 4;
|
||||
$output .= $itoa64[$c1];
|
||||
$c1 = ($c2 & 0x0f) << 2;
|
||||
|
||||
$c2 = ord($input[$i++]);
|
||||
$c1 |= $c2 >> 6;
|
||||
$output .= $itoa64[$c1];
|
||||
$output .= $itoa64[$c2 & 0x3f];
|
||||
} while (1);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $password
|
||||
*/
|
||||
public function HashPassword($password)
|
||||
{
|
||||
$random = '';
|
||||
|
||||
if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) {
|
||||
$random = $this->get_random_bytes(16);
|
||||
$hash =
|
||||
crypt($password, $this->gensalt_blowfish($random));
|
||||
if (strlen($hash) == 60) {
|
||||
return $hash;
|
||||
}
|
||||
}
|
||||
|
||||
if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) {
|
||||
if (strlen($random) < 3) {
|
||||
$random = $this->get_random_bytes(3);
|
||||
}
|
||||
$hash =
|
||||
crypt($password, $this->gensalt_extended($random));
|
||||
if (strlen($hash) == 20) {
|
||||
return $hash;
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen($random) < 6) {
|
||||
$random = $this->get_random_bytes(6);
|
||||
}
|
||||
|
||||
$hash =
|
||||
$this->crypt_private($password,
|
||||
$this->gensalt_private($random));
|
||||
if (strlen($hash) == 34) {
|
||||
return $hash;
|
||||
}
|
||||
|
||||
// Returning '*' on error is safe here, but would _not_ be safe
|
||||
// in a crypt(3)-like function used _both_ for generating new
|
||||
// hashes and for validating passwords against existing hashes.
|
||||
return '*';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $password
|
||||
* @param String $stored_hash
|
||||
* @return boolean
|
||||
*/
|
||||
public function CheckPassword($password, $stored_hash)
|
||||
{
|
||||
$hash = $this->crypt_private($password, $stored_hash);
|
||||
if ($hash[0] == '*') {
|
||||
$hash = crypt($password, $stored_hash);
|
||||
}
|
||||
|
||||
return $hash === $stored_hash;
|
||||
}
|
||||
}
|
||||
3
vendor/matthiasmullie/minify/.coveralls.yml
vendored
3
vendor/matthiasmullie/minify/.coveralls.yml
vendored
|
|
@ -1,3 +0,0 @@
|
|||
src_dir: src
|
||||
coverage_clover: build/logs/clover.xml
|
||||
json_path: build/logs/coveralls-upload.json
|
||||
59
vendor/matthiasmullie/minify/CONTRIBUTING.md
vendored
Normal file
59
vendor/matthiasmullie/minify/CONTRIBUTING.md
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# How to contribute
|
||||
|
||||
|
||||
## Issues
|
||||
|
||||
When [filing bugs](https://github.com/matthiasmullie/minify/issues/new),
|
||||
try to be as thorough as possible:
|
||||
* What version did you use?
|
||||
* What did you try to do? ***Please post the relevant parts of your code.***
|
||||
* What went wrong? ***Please include error messages, if any.***
|
||||
* What was the expected result?
|
||||
|
||||
|
||||
## Pull requests
|
||||
|
||||
Bug fixes and general improvements to the existing codebase are always welcome.
|
||||
New features are also welcome, but will be judged on an individual basis. If
|
||||
you'd rather not risk wasting your time implementing a new feature only to see
|
||||
it turned down, please start the discussion by
|
||||
[opening an issue](https://github.com/matthiasmullie/minify/issues/new).
|
||||
|
||||
Don't forget to add your changes to the [changelog](CHANGELOG.md).
|
||||
|
||||
|
||||
### Testing
|
||||
|
||||
Please include tests for every change or addition to the code.
|
||||
To run the complete test suite:
|
||||
|
||||
```sh
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
When submitting a new pull request, please make sure that that the test suite
|
||||
passes (Travis CI will run it & report back on your pull request.)
|
||||
|
||||
To run the tests on Windows, run `tests/convert_symlinks_to_windows_style.sh`
|
||||
from the command line in order to convert Linux-style test symlinks to
|
||||
Windows-style.
|
||||
|
||||
|
||||
### Coding standards
|
||||
|
||||
All code must follow [PSR-2](http://www.php-fig.org/psr/psr-2/). Just make sure
|
||||
to run php-cs-fixer before submitting the code, it'll take care of the
|
||||
formatting for you:
|
||||
|
||||
```sh
|
||||
vendor/bin/php-cs-fixer fix src
|
||||
vendor/bin/php-cs-fixer fix tests
|
||||
```
|
||||
|
||||
Document the code thoroughly!
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Note that minify is MIT-licensed, which basically allows anyone to do
|
||||
anything they like with it, without restriction.
|
||||
3
vendor/matthiasmullie/minify/composer.json
vendored
3
vendor/matthiasmullie/minify/composer.json
vendored
|
|
@ -20,8 +20,7 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"matthiasmullie/scrapbook": "~1.0",
|
||||
"phpunit/phpunit": "~4.8",
|
||||
"satooshi/php-coveralls": "~1.0"
|
||||
"phpunit/phpunit": "~4.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
|
|||
78
vendor/matthiasmullie/minify/src/CSS.php
vendored
78
vendor/matthiasmullie/minify/src/CSS.php
vendored
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace MatthiasMullie\Minify;
|
||||
|
||||
use MatthiasMullie\Minify\Exceptions\FileImportException;
|
||||
use MatthiasMullie\PathConverter\Converter;
|
||||
|
||||
/**
|
||||
|
|
@ -68,7 +69,7 @@ class CSS extends Minify
|
|||
/**
|
||||
* Move any import statements to the top.
|
||||
*
|
||||
* @param $content string Nearly finished CSS content
|
||||
* @param string $content Nearly finished CSS content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
|
@ -94,12 +95,15 @@ class CSS extends Minify
|
|||
* @import's will be loaded and their content merged into the original file,
|
||||
* to save HTTP requests.
|
||||
*
|
||||
* @param string $source The file to combine imports for.
|
||||
* @param string $content The CSS content to combine imports for.
|
||||
* @param string $source The file to combine imports for.
|
||||
* @param string $content The CSS content to combine imports for.
|
||||
* @param string[] $parents Parent paths, for circular reference checks.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws FileImportException
|
||||
*/
|
||||
protected function combineImports($source, $content)
|
||||
protected function combineImports($source, $content, $parents)
|
||||
{
|
||||
$importRegexes = array(
|
||||
// @import url(xxx)
|
||||
|
|
@ -208,14 +212,20 @@ class CSS extends Minify
|
|||
|
||||
// only replace the import with the content if we can grab the
|
||||
// content of the file
|
||||
if (strlen($importPath) < PHP_MAXPATHLEN && file_exists($importPath) && is_file($importPath)) {
|
||||
if ($this->canImportFile($importPath)) {
|
||||
// check if current file was not imported previously in the same
|
||||
// import chain.
|
||||
if (in_array($importPath, $parents)) {
|
||||
throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
|
||||
}
|
||||
|
||||
// grab referenced file & minify it (which may include importing
|
||||
// yet other @import statements recursively)
|
||||
$minifier = new static($importPath);
|
||||
$importContent = $minifier->execute($source);
|
||||
$importContent = $minifier->execute($source, $parents);
|
||||
|
||||
// check if this is only valid for certain media
|
||||
if ($match['media']) {
|
||||
if (!empty($match['media'])) {
|
||||
$importContent = '@media '.$match['media'].'{'.$importContent.'}';
|
||||
}
|
||||
|
||||
|
|
@ -259,20 +269,15 @@ class CSS extends Minify
|
|||
|
||||
// only replace the import with the content if we're able to get
|
||||
// the content of the file, and it's relatively small
|
||||
$import = file_exists($path);
|
||||
$import = $import && is_file($path);
|
||||
$import = $import && filesize($path) <= $this->maxImportSize * 1024;
|
||||
if (!$import) {
|
||||
continue;
|
||||
if ($this->canImportFile($path) && $this->canImportBySize($path)) {
|
||||
// grab content && base64-ize
|
||||
$importContent = $this->load($path);
|
||||
$importContent = base64_encode($importContent);
|
||||
|
||||
// build replacement
|
||||
$search[] = $match[0];
|
||||
$replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
|
||||
}
|
||||
|
||||
// grab content && base64-ize
|
||||
$importContent = $this->load($path);
|
||||
$importContent = base64_encode($importContent);
|
||||
|
||||
// build replacement
|
||||
$search[] = $match[0];
|
||||
$replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
|
||||
}
|
||||
|
||||
// replace the import statements
|
||||
|
|
@ -286,15 +291,16 @@ class CSS extends Minify
|
|||
* Minify the data.
|
||||
* Perform CSS optimizations.
|
||||
*
|
||||
* @param string[optional] $path Path to write the data to.
|
||||
* @param string[optional] $path Path to write the data to.
|
||||
* @param string[] $parents Parent paths, for circular reference checks.
|
||||
*
|
||||
* @return string The minified data.
|
||||
*/
|
||||
public function execute($path = null)
|
||||
public function execute($path = null, $parents = array())
|
||||
{
|
||||
$content = '';
|
||||
|
||||
// loop files
|
||||
// loop css data (raw data and files)
|
||||
foreach ($this->data as $source => $css) {
|
||||
/*
|
||||
* Let's first take out strings & comments, since we can't just remove
|
||||
|
|
@ -314,8 +320,9 @@ class CSS extends Minify
|
|||
// restore the string we've extracted earlier
|
||||
$css = $this->restoreExtractedData($css);
|
||||
|
||||
$source = $source ?: '';
|
||||
$css = $this->combineImports($source, $css);
|
||||
$source = is_int($source) ? '' : $source;
|
||||
$parents = $source ? array_merge($parents, array($source)) : $parents;
|
||||
$css = $this->combineImports($source, $css, $parents);
|
||||
$css = $this->importFiles($source, $css);
|
||||
|
||||
/*
|
||||
|
|
@ -443,8 +450,15 @@ class CSS extends Minify
|
|||
// determine if it's a url() or an @import match
|
||||
$type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
|
||||
|
||||
// attempting to interpret GET-params makes no sense, so let's discard them for awhile
|
||||
$params = strrchr($match['path'], '?');
|
||||
$url = $params ? substr($match['path'], 0, -strlen($params)) : $match['path'];
|
||||
|
||||
// fix relative url
|
||||
$url = $converter->convert($match['path']);
|
||||
$url = $converter->convert($url);
|
||||
|
||||
// now that the path has been converted, re-apply GET-params
|
||||
$url .= $params;
|
||||
|
||||
// build replacement
|
||||
$search[] = $match[0];
|
||||
|
|
@ -569,4 +583,16 @@ class CSS extends Minify
|
|||
|
||||
return trim($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file is small enough to be imported.
|
||||
*
|
||||
* @param string $path The path to the file.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function canImportBySize($path)
|
||||
{
|
||||
return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
namespace MatthiasMullie\Minify;
|
||||
|
||||
/**
|
||||
* @deprecated Use Exceptions\BasicException instead
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
abstract class Exception extends \Exception
|
||||
{
|
||||
}
|
||||
|
|
|
|||
12
vendor/matthiasmullie/minify/src/Exceptions/BasicException.php
vendored
Normal file
12
vendor/matthiasmullie/minify/src/Exceptions/BasicException.php
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace MatthiasMullie\Minify\Exceptions;
|
||||
|
||||
use MatthiasMullie\Minify\Exception;
|
||||
|
||||
/**
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
*/
|
||||
abstract class BasicException extends Exception
|
||||
{
|
||||
}
|
||||
10
vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php
vendored
Normal file
10
vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace MatthiasMullie\Minify\Exceptions;
|
||||
|
||||
/**
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
*/
|
||||
class FileImportException extends BasicException
|
||||
{
|
||||
}
|
||||
10
vendor/matthiasmullie/minify/src/Exceptions/IOException.php
vendored
Normal file
10
vendor/matthiasmullie/minify/src/Exceptions/IOException.php
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace MatthiasMullie\Minify\Exceptions;
|
||||
|
||||
/**
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
*/
|
||||
class IOException extends BasicException
|
||||
{
|
||||
}
|
||||
22
vendor/matthiasmullie/minify/src/JS.php
vendored
22
vendor/matthiasmullie/minify/src/JS.php
vendored
|
|
@ -263,12 +263,20 @@ class JS extends Minify
|
|||
// strip whitespace that ends in (or next line begin with) an operator
|
||||
// that allows statements to be broken up over multiple lines
|
||||
unset($before['+'], $before['-'], $after['+'], $after['-']);
|
||||
$content = preg_replace('/('.implode('|', $before).')\s+/', '\\1', $content);
|
||||
$content = preg_replace('/\s+('.implode('|', $after).')/', '\\1', $content);
|
||||
$content = preg_replace(
|
||||
array(
|
||||
'/('.implode('|', $before).')\s+/',
|
||||
'/\s+('.implode('|', $after).')/',
|
||||
), '\\1', $content
|
||||
);
|
||||
|
||||
// make sure + and - can't be mistaken for, or joined into ++ and --
|
||||
$content = preg_replace('/(?<![\+\-])\s*([\+\-])(?![\+\-])/', '\\1', $content);
|
||||
$content = preg_replace('/(?<![\+\-])([\+\-])\s*(?![\+\-])/', '\\1', $content);
|
||||
$content = preg_replace(
|
||||
array(
|
||||
'/(?<![\+\-])\s*([\+\-])(?![\+\-])/',
|
||||
'/(?<![\+\-])([\+\-])\s*(?![\+\-])/',
|
||||
), '\\1', $content
|
||||
);
|
||||
|
||||
/*
|
||||
* We didn't strip whitespace after a couple of operators because they
|
||||
|
|
@ -427,7 +435,7 @@ class JS extends Minify
|
|||
* we want to use property notation on) - this is to make sure
|
||||
* standalone ['value'] arrays aren't confused for keys-of-an-array.
|
||||
* We can (and only have to) check the last character, because PHP's
|
||||
* regex implementation doesn't allow un-fixed-length lookbehind
|
||||
* regex implementation doesn't allow unfixed-length look-behind
|
||||
* assertions.
|
||||
*/
|
||||
preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar);
|
||||
|
|
@ -436,8 +444,8 @@ class JS extends Minify
|
|||
/*
|
||||
* Make sure word preceding the ['value'] is not a keyword, e.g.
|
||||
* return['x']. Because -again- PHP's regex implementation doesn't allow
|
||||
* un-fixed-length lookbehind assertions, I'm just going to do a lot of
|
||||
* separate lookbehind assertions, one for each keyword.
|
||||
* unfixed-length look-behind assertions, I'm just going to do a lot of
|
||||
* separate look-behind assertions, one for each keyword.
|
||||
*/
|
||||
$keywords = $this->getKeywordsForRegex($keywords);
|
||||
$keywords = '(?<!'.implode(')(?<!', $keywords).')';
|
||||
|
|
|
|||
139
vendor/matthiasmullie/minify/src/Minify.php
vendored
139
vendor/matthiasmullie/minify/src/Minify.php
vendored
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace MatthiasMullie\Minify;
|
||||
|
||||
use MatthiasMullie\Minify\Exceptions\IOException;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -76,52 +77,6 @@ abstract class Minify
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data.
|
||||
*
|
||||
* @param string $data Either a path to a file or the content itself.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function load($data)
|
||||
{
|
||||
// check if the data is a file
|
||||
if (strlen($data) < PHP_MAXPATHLEN && file_exists($data) && is_file($data)) {
|
||||
$data = file_get_contents($data);
|
||||
|
||||
// strip BOM, if any
|
||||
if (substr($data, 0, 3) == "\xef\xbb\xbf") {
|
||||
$data = substr($data, 3);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save to file.
|
||||
*
|
||||
* @param string $content The minified data.
|
||||
* @param string $path The path to save the minified data to.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function save($content, $path)
|
||||
{
|
||||
// create file & open for writing
|
||||
if (($handler = @fopen($path, 'w')) === false) {
|
||||
throw new Exception('The file "'.$path.'" could not be opened. Check if PHP has enough permissions.');
|
||||
}
|
||||
|
||||
// write to file
|
||||
if (@fwrite($handler, $content) === false) {
|
||||
throw new Exception('The file "'.$path.'" could not be written to. Check if PHP has enough permissions.');
|
||||
}
|
||||
|
||||
// close the file
|
||||
@fclose($handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the data & (optionally) saves it to a file.
|
||||
*
|
||||
|
|
@ -186,13 +141,50 @@ abstract class Minify
|
|||
*/
|
||||
abstract public function execute($path = null);
|
||||
|
||||
/**
|
||||
* Load data.
|
||||
*
|
||||
* @param string $data Either a path to a file or the content itself.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function load($data)
|
||||
{
|
||||
// check if the data is a file
|
||||
if ($this->canImportFile($data)) {
|
||||
$data = file_get_contents($data);
|
||||
|
||||
// strip BOM, if any
|
||||
if (substr($data, 0, 3) == "\xef\xbb\xbf") {
|
||||
$data = substr($data, 3);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save to file.
|
||||
*
|
||||
* @param string $content The minified data.
|
||||
* @param string $path The path to save the minified data to.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function save($content, $path)
|
||||
{
|
||||
$handler = $this->openFileForWriting($path);
|
||||
|
||||
$this->writeToFile($handler, $content);
|
||||
|
||||
@fclose($handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a pattern to execute against the source content.
|
||||
*
|
||||
* @param string $pattern PCRE pattern.
|
||||
* @param string|callable $replacement Replacement value for matched pattern.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function registerPattern($pattern, $replacement = '')
|
||||
{
|
||||
|
|
@ -316,7 +308,7 @@ abstract class Minify
|
|||
* placeholder text, so we've rid all strings from characters that may be
|
||||
* misinterpreted. Original string content will be saved in $this->extracted
|
||||
* and after doing all other minifying, we can restore the original content
|
||||
* via restoreStrings()
|
||||
* via restoreStrings().
|
||||
*
|
||||
* @param string[optional] $chars
|
||||
*/
|
||||
|
|
@ -325,7 +317,8 @@ abstract class Minify
|
|||
// PHP only supports $this inside anonymous functions since 5.4
|
||||
$minifier = $this;
|
||||
$callback = function ($match) use ($minifier) {
|
||||
if (!$match[1]) {
|
||||
// check the second index here, because the first always contains a quote
|
||||
if ($match[2] === '') {
|
||||
/*
|
||||
* Empty strings need no placeholder; they can't be confused for
|
||||
* anything else anyway.
|
||||
|
|
@ -379,4 +372,50 @@ abstract class Minify
|
|||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the path is a regular file and can be read.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function canImportFile($path)
|
||||
{
|
||||
return strlen($path) < PHP_MAXPATHLEN && is_file($path) && is_readable($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open file specified by $path for writing.
|
||||
*
|
||||
* @param string $path The path to the file.
|
||||
*
|
||||
* @return resource Specifier for the target file.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function openFileForWriting($path)
|
||||
{
|
||||
if (($handler = @fopen($path, 'w')) === false) {
|
||||
throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.');
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to write $content to the file specified by $handler. $path is used for printing exceptions.
|
||||
*
|
||||
* @param resource $handler The resource to write to.
|
||||
* @param string $content The content to write.
|
||||
* @param string $path The path to the file (for exception printing only).
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function writeToFile($handler, $content, $path = '')
|
||||
{
|
||||
if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) {
|
||||
throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue