Skip to content

添付ファイルの拡張子制限 ​

第1版作成 最終更新 (日本時間)
確認バージョン1.5.1.01.5.8.1

本体の標準機能ではありません

項目ごとの拡張子の許可・拒否は 1.5.8.1 にはありません。このページの後半は改修する場合の設計メモです。

現行の拡張子チェック(1.5.8.1) ​

設定は Form.json の 1 か所だけ ​

拡張子の拒否リストはサーバー全体で 1 つで、App_Data/Parameters/Form.json の AttachmentExcludedExtensions にあります(Form.json、Form.cs)。

json
{
    "Enabled": false,
    "AttachmentExcludedExtensions": [
        ".exe", ".dll", ".com", ".scr", ".pif", ".msi", ".msp", ".bat", ".cmd", ".ps1",
        ".vbs", ".vbe", ".js", ".jse", ".wsf", ".wsh", ".aspx", ".asp", ".php", ".php3",
        ".php4", ".php5", ".phtml", ".jsp", ".jspx", ".cfm", ".cfc", ".hta", ".htaccess",
        ".htpasswd", ".config", ".cer", ".sh", ".bash", ".csh", ".ksh", ".pl", ".py",
        ".rb", ".jar", ".war"
    ]
}

実際のファイルは 1 行に 1 つずつ書かれています。既定で 41 個です。

判定のしかた ​

