【重要】Ver. 3.2.29・3.2.30 でシステム更新(オンラインアップデート)が失敗する場合について


平素より a-blog cms をご利用いただきありがとうございます。

Ver. 3.2.29 および Ver. 3.2.30 において、サーバー環境によっては管理画面の「システム更新」からのオンラインアップデートが失敗する不具合が判明しました。お手数をおかけしますが、対象環境をご利用のお客様は下記の対応をご確認ください。

症状

管理画面の「システム更新」からアップデートを実行すると、パッケージのダウンロード中にエラーとなりアップデートが完了しません。

原因

サーバーの PHP がリンクしている libcurl のバージョンが 7.55.0 未満の環境で発生します。パッケージダウンロード後のサイズ検証機能(Ver. 3.2.29 で追加)が、該当環境では未定義の定数を参照してしまうことが原因です。

影響範囲

  • 影響あり: 管理画面「システム更新」からのオンラインアップデート機能

  • 影響なし: 配布ZIPを手動でサーバーに配置し /update/ を実行する手動アップデート手順

対応方法

恒久対応として Ver. 3.2.31 をリリースしました。ただし、Ver. 3.2.31 へのオンラインアップデート自体も対象環境では本不具合の影響を受け失敗する場合があるため、Ver. 3.2.31 へアップデートいただく際は、以下のいずれかの方法をご利用ください。

方法1: 手動アップデート

配布ZIPをダウンロードし、手動アップデート手順に従ってサーバーに反映してください。

手動アップデート手順: https://developer.a-blogcms.jp/document/update/update-yourself-all.html

方法2: 該当ファイルのみ差し替え

手動アップデートが難しい場合、以下のファイルのみを差し替えることでも回避できます。差し替え後、管理画面からのオンラインアップデートが正常にご利用いただけます。

対象ファイル: php/Services/Update/System/Download.php(Ver. 3.2.31 時点の内容です)

<?php

namespace Acms\Services\Update\System;

use Acms\Services\Update\Contracts\LoggerInterface;
use Acms\Services\Update\Exceptions\IncompleteDownloadException;
use Acms\Services\Facades\LocalStorage;
use Acms\Services\Facades\Http;

class Download
{
    /**
     * @var \Acms\Services\Update\Contracts\LoggerInterface
     */
    protected $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    /**
     * @param string $dest_path
     * @param string $url
     */
    public function download($dest_path, $url)
    {
        $this->logger->message(gettext('パッケージをダウンロード中...') . ' (' . $url . ')', 5);

        set_time_limit(0);
        LocalStorage::makeDirectory($dest_path);

        $filename = basename($url);
        $path = $dest_path . $filename;
        $fp = fopen($path, 'w+');
        if ($fp === false) {
            throw new \RuntimeException(sprintf(gettext('パッケージを保存するためのファイルを開けませんでした: %s'), $path));
        }

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_FILE, $fp);
        curl_setopt($curl, CURLOPT_TIMEOUT, 0);

        Http::setCurlSsl($curl);
        Http::setCurlProxy($curl);

        $succeeded = curl_exec($curl);
        $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        // Why not CURLINFO_CONTENT_LENGTH_DOWNLOAD_T 直書き: リンクされた libcurl が 7.55.0 未満のビルドでは
        // 定数自体が未定義で Undefined constant になる(PHP 拡張のバージョンでなく libcurl 側のビルド依存)。
        $contentLengthInfo = defined('CURLINFO_CONTENT_LENGTH_DOWNLOAD_T')
            ? CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
            : CURLINFO_CONTENT_LENGTH_DOWNLOAD;
        $contentLength = (int)curl_getinfo($curl, $contentLengthInfo);
        $error = curl_error($curl);
        // getFileSize() で実際に保存されたサイズを見るため、書き込みバッファを確定させる
        fclose($fp);

        if ($succeeded === false || $status !== 200) {
            throw new \RuntimeException(sprintf(gettext('パッケージのダウンロードに失敗しました。%s: %s'), (string)$status, $error));
        }
        $this->validateDownloadedSize($path, $contentLength);

        $this->logger->message(gettext('パッケージダウンロード完了'), 15);
        $this->logger->message(gettext('パッケージを解凍中...'), 0);

        LocalStorage::unzip($path, $dest_path);
        LocalStorage::remove($path);

        $this->logger->message(gettext('パッケージを解凍完了'), 30);
    }

    /**
     * ダウンロードしたパッケージのサイズが Content-Length と一致するか検証する
     *
     * ダウンロードが途中で切れても HTTP ステータスは 200 のままなので、
     * 不完全なファイルを解凍しようとして原因の分かりにくい失敗になるのを防ぐ。
     *
     * Why public: download() は curl 経由の実通信に依存し直接テストしづらいため、
     * 副作用を持たないこの検証ロジックだけを切り出して単体テスト可能にしている。
     *
     * @param string $path
     * @param int $expectedSize Content-Length。取得できなかった場合は 0
     * @throws IncompleteDownloadException
     * @return void
     */
    public function validateDownloadedSize(string $path, int $expectedSize): void
    {
        if ($expectedSize <= 0) {
            return;
        }
        $actualSize = LocalStorage::getFileSize($path);
        if ($actualSize === $expectedSize) {
            return;
        }
        throw new IncompleteDownloadException(sprintf(
            gettext('ダウンロードしたパッケージが不完全です。期待サイズ: %d バイト, 実際のサイズ: %d バイト'),
            $expectedSize,
            $actualSize
        ));
    }
}

方法3: config.user.php に追記する

PHP がリンクしている libcurl のバージョンが原因のため、a-blog cms 設置ディレクトリ直下にある config.user.php(アップデートで上書きされない、a-blog cms 標準のカスタマイズ用ファイルです)に、以下の記述を追加することでも回避できます。CMS本体のファイルは変更しません。

if (!defined('CURLINFO_CONTENT_LENGTH_DOWNLOAD_T')) {
    define('CURLINFO_CONTENT_LENGTH_DOWNLOAD_T', CURLINFO_CONTENT_LENGTH_DOWNLOAD);
}

お問い合わせ

本件に関するご質問・ご相談は、a-blog cms のサポート窓口またはサポートフォーラムまでお寄せください。

改めまして、ご利用のお客様にはご迷惑をおかけし、誠に申し訳ありません。