Skip to content

MS_UploadTopics

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

アップロードのいろいろ

概要

ファイルのアップロードについての情報を纏めます。

通常のアップロード方法

Form-based File Upload

RFC1867 で規定されている模様。

Content-Type

multipart/form-data を使用する。

  • WWW ブラウザの Form からファイルアップロードする一般的方法

  • <INPUT type="file"> を使用してアップロードできる。

HTTP1.0 / 1.1メソッド

  • POST メソッドを使用する。
  • PUT は使用できない模様(後述)。

補足(RFC 1867 の現況と、multipart/form-data の構造): 原文が挙げる
RFC 1867 は 1995 年の実験的仕様で、
**現在の正式な規定は RFC 7578(2015 年)**である
(RFC 2388 を経て置き換えられた)。仕様の中身は概ね同じである。

【multipart/form-data の中身】

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXyz

------WebKitFormBoundaryXyz
Content-Disposition: form-data; name="title"

報告書
------WebKitFormBoundaryXyz
Content-Disposition: form-data; name="file"; filename="report.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

(バイナリ本体)
------WebKitFormBoundaryXyz--

要点:

・boundary(区切り文字)で複数のパートを区切る
・各パートに Content-Disposition があり、name と filename を持つ
・【バイナリをそのまま送れる】(Base64 等の変換が不要)★
   → application/x-www-form-urlencoded だとサイズが約 3 倍になる

filename は利用者側から来る値であるため、
絶対に信用してはならない(後述の「現在の実装指針」)。

<input type="file"> の現在の属性:

<input type="file" name="files"
       multiple                                  <!-- 複数選択 -->
       accept=".xlsx,.csv,image/*"               <!-- 候補を絞る(強制ではない) -->
       capture="environment">                    <!-- スマホでカメラを起動 -->

accept はファイル選択ダイアログの絞り込みにすぎず、
検証にはならない
(利用者は「すべてのファイル」を選べる)。

その他の方法

HTTP1.0 / 1.1メソッド

PUT メソッドなども使用できるが、
multipart/form-data とは組み合わせられない模様。
(application/x-www-form-urlencoded では動作する)

移行メモ(PUT と multipart は「組み合わせられない」わけではない): 原文の
記述は当時のフレームワーク側の実装事情を指していると読める。
HTTP の仕様上は、PUT で multipart/form-data を送っても構わない

【実際の制約】
   ・サーバ側フレームワークの多くが、
     【POST の multipart しかパースしない】実装だった
      - PHP の $_FILES は POST のみ
      - 古い ASP.NET / Java サーブレットも同様
   ・したがって「使えない」のは【実装の都合】であって
     プロトコルの制約ではない

【現在】
   ・ASP.NET Core は【PUT でも multipart を読める】
      ([FromForm] / Request.Form はメソッドに依存しない)
   ・とはいえ、慣習として【POST を使う】のが無難

POST と PUT の使い分け(原文の参考リンクの主題):

POST PUT
意味 「この下に作る」(サーバが URI を決める) 「この URI にこれを置く」
冪等性 なし(2 回送れば 2 件できる) あり(何回送っても同じ結果)★
用途 一般的なファイル アップロード リトライしたい転送、S3 / Blob への直接 PUT
【冪等であることの実用的な価値】
   通信が切れた際、【安全に再送できる】
     → 大容量アップロードのリトライで効く
     → オブジェクト ストレージへの直接アップロードが
       PUT なのはこのため ★

WebDAV追加メソッド

  • WebDAV - Wikipedia
    https://ja.wikipedia.org/wiki/WebDAV

    • ボディ部では、クライアント・サーバ双方とも XML を用いる。

    • HTTP 1.1(のメソッド)に加え

      • GET
      • HEAD
      • POST
      • PUT
      • DELETE
      • OPTIONS
    • 次のメソッドが存在する。

      • PROPFIND
        プロパティの取得
      • PROPPATCH
        プロパティの変更
      • MKCOL
        指定した URI の場所に新たな資源を作成する。
      • COPY
        指定した URI が示す資源およびその属性値を別の URI にコピーする。
      • MOVE
        指定した URI が示す資源およびその属性値を別の URI に移動する。
      • LOCK
        指定した URI が示す資源のファイルロックを設定する。
        共有ロックと排他ロックの二種類が利用できる。
      • UNLOCK
        指定した URI が示す資源のロックを解除する。

補足(WebDAV の現況): 新規のシステムで採用することはほぼないが、
既存環境で遭遇するため知識として要る。

【今も残っている場所】
   ・SharePoint / OneDrive の「エクスプローラーで開く」
   ・IIS の WebDAV 発行
   ・NAS の Web アクセス機能
   ・Nextcloud / ownCloud