判定は BinaryValidators.IsAllowedExtension() です(BinaryValidators.cs#L606-L631)。

  • ファイル名の最初の . から後ろを . で区切り、各部分を拒否リストと照合します。report.exe.pdf も malware.txt.exe も .exe を含むので拒否されます。. を含まないファイル名は通ります。
  • リストは HashSet<string> で、JSON から読み込むと既定の比較(大文字小文字を区別する)になります。照合の前に小文字にしていないので、.EXE のように大文字で書かれた拡張子はリストの .exe と一致しません。
  • 拒否リスト方式だけで、「この拡張子だけ許可する」設定はありません。

掛かるのは公開フォームからのアップロードだけ ​

IsAllowedExtension() を呼ぶのは OnValidatingFormUpload() だけで、その先頭が if (!context.IsForm) return Error.Types.None; です(BinaryValidators.cs#L636-L689)。

  • 公開フォーム(フォーム機能)からのアップロードでは、ファイル名の検査(IsValidFileName())と拡張子の検査が掛かり、引っかかると InvalidRequest になります。
  • ログインしたユーザーの編集画面や API からのアップロードでは、拡張子は検査しません。
  • 公開フォームは Form.json の Enabled(既定 false)か、テナントの契約設定の拡張 Form で有効になる機能です(SiteUtilities.cs#L20295)。どちらも無効な既定の構成では、このリストは使われていません。

添付ファイル項目の既存の設定 ​

添付ファイル項目の詳細設定(SiteUtilities.cs#L8864-L8952)にあるのは次の項目で、拡張子に関するものはありません。

設定controlId種類
添付ファイルの削除を許可AllowDeleteAttachmentsチェックボックス
既存の履歴を削除しないNotDeleteExistHistoryチェックボックス
保存先BinaryStorageProviderドロップダウン(BinaryStorage.json の UseStorageSelect が有効なときだけ)
同名ファイルを上書きOverwriteSameFileNameチェックボックス
件数の上限LimitQuantityスピナー
1 ファイルのサイズ上限(MB)LimitSizeスピナー
合計サイズの上限(MB)LimitTotalSizeスピナー
ローカル保存時の 1 ファイルの上限LocalFolderLimitSizeスピナー
ローカル保存時の合計の上限LocalFolderLimitTotalSizeスピナー

改修案:項目ごとに許可・拒否する拡張子を設定する ​

判定の順序 ​

サーバー全体の拒否リストを先に見て、次に項目の拒否リスト、最後に項目の許可リスト(設定があるときだけ)を見ます。

図を読み込み中…

設定状態動作
どちらも未設定サーバー全体の拒否リストだけ(現行と同じ)
許可リストだけリストにある拡張子だけアップロードできる
拒否リストだけリストにある拡張子はアップロードできない
両方拒否リストを先に見て、残りを許可リストで絞る。両方に同じ拡張子があれば拒否

改修するファイル ​

ファイル内容
Libraries/Settings/Column.csHashSet<string> AllowedExtensions・ExcludedExtensions を追加(既存の AllowDeleteAttachments などの並び。Column.cs#L139)
Libraries/Settings/SiteSettings.cs保存用の差分を作る処理(SiteSettings.cs#L1785-L1788 の AllowDeleteAttachments と同じ形)と、画面から来た値を項目に反映する SetColumnProperty の switch(SiteSettings.cs#L4498)
Models/Sites/SiteUtilities.cs添付ファイル項目の詳細設定に、許可・拒否の入力欄を追加
Models/Binaries/BinaryValidators.cs項目を受け取る判定を追加
Libraries/HtmlParts/HtmlControls.csファイル選択の accept 属性
フロントエンドのスクリプト入力欄から一覧に拡張子を足す処理
Models/Sites/ApiSiteSettings/ColumnApiSettingModel.csサイト設定 API 用のモデルに同じプロパティ
App_Data/Displays/「許可する拡張子」(AllowedExtensions)・「許可しない拡張子」(ExcludedExtensions)の表示文字列

Column.cs は CodeDefiner の生成対象ではないので、直接編集して構いません。

入力欄:バスケットを流用する ​

拡張子の入力には、既存のバスケット(入力した値がタグ状に並び、× で個別に消せる UI)を流用できます。サーバー側は HtmlFields.FieldBasket()(HtmlFields.cs#L2201)、画面側は $p.addBasket()(basket.js)と × クリックで要素を消す basketevents.js です。

csharp
.FieldBasket(
    controlId: "AllowedExtensions",
    fieldCss: "field-wide",
    controlCss: "control-basket cf",
    labelAction: () => hb
        .Text(text: Displays.AllowedExtensions(context: context)))
.FieldTextBox(
    textType: HtmlTypes.TextTypes.Normal,
    controlId: "AllowedExtensionsInput",
    fieldCss: "field-wide",
    labelText: string.Empty,
    text: string.Empty,
    placeholder: ".pdf")
// ExcludedExtensions も同じ形で追加する

入力欄から一覧に足すスクリプトの例です。先頭の . を補い、小文字にし、重複を除きます。

js
$p.addExtensionToBasket = function (inputId, basketId) {
    var $input = $('#' + inputId);
    var extension = $input.val().trim().toLowerCase();
    if (!extension.startsWith('.')) {
        extension = '.' + extension;
    }
    var exists = false;
    $('#' + basketId + ' li').each(function () {
        if ($(this).find('span:first').text() === extension) {
            exists = true;
            return false;
        }
    });
    if (!exists && extension.length > 1) {
        $p.addBasket($('#' + basketId), extension, extension);
    }
    $input.val('');
};

判定の追加 ​

既存の IsAllowedExtension(fileName) を先に呼び、そのあと項目の設定を見るオーバーロードを足します。項目側の照合は小文字にそろえて行います。

csharp
public static bool IsAllowedExtension(string fileName, Column column)
{
    if (!IsAllowedExtension(fileName)) return false;
    if (column == null) return true;
    var firstDotIndex = fileName.IndexOf('.');
    if (firstDotIndex < 0) return true;
    foreach (var part in fileName[firstDotIndex..].Split('.'))
    {
        if (string.IsNullOrEmpty(part)) continue;
        var extension = "." + part.ToLower();
        if (column.ExcludedExtensions?.Contains(extension) == true) return false;
        if (column.AllowedExtensions?.Any() == true
            && !column.AllowedExtensions.Contains(extension)) return false;
    }
    return true;
}
  • 拡張子のないファイル名は、許可リストがあってもこの判定を通ります。
  • 複合拡張子の各部分に許可リストを当てるので、許可リストが .pdf だけだと report.v2.pdf のような名前(.v2 が許可リストにない)も拒否されます。最後の拡張子だけを見る方式にするかは要件次第です。
  • 現行の呼び出し元 OnValidatingFormUpload() は context.IsForm でないと何もしないので、画面や API からのアップロードにも項目の設定を効かせるなら、呼び出し位置を UploadFile() の OnUploading() の近く(項目 column を持っている所)に移すか、別に呼びます。API のアップロード(Api/BinariesController)も同様です。

accept 属性(補助) ​

添付ファイル項目のファイル選択は、1.5.8.1 では accept 属性なしで描画しています(HtmlControls.cs#L1053-L1057)。許可リストがあるときに付けると、ファイル選択ダイアログで該当する種類だけが表示されます。

csharp
.Input(attributes: new HtmlAttributes()
    .Id(columnName + ".input")
    .Class("hidden")
    .Type("file")
    .Multiple(true)
    .Accept(column?.AllowedExtensions?.Any() == true
        ? string.Join(",", column.AllowedExtensions)
        : null))

accept はブラウザへのヒントで、「すべてのファイル」を選べば回避できます。サーバー側の判定は必ず残します。

関連ページ ​

変更履歴

第1版レートリミッターの解説と、フォーム投稿の制限・ウイルススキャン・拡張子制限・外部検索エンジン・管理画面設定の改修・設計メモを追加