麦星 - arcturus technologies- 

テクノロジー関連情報と日常的な何かを発信しています

OSS(Object Storage Service)のSDK(aliyun-oss-php-sdk)を修正した話

ども、うし@tangniuwanです。

とある案件で、アリババクラウドOSS(Object Storage Service)を使用させて頂いたのですが、
SDK(aliyun-oss-php-sdk)の「uploadFile」メソッドに不具合があったので修正しました。

サクッと修正できるかな?と思ったら、何気に苦労したなーということでブログの記事にさせて頂きました。

 
※ちなみに現状は、
以下、本家のリポジトリをフォークして修正、Unitテストまで終わった所になります。

github.com

そしてフォークして修正したリポジトリは以下になります。

github.com

OSSAPIでやったこと

WEBサーバから、OSS(Object Storage Service)に対し、SDKを利用しファイルをアップロードしました。

参考にしたのは、以下のソースコードで、ドキュメントセンターにある
簡易アップロード(ローカルファイルのアップロード)
に記載されているものです。

※以下に記載しているものは、ちょっと見やすく整形しています。

<? php
require_once __DIR__ . '/vendor/autoload.php';

use OSS\OssClient;
use OSS\Core\OssException;

$accessKeyId = "<yourAccessKeyId>";
$accessKeySecret = "<yourAccessKeySecret>";
$endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
$bucket= "<yourBucketName>";
$object = "<yourObjectName>";
$filePath = "<yourLocalFile>";

try
{
    $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint);
    $ossClient->uploadFile($bucket, $object, $filePath);
} 
catch(OssException $e) 
{
    printf(__FUNCTION__ . ": FAILED\n");
    printf($e->getMessage() . "\n");
    return;
}
print(__FUNCTION__ . ": OK" . "\n");

不具合の内容

上記ソースコードで、
変数「 $filePath 」に日本語のファイル名を設定すると、中国語と誤解釈し、
GBKへエンコーディングしてエラーになる。というもの。。
しかも、これは、Windows OSの場合のみ発生します。

具体的には、
上記のソースコードに諸々設定した上で、以下の様に変数「 $filePath 」へ日本語文字列を設定し、
実行すると、そんなファイルは無いよ!とエラーになります。

// 設定値  
$filePath = "北海道testテスト.png";  
  
// 実行結果  
testƥ.png file does not exist  

見ていただくとわかる通り、
日本語が消えてしまい。文字化けを起こしたりしています。

こりゃいかん。とのことでSDKを修正した内容を以下に

原因

まず、原因
OssUtil.php(PATH: /oss-sdk-php/src/OSS/Core/OssUtil.php)の341行目にある以下の構文で、
設定したファイル名を、GBKにエンコードしています。
この際、中国語で且つ、WindowsOSか?を判定していますが、
日本語「北海道testテスト.png」を設定していても中国語と判定されます。
では何故、日本語を指定しているにも関わらず、「chkChinese」メソッドで、中国語と判定されるのか?

※メソッドの引数「$file_path」に、設定した「北海道testテスト.png」が代入されます。

    /**
     * Encodes the file path from GBK to UTF-8.
     * The default encoding in Windows is GBK. 
     * And if the file path is in Chinese, the file would not be found without the transcoding to UTF-8.
     *
     * @param $file_path
     * @return string
     */
    public static function encodePath($file_path)
    {
        if (self::chkChinese($file_path) && self::isWin()) {
            $file_path = iconv('utf-8', 'gbk', $file_path);
        }
        return $file_path;
    }

という事で、さらに「chkChinese」メソッド(以下ソースコード)を確認すると
preg_match関数で「/[\x80-\xff]./」が指定されています。
  
これは簡単に言うと、非ASCII文字範囲で、
2バイト文字を含まない半角の英数字、記号、制御記号(空白等)か?ということを判定しています。

つまり、1バイト以外は中国語として認識します。。ーみたない判定をしちゃってます。
(なかなか、豪快で男気に溢れています)

    /**
     * Check whether the string includes any chinese character
     *
     * @param $str
     * @return int
     */
    public static function chkChinese($str)
    {
        return preg_match('/[\x80-\xff]./', $str);
    }

原因が判明したので、この「chkChinese」を修正していきます。
とりあえず、日本語だけでもエンコードされない様に。とのことで
先程の「encodePath」メソッドで日本語だったらエンコードされない様にすれば良い。くらいに考えていましたが、 ここで大きな間違いに気づきます。

地味に苦労した内容

何に苦労しかと言うと、中国語には、繁体字簡体字があります。
これは皆さんご存じだと思います。
この繁体字簡体字には、日本語と被る文字が沢山、それは沢山あります。

例えば、「作」という漢字は、日本語にも繁体字にも簡体字にも存在します。
また「義」という漢字は、日本語と繁体字にはあるけど、簡体字には違う文字として存在していたりします。

例)パターン一覧

日本語 繁体字 簡体字 パターン
三者三様
三者同一
日本語だけ違う
繁体字だけ違う
簡体字だけ違う

参考:繁体字と簡体字と日本語を区別する - Qiita

しかも、 CJK統合漢字 - Wikipedia と言うやつがあり、
例えば「」という漢字は、中国語、日本語、朝鮮語でも、言語を問わず同じコード。。

つまり、例えば「作義.txt」というファイルがあった場合、日本語とも、繁体字とも取れ、
一概に日本語文字と判定ができないってことになります。

※ちなみに、ソースコード上に 「GB 2312 - Wikipedia」というものがありますが、調べると簡体字のみサポートしている。。とのこと

修正内容

という事で、「chkChinese」メソッドの修正内容を以下の方針としコーディングしました。

また、幸い?「checkChar」(GBK=「繁体字簡体字」を判定するメソッド)や、
「isGb2312」(簡体字を判定するメソッド)が既に実装されており、流用させて頂きました。

  • 半角文字(1バイトの文字)を削除
  • 残った文字列が、日本語にしかない文字(ひらがな・カタカナ・漢字)で構成されてたら「日本語」
  • 更に繁体字か、簡体字で構成されているかを確認
    /**
     * Check whether the string includes any chinese character
     *
     * @param $str
     * @return int
     */
    public static function chkChinese($str)
    {
        // 1byte character delete 
        $str =  preg_replace( "/[\x20-\x7E]+/" , "" , $str );

        // chk japanese character only
        if( preg_match("/^[ぁ-んァ-ヶ一-龠々]+$/u" , $str ) ) return false;

        //Checks if the string is encoded by GBK
        return checkChar( $str );
    }

朝鮮語など、他言語での動作は不明ですが、
ユニットテストが通ったので、これでファイル名に日本語が含まれててもエラー無く、アップロードが出来るようになりました。
簡易版としては、取りあえずOKで、日本語で使用する分には問題なく動作するかな?と思います。

また、繁体字簡体字を判定する正規表現メソッドも作成しときましたので、
別途、ブランチ作成しときたいと思います。

とりあえず、イシューたてて、プルリクしときたいと思います。

最後まで読んで頂いてありがとうございます。

でわ