【廃れた理由】
   ・プロトコルが重い(XML の往復、LOCK の管理)
   ・ファイアウォール / プロキシとの相性が悪い
   ・【ファイル名の文字コード】で日本語が化ける([エンコーディング](MS_Encoding))
   ・オブジェクト ストレージ + REST API に置き換わった ★

セキュリティ上の注意:

【IIS の WebDAV は、使わないなら無効化する】
   ・PUT / DELETE / PROPFIND が意図せず有効になっていると、
     【ファイルを書き込まれる】危険がある
   ・過去に CVE も出ている(バッファ オーバーフロー等)
   → [IIS、ASP.NETでのHTTPメソッドの制御](MS_HTTPMethodControl) を参照

大容量ファイルのアップロード

大量データのアップロードには、以下の方式がある。

  • 分割アップロード
  • その他の方法
    • MTOM

Streaming、MTOM などに対応している
AP サーバは少ないと考えられるため、
分割アップロードが一般的設計と考える。

分割アップロード

主に、AP サーバの Streaming 対応の有無によって、
分割アップロードが必要になることがある。

必要性

Streaming 対応が無い AP サーバでは、
multipart/form-data を全てメモリに溜め込んでしまうため、
メモリ・リークに陥る。この対応として分割アップロードがある。

移行メモ(「メモリ・リーク」は正確には「メモリ枯渇」): 原文の
**「メモリ・リークに陥る」**という表現は、
厳密にはメモリ リーク(解放漏れ)ではない

【実際に起きること】
   ・受信データを全部メモリに載せる
   ・1GB のファイル × 同時 10 人 = 10GB
     → OutOfMemoryException / プロセス強制終了
     → .NET なら【LOH(Large Object Heap)の断片化】も起こす ★

 → 解放漏れではなく【一時的な使用量の過大】
   ただし、実務上の症状(サーバが落ちる)は同じである

.NET での「メモリに載せてしまう」実装の例:

// ✗ ファイル全体をメモリに読む
using var ms = new MemoryStream();
await file.CopyToAsync(ms);
var bytes = ms.ToArray();          // ← ここで更にもう 1 部コピーされる

// ○ ストリームのまま流す
await using var dest = File.Create(path);
await file.CopyToAsync(dest);      // 既定 81,920 バイトのバッファで転送

選択肢

  • クライアントを作りこむ。
    HTTP クライアントライブラリ(API)を使用して
    ファイル分割アップロード処理を行う HTTP クライアントを開発する。

  • JavaScript ライブラリを使用する。
    jQuery-File-Upload を使用して、分割アップロードが可能。

    以下も参考になるが、こちらは、ノーマルな jQuery のサンプルのもよう。

補足(現在の分割アップロード): 原文の
**「分割アップロードが一般的設計」**という結論は今も正しいが、
手段が変わった

① ブラウザ標準の API で分割できる

// File は Blob なので slice できる(jQuery プラグイン不要)
const CHUNK = 5 * 1024 * 1024;   // 5MB
for (let start = 0; start < file.size; start += CHUNK) {
    const chunk = file.slice(start, start + CHUNK);
    await fetch(`/api/upload/${uploadId}?index=${start / CHUNK}`, {
        method: 'PUT', body: chunk
    });
}
await fetch(`/api/upload/${uploadId}/complete`, { method: 'POST' });

② ライブラリを使う

ライブラリ 内容
tus / tus-js-client レジューム可能アップロードの標準的なプロトコル
Uppy UI 込み。tus / S3 / Azure に対応
Dropzone.js 手軽。分割にも対応
jQuery-File-Upload アーカイブ済み(原文のもの。新規採用は不可)

③ そもそもアプリ サーバを経由させない(推奨)

【SAS URL / 署名付き URL 方式】

  ① ブラウザ → アプリ: 「アップロードしたい」
  ② アプリ   → ブラウザ: 【期限付きの書き込み URL】を返す
  ③ ブラウザ → Blob Storage / S3 に【直接】アップロード
       (SDK が分割・並列・リトライを自動で行う)
  ④ ブラウザ → アプリ: 「完了した」(Blob 名を通知)
  ⑤ アプリ: 検証(サイズ、種類、ウイルス スキャン)

 → アプリのメモリ・帯域・タイムアウトの問題が【まとめて消える】★
// ② SAS URL の発行(書き込みのみ・15 分・特定の Blob 名に限定)
var sas = blobClient.GenerateSasUri(new BlobSasBuilder(
    BlobSasPermissions.Write | BlobSasPermissions.Create,
    DateTimeOffset.UtcNow.AddMinutes(15))
    { BlobContainerName = "uploads", BlobName = $"{userId}/{Guid.NewGuid()}" });
【SAS 発行時の必須事項】
   ・権限を【Write / Create だけ】に絞る(Read や Delete を付けない)
   ・【有効期限を短く】する
   ・【Blob 名をサーバ側で決める】(利用者に指定させない)★
     → 他人のファイルを上書きされないため

