WordPress で一部の .xlsファイル だけがアップロードできない (解決)

前回の続きです。プラグインを作成しましたが、挙動が変わらなかったためさらに調査を続行しました。

結果

結果から記すと、プラグインを以下のように改修することで解決できました。

<?php
/*
Plugin Name: MIMEuruwashii
Description: PHP の MIME タイプ判定で特定の .xlsファイル が弾かれる現象への対策
Version: 0.0.3
Author: アルム=バンド
*/

/**
 * MIMEuruwashii : PHP の MIME タイプ判定で特定の .xlsファイル が弾かれる現象への対策
 */
class MIMEuruwashii
{
    /**
     * __construct : コンストラクタ
     *
     */
     public function __construct() {
        add_filter(
            'wp_check_filetype_and_ext',
            [
                $this,
                'Bibishii',
            ],
            99,
            3
        );
    }
    /**
     * Vivid                     : 追加する MIMEタイプ の定義
     *
     * @return {Object}          : 誤判定している MIME タイプを追記する
     *
     */
    public static function Vivid(){
        return [
            [
                'xla|xls|xlt|xlw' => 'application/vnd.ms-office'
            ],
            [
                'xla|xls|xlt|xlw' => 'application/vnd.ms-excel'
            ],
        ];
    }
    /**
     * Bibishii                  : フィルター追加
     *
     * @param {Object} $check    : MIMEタイプ の一覧のオブジェクト
     * @param {File} $file       : チェック対象ファイル
     * @param {Object} $filename : チェック対象ファイルの名前
     *
     * @return {Object}          : 誤判定している MIME タイプを追記する
     *
     */
    public function Bibishii ( $check, $file, $filename )
    {
        if ( empty( $check['ext'] ) && empty( $check['type'] ) ) {
            foreach ( self::Vivid() as $mime ) {
                remove_filter(
                    'wp_check_filetype_and_ext',
                    [
                        $this,
                        'Bibishii'
                    ],
                    99
                );
                $mime_filter = function($mimes) use ($mime) {
                    return array_merge($mimes, $mime);
                };

                add_filter(
                    'upload_mimes',
                    $mime_filter,
                    99
                );
                $check = wp_check_filetype_and_ext( $file, $filename, $mime );
                remove_filter(
                    'upload_mimes',
                    $mime_filter,
                    99
                );
                add_filter(
                    'wp_check_filetype_and_ext',
                    [
                        $this,
                        'Bibishii'
                    ],
                    99,
                    3
                );
                if ( ! empty( $check['ext'] ) || ! empty( $check['type'] ) ) {
                    return $check;
                }
            }
        }

        return $check;
    }
}

// instantiate
$ab_wp_plugin_mimeuruwashii = new MIMEuruwashii();

元の原型ないですね。引っかけるアクションフックも upload_mimes から wp_check_filetype_and_ext に変わっています。

調査

調査の過程を以下に記します。

挙動が変わらなかった理由を wp-includes/functions.php を追いかけて調べて行きます。

  • メソッドwp_get_mime_types() では拡張子と MIMEタイプ をそれぞれキー・値として持つ連想配列を返却しています
  • wp_get_mime_types() が呼ばれている場所を辿っていきます
    • get_allowed_mime_types() はいくつかの処理( .swf.exe の除外、ユーザ権限がない場合は .html, .htm, .js を除外)をした後に upload_mimes でチェックする拡張子と MIMEタイプ を登録
    • do_enclose() は動画関連のチェックのように見えます。しかし、ここで気になるコードを発見
      • 拡張子の方は if ( preg_match( '!^(' . $exts . ')$!i', $extension ) ) で正規表現による判定を行っている (例えば !^(xla|xls|xlt|xlw)$!i のような形式)
      • 一方、 MIMEタイプ は $type = $mime; で代入しているだけ
        • しかも break; している

上述のコードを見て、「ひょっとして複数の MIMEタイプ を想定していないのでは?」と思い至り、検索。

やはり1つの拡張子に対して複数の MIMEタイプ を持つ場合に対応していない (今回のケースで言うと「 .xlsファイル は application/vnd.ms-excel または application/vnd.ms-office を受け付ける」としたい) ようです。

前回のように MIMEタイプ を付け足す方法は、 .ai.psd のような、デフォルトの連想配列に拡張子がキーとして存在しない MIMEタイプ ならば良いと思います。しかし、今回の .xls のように既存の拡張子の場合は最後に付け足したキーまで評価されていなさそうです。

そこで、上述記事のように前回の upload_mimes 時に判定する MIMEタイプ を付け足すのではなく、そもそものチェック処理の wp_check_filetype_and_ext を変更する、という方針に転換しました。

その結果が冒頭のプラグインのコードとなります。

雑感

複数の MIMEタイプ が想定されていない作りということを初めて知って驚きました。

上述の Stackoverflow でも取り上げられていますが、 .svg.zip 等他にも類似のケースが考えられるので意外でした。

参考

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。