Skip to content

Commit

Permalink
Windows and Intl fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-grekas committed Aug 25, 2015
1 parent 41103df commit e044e77
Show file tree
Hide file tree
Showing 20 changed files with 223 additions and 131 deletions.
4 changes: 4 additions & 0 deletions src/Symfony/Component/Console/Tests/ApplicationTest.php
Expand Up @@ -491,6 +491,10 @@ public function testRenderException()

public function testRenderExceptionWithDoubleWidthCharacters()
{
if (!function_exists('mb_strwidth')) {
$this->markTestSkipped('The "mb_strwidth" function is not available');
}

$application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
$application->setAutoExit(false);
$application->expects($this->any())
Expand Down
45 changes: 34 additions & 11 deletions src/Symfony/Component/DomCrawler/Crawler.php
Expand Up @@ -156,20 +156,43 @@ public function addHtmlContent($content, $charset = 'UTF-8')
$dom = new \DOMDocument('1.0', $charset);
$dom->validateOnParse = true;

if (function_exists('mb_convert_encoding')) {
$hasError = false;
set_error_handler(function () use (&$hasError) {
$hasError = true;
});
$tmpContent = @mb_convert_encoding($content, 'HTML-ENTITIES', $charset);

restore_error_handler();

if (!$hasError) {
$content = $tmpContent;
set_error_handler(function () {throw new \Exception();});

try {
// Convert charset to HTML-entities to work around bugs in DOMDocument::loadHTML()

if (function_exists('mb_convert_encoding')) {
$content = mb_convert_encoding($content, 'HTML-ENTITIES', $charset);
} elseif (function_exists('iconv')) {
$content = preg_replace_callback(
'/[\x80-\xFF]+/',
function ($m) {
$m = unpack('C*', $m[0]);
$i = 1;
$entities = '';

while (isset($m[$i])) {
if (0xF0 <= $m[$i]) {
$c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
} elseif (0xE0 <= $m[$i]) {
$c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
} else {
$c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
}

$entities .= '&#'.$c.';';
}

return $entities;
},
iconv($charset, 'UTF-8', $content)
);
}
} catch (\Exception $e) {
}

restore_error_handler();

if ('' !== trim($content)) {
@$dom->loadHTML($content);
}
Expand Down
4 changes: 3 additions & 1 deletion src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php
Expand Up @@ -80,6 +80,7 @@ public function testAddHtmlContent()

/**
* @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent
* @requires extension mbstring
*/
public function testAddHtmlContentCharset()
{
Expand Down Expand Up @@ -114,6 +115,7 @@ public function testAddHtmlContentUnsupportedCharset()

/**
* @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent
* @requires extension mbstring
*/
public function testAddHtmlContentCharsetGbk()
{
Expand Down Expand Up @@ -234,7 +236,7 @@ public function testAddContent()
$this->assertEquals('中文', $crawler->filterXPath('//span')->text(), '->addContent() guess wrong charset');

$crawler = new Crawler();
$crawler->addContent(mb_convert_encoding('<html><head><meta charset="Shift_JIS"></head><body>日本語</body></html>', 'SJIS', 'UTF-8'));
$crawler->addContent(iconv('UTF-8', 'SJIS', '<html><head><meta charset="Shift_JIS"></head><body>日本語</body></html>'));
$this->assertEquals('日本語', $crawler->filterXPath('//body')->text(), '->addContent() can recognize "Shift_JIS" in html5 meta charset tag');
}

Expand Down
36 changes: 8 additions & 28 deletions src/Symfony/Component/Filesystem/Tests/FilesystemTest.php
Expand Up @@ -34,16 +34,13 @@ class FilesystemTest extends \PHPUnit_Framework_TestCase

public static function setUpBeforeClass()
{
if ('\\' === DIRECTORY_SEPARATOR) {
self::$symlinkOnWindows = true;
$originDir = tempnam(sys_get_temp_dir(), 'sl');
$targetDir = tempnam(sys_get_temp_dir(), 'sl');
if (true !== @symlink($originDir, $targetDir)) {
$report = error_get_last();
if (is_array($report) && false !== strpos($report['message'], 'error code(1314)')) {
self::$symlinkOnWindows = false;
}
if ('\\' === DIRECTORY_SEPARATOR && null === self::$symlinkOnWindows) {
$target = tempnam(sys_get_temp_dir(), 'sl');
$link = sys_get_temp_dir().'/sl'.microtime(true).mt_rand();
if (self::$symlinkOnWindows = @symlink($target, $link)) {
unlink($link);
}
unlink($target);
}
}

Expand All @@ -58,27 +55,10 @@ protected function setUp()

protected function tearDown()
{
$this->clean($this->workspace);
$this->filesystem->remove($this->workspace);
umask($this->umask);
}

/**
* @param string $file
*/
private function clean($file)
{
if (is_dir($file) && !is_link($file)) {
$dir = new \FilesystemIterator($file);
foreach ($dir as $childFile) {
$this->clean($childFile);
}

rmdir($file);
} else {
unlink($file);
}
}

public function testCopyCreatesNewFile()
{
$sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file';
Expand Down Expand Up @@ -1035,7 +1015,7 @@ private function markAsSkippedIfSymlinkIsMissing()
$this->markTestSkipped('symlink is not supported');
}

if ('\\' === DIRECTORY_SEPARATOR && false === self::$symlinkOnWindows) {
if (false === self::$symlinkOnWindows) {
$this->markTestSkipped('symlink requires "Create symbolic links" privilege on Windows');
}
}
Expand Down
Expand Up @@ -60,9 +60,6 @@ public function testSeek($path, $seekable, $contains, $message = null)
$i->seek(1);
$actual[] = $i->getPathname();

$i->seek(2);
$actual[] = $i->getPathname();

$this->assertEquals($contains, $actual);
}

Expand All @@ -73,7 +70,6 @@ public function getPaths()
// ftp
$contains = array(
'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'README',
'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'index.html',
'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'pub',
);
$data[] = array('ftp://ftp.mozilla.org/', false, $contains);
Expand Down
Expand Up @@ -33,7 +33,11 @@ public function testAccept($mode, $expected)
if (!is_callable($mode)) {
switch ($mode) {
case SortableIterator::SORT_BY_ACCESSED_TIME :
file_get_contents(self::toAbsolute('.git'));
if ('\\' === DIRECTORY_SEPARATOR) {
touch(self::toAbsolute('.git'));
} else {
file_get_contents(self::toAbsolute('.git'));
}
sleep(1);
file_get_contents(self::toAbsolute('.bar'));
break;
Expand All @@ -56,7 +60,11 @@ public function testAccept($mode, $expected)

if ($mode === SortableIterator::SORT_BY_ACCESSED_TIME
|| $mode === SortableIterator::SORT_BY_CHANGED_TIME
|| $mode === SortableIterator::SORT_BY_MODIFIED_TIME) {
|| $mode === SortableIterator::SORT_BY_MODIFIED_TIME
) {
if ('\\' === DIRECTORY_SEPARATOR && SortableIterator::SORT_BY_MODIFIED_TIME !== $mode) {
$this->markTestSkipped('Sorting by atime or ctime is not supported on Windows');
}
$this->assertOrderedIteratorForGroups($expected, $iterator);
} else {
$this->assertOrderedIterator($expected, $iterator);
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Finder/Tests/Shell/CommandTest.php
Expand Up @@ -86,7 +86,7 @@ public function testArg()
$cmd = Command::create()->add('--force');

$cmd->arg('--run');
$this->assertSame('--force \'--run\'', $cmd->join());
$this->assertSame('--force '.escapeshellarg('--run'), $cmd->join());
}

public function testCmd()
Expand Down
Expand Up @@ -260,7 +260,7 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $
$pattern = $formatter->getPattern();
$timezone = $formatter->getTimezoneId();

if ($setTimeZone = method_exists($formatter, 'setTimeZone')) {
if ($setTimeZone = PHP_VERSION_ID >= 50500 || method_exists($formatter, 'setTimeZone')) {
$formatter->setTimeZone('UTC');
} else {
$formatter->setTimeZoneId('UTC');
Expand Down
62 changes: 56 additions & 6 deletions src/Symfony/Component/HttpFoundation/JsonResponse.php
Expand Up @@ -27,6 +27,10 @@ class JsonResponse extends Response
protected $data;
protected $callback;

// Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML.
// 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
private $encodingOptions = 15;

/**
* Constructor.
*
Expand All @@ -41,6 +45,7 @@ public function __construct($data = null, $status = 200, $headers = array())
if (null === $data) {
$data = new \ArrayObject();
}

$this->setData($data);
}

Expand All @@ -55,11 +60,11 @@ public static function create($data = null, $status = 200, $headers = array())
/**
* Sets the JSONP callback.
*
* @param string $callback
* @param string|null $callback The JSONP callback or null to use none
*
* @return JsonResponse
*
* @throws \InvalidArgumentException
* @throws \InvalidArgumentException When the callback name is not valid
*/
public function setCallback($callback = null)
{
Expand All @@ -80,7 +85,7 @@ public function setCallback($callback = null)
}

/**
* Sets the data to be sent as json.
* Sets the data to be sent as JSON.
*
* @param mixed $data
*
Expand All @@ -90,18 +95,63 @@ public function setCallback($callback = null)
*/
public function setData($data = array())
{
// Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML.
$this->data = json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
if (defined('HHVM_VERSION')) {
// HHVM does not trigger any warnings and let exceptions
// thrown from a JsonSerializable object pass through.
// If only PHP did the same...
$data = json_encode($data, $this->encodingOptions);
} else {
try {
if (PHP_VERSION_ID < 50400) {
// PHP 5.3 triggers annoying warnings for some
// types that can't be serialized as JSON (INF, resources, etc.)
// but doesn't provide the JsonSerializable interface.
set_error_handler('var_dump', 0);
$data = @json_encode($data, $this->encodingOptions);
} else {
// PHP 5.4 and up wrap exceptions thrown by JsonSerializable
// objects in a new exception that needs to be removed.
// Fortunately, PHP 5.5 and up do not trigger any warning anymore.
if (PHP_VERSION_ID < 50500) {
// Clear json_last_error()
json_encode(null);
$errorHandler = set_error_handler('var_dump');
restore_error_handler();
set_error_handler(function () use ($errorHandler) {
if (JSON_ERROR_NONE === json_last_error()) {
return $errorHandler && false !== call_user_func_array($errorHandler, func_get_args());
}
});
}

$data = json_encode($data, $this->encodingOptions);
}

if (PHP_VERSION_ID < 50500) {
restore_error_handler();
}
} catch (\Exception $e) {
if (PHP_VERSION_ID < 50500) {
restore_error_handler();
}
if (PHP_VERSION_ID >= 50400 && 'Exception' === get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
throw $e->getPrevious() ?: $e;
}
throw $e;
}
}

if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException($this->transformJsonError());
}

$this->data = $data;

return $this->update();
}

/**
* Updates the content and headers according to the json data and callback.
* Updates the content and headers according to the JSON data and callback.
*
* @return JsonResponse
*/
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/HttpFoundation/Request.php
Expand Up @@ -1740,9 +1740,9 @@ protected function prepareBaseUrl()
return $prefix;
}

if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(dirname($baseUrl), '/').'/')) {
if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(dirname($baseUrl), '/'.DIRECTORY_SEPARATOR).'/')) {
// directory portion of $baseUrl matches
return rtrim($prefix, '/');
return rtrim($prefix, '/'.DIRECTORY_SEPARATOR);
}

$truncatedRequestUri = $requestUri;
Expand All @@ -1763,7 +1763,7 @@ protected function prepareBaseUrl()
$baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
}

return rtrim($baseUrl, '/');
return rtrim($baseUrl, '/'.DIRECTORY_SEPARATOR);
}

/**
Expand Down

0 comments on commit e044e77

Please sign in to comment.