注意点

こちらと同様、2GB 以上のファイルを扱えないケースがある。

補足(2GB 制限の現況): 原文が指す 2GB の壁は、
多くが解消したが、一部は残っている

箇所 当時 現在
Array / byte[] の要素数 2GB 上限 gcAllowVeryLargeObjects で緩和可(要素数上限は残る)
MemoryStream 2GB 上限 同左(内部が byte[])★
ASP.NET(.NET Framework)の要求サイズ maxRequestLength(KB 単位、int 同左
ASP.NET Core の要求サイズ long なので 2GB 超も可(既定は 30MB)
IIS の maxAllowedContentLength uint(約 4GB) 同左
ファイル システム NTFS は問題なし 同左(FAT32 は 4GB 上限)
【結論】
   ・ストリームで扱えば 2GB 超も扱える
   ・【メモリに載せる実装をした瞬間に 2GB の壁に当たる】★
   → 分割アップロード/直接アップロードにすれば、そもそも当たらない
<!-- .NET Framework:両方の設定が要る(片方だけだと効かない) -->
<system.web>
  <httpRuntime maxRequestLength="2097151" executionTimeout="3600" />
</system.web>
<system.webServer><security><requestFiltering>
  <requestLimits maxAllowedContentLength="2147483648" />
</requestFiltering></security></system.webServer>
// ASP.NET Core
[RequestSizeLimit(1_073_741_824)]                 // 1GB
[RequestFormLimits(MultipartBodyLengthLimit = 1_073_741_824)]
public async Task<IActionResult> Upload() { ... }

その他の方法

MTOM

MTOM(Message Transmission Optimization Mechanism)

補足(MTOM とは何か、そして現況): 名前だけでは分かりにくいので
補っておく。

【SOAP の弱点】
   SOAP のボディは XML
     → バイナリを入れるには【Base64 にする】必要がある
     → サイズが約 4/3 に膨らむ([エンコーディング](MS_Encoding))
     → XML パーサがメモリに全部載せる

【MTOM の解】
   バイナリ部分を XML の外に出し、
   【MIME のパートとして生のまま】送る
     → サイズが膨らまない
     → ストリームで処理できる

移行メモ(WCF と MTOM の現況):

・【WCF は .NET(Core 系)には移植されなかった】
   → クライアント側のみ System.ServiceModel.* で部分的に利用可
   → サーバ側は【CoreWCF】(コミュニティ主導)が受け皿
・SOAP / MTOM 自体、新規採用はほぼない
   → REST + オブジェクト ストレージ、または gRPC へ

原文の「JavaScript から MTOM の Web サービスは利用できない」
という指摘は、SOAP 系が Web フロントエンドと相性が悪いという
一般的な問題を突いており、
これが REST が主流になった理由の 1 つでもある。

補足(アップロード実装の現在の必須事項): 本ページ全体を通した
セキュリティ上の要点をまとめる。すべて実際の事故につながる

① ファイル名を信用しない(最重要)

// ✗ 送られてきた名前でそのまま保存する
var path = Path.Combine(uploadDir, file.FileName);
//   FileName が "..\\..\\web.config" だったら?(パス トラバーサル)★

// ○ 保存名はサーバが決める
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!Allowed.Contains(ext)) return BadRequest();
var stored = $"{Guid.NewGuid():N}{ext}";
var path = Path.Combine(uploadDir, stored);
// 元のファイル名は DB のメタデータとして保持する

② 拡張子と Content-Type を信用しない

・Content-Type はクライアントが自称するだけ
・拡張子も自由に付けられる
   → 【中身(マジック ナンバー)を確認する】
   → 画像なら、実際にデコードできるか試す
   → [ウイルススキャン](MS_VirusScanning) を通す

③ 保存先を公開ディレクトリにしない

✗ wwwroot/uploads/ に置く
    → アップロードされた .aspx / .php が【実行される】危険 ★
    → 少なくとも実行を無効化する(handlers の削除)

○ ・Web ルート外のディレクトリに置き、
     配信は【コントローラ経由】(認可を効かせる)
   ・またはオブジェクト ストレージに置く(推奨)

④ サイズ・件数・拡張子を必ず上限で縛る

・1 ファイルのサイズ、1 リクエストの合計、1 ユーザーの総容量
・上限がないと【容量枯渇による DoS】になる

⑤ 認可を忘れない

・アップロードできるのは誰か
・アップロードしたファイルを取得できるのは誰か
   → 【推測しにくい名前】は認可の代わりにならない ★

参考

Open 棟梁 Wiki

  • ファイルのアップロード(OTR_FileUpload.md

Microsoft Learn


Tags: 移行, その他、開発の色々

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally