Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fixes #13919, make response->setFileToSend support non-ASCII filename. #14333

Merged
merged 14 commits into from
Aug 30, 2019
2 changes: 2 additions & 0 deletions .github/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ exemptLabels:
- "Bug - High"
- WIP
- Locked
- "Feature - NFR"
- "Enhancement"

# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
Expand Down
43 changes: 43 additions & 0 deletions phalcon/Helper/Fs.zep
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

/**
* This file is part of the Phalcon.
*
* (c) Phalcon Team <team@phalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Phalcon\Helper;

/**
* This class offers file operation helper
*/
class Fs {

/**
* Gets the filename from a given path, Same as PHP's basename() but has non-ASCII support.
* PHP's basename() does not properly support streams or filenames beginning with a non-US-ASCII character.
* see https://bugs.php.net/bug.php?id=37738
*
* @param string $uri
* @param string $suffix
*
* @return string
*/
final public static function basename(string! uri, var suffix = null) -> string
{
var filename, matches;
let uri = rtrim(uri, DIRECTORY_SEPARATOR);
let filename = preg_match(
"@[^" . preg_quote(DIRECTORY_SEPARATOR, "@") . "]+$@",
uri,
matches
) ? matches[0] : "";
if suffix {
let filename = preg_replace("@" . preg_quote(suffix, "@") . "$@", "", filename);
}

return filename;
}
}
20 changes: 17 additions & 3 deletions phalcon/Http/Response.zep
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use DateTime;
use DateTimeZone;
use Phalcon\Di;
use Phalcon\Di\DiInterface;
use Phalcon\Helper\Fs;
use Phalcon\Http\Response\Exception;
use Phalcon\Http\Response\HeadersInterface;
use Phalcon\Http\Response\CookiesInterface;
Expand Down Expand Up @@ -546,18 +547,31 @@ class Response implements ResponseInterface, InjectionAwareInterface, EventsAwar
public function setFileToSend(string filePath, attachmentName = null, attachment = true) -> <ResponseInterface>
{
var basePath;
var basePathEncoding = "ASCII";
var basePathEncoded;

if typeof attachmentName != "string" {
let basePath = basename(filePath);
let basePath = Fs::basename(filePath);
} else {
let basePath = attachmentName;
}

if attachment {
let basePathEncoded = rawurlencode(basePath);
// mbstring is a non-default extension
if function_exists("mb_detect_encoding") {
let basePathEncoding = mb_detect_encoding(
basePath,
mb_detect_order(),
true
);
}
this->setRawHeader("Content-Description: File Transfer");
this->setRawHeader("Content-Type: application/octet-stream");
this->setRawHeader("Content-Disposition: attachment; filename=" . basePath . ";");
this->setRawHeader("Content-Disposition: attachment; filename=" . basePathEncoded . ";");
this->setRawHeader("Content-Transfer-Encoding: binary");
if basePathEncoding != "ASCII" {
this->setRawHeader("Content-Disposition: attachment; filename=" . basePathEncoded . "; filename*=". strtolower(basePathEncoding) . "''" . basePathEncoded . ";");
}
ian4hu marked this conversation as resolved.
Show resolved Hide resolved
}

let this->file = filePath;
Expand Down
71 changes: 71 additions & 0 deletions tests/unit/Helper/Fs/FsBasenameCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);

/**
* This file is part of the Phalcon Framework.
*
* (c) Phalcon Team <team@phalconphp.com>
*
* For the full copyright and license information, please view the LICENSE.txt
* file that was distributed with this source code.
*/

namespace Phalcon\Test\Unit\Helper\Fs;


use Phalcon\Helper\Fs;
use UnitTester;

class FsBasenameCest {
/**
* Tests Phalcon\Helper\Fs :: basename()
* with ASCII $uri it should be same as PHP's basename
*
* @author Ian Hu <hu2008yinxiang@163.com>
* @since 2019-08-27
*/
public function helperFsBasenamePureASCII(UnitTester $I) {
$I->wantToTest('Helper\Fs - basename() with pure ASCII uri');
$filePathAndSuffixes = [
["/etc/sudoers.d", ".d"],
["/etc/sudoers.d", ''],
['/etc/passwd', ''],
['/etc/', ''],
['.', ''],
['/', '']
];

foreach ($filePathAndSuffixes as $filePathAndSuffix) {
list($filePath, $suffix) = $filePathAndSuffix;
$I->assertEquals(
basename($filePath, $suffix),
Fs::basename($filePath, $suffix)
);
}
}

/**
* Tests Phalcon\Helper\Fs :: basename()
* with non-ASCII $uri support
*
* @author Ian Hu <hu2008yinxiang@163.com>
* @since 2019-08-27
*/
public function helperFsBasenameNonASCII(UnitTester $I) {
$I->wantToTest('Helper\Fs - basename() with non-ASCII uri');
$filePathAndExpects = [
'/file/热爱中文.txt' => '热爱中文.txt',
ian4hu marked this conversation as resolved.
Show resolved Hide resolved
'/中文目录/热爱中文.txt' => '热爱中文.txt',
'/myfolder/日本語のファイル名.txt' => '日本語のファイル名.txt',
'/のファ/日本語のファイル名.txt' => '日本語のファイル名.txt',
'/root/ελληνικά.txt' => 'ελληνικά.txt',
'/νικά/ελληνικά.txt' => 'ελληνικά.txt'
];
foreach ($filePathAndExpects as $filePath => $expect) {
$I->assertEquals(
$expect,
Fs::basename($filePath)
);
}
}
}