diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..f2ffbce --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,36 @@ +name: Test + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + node: ['20.19.0', '22'] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: npm + cache-dependency-path: npm-shrinkwrap.json + - run: npm ci + - run: npm run test:unit + - run: npm run check:contrast + - run: npx playwright install --with-deps chromium + - run: npm run test:e2e -- --workers=2 + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-results-node-${{ matrix.node }} + path: test-results/ + retention-days: 7 diff --git a/README.md b/README.md index 3c0ef71..bac94d6 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ docview ./docs --no-open ## Custom CSS -Place a `.docview.css` file in the directory you are viewing (or any parent directory). DocView will automatically load it and apply your styles on top of the default theme. +Place a `.docview.css` file in the directory you are viewing. DocView will automatically load it and apply your styles on top of the default theme. Symlinks must resolve inside the served directory. Set `--font-size-base` to customize the document size used at the 100% Text size setting. @@ -179,6 +179,22 @@ fixtures/** # everything under fixtures/ `.gitignore` is intentionally **not** honored automatically, because files like `.env` are commonly git-ignored yet you usually want to view them here. +## Development and verification + +Use `npm ci` to reproduce the dependencies in `npm-shrinkwrap.json`. Installation +also builds the frontend. Run `npm run test:unit`, `npm run check:contrast`, and, +after `npx playwright install chromium`, `npm run test:e2e` to verify changes. + +To run the CLI using this checkout's fixed dependency tree, use +`node bin/mdv.mjs /path/to/documents` after `npm ci`. In the npm 11.9 environment +used for the audit, installing DocView as a dependency from GitHub or a tarball +re-resolved upstream-pinned dependencies instead of using the bundled shrinkwrap; +see [the audit record](docs/audit-2026-09-13.md) for the remaining dependency issue. + +The C2PA native module is optional. If it cannot be installed, normal document +viewing and byte-based provenance detection remain available; cryptographic +C2PA verification is unavailable in that environment. + ## Tech Stack - **[Vite](https://vitejs.dev/)** — build tooling and dev server diff --git a/bin/mdv.mjs b/bin/mdv.mjs index df14033..6e1f8b0 100755 --- a/bin/mdv.mjs +++ b/bin/mdv.mjs @@ -73,16 +73,25 @@ const child = fork(serverPath, filteredArgs, { stdio: ['inherit', 'pipe', 'inher child.stdout.on('data', (data) => { process.stdout.write(data); - const match = data.toString().match(/localhost:(\d+)/); - if (match && !noOpen) { - const actualPort = match[1]; - const url = `http://localhost:${actualPort}/`; - const platform = process.platform; - const cmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open'; - execFile(cmd, [url], () => {}); - } }); +// The server already reports readiness over IPC. Parsing stdout can match a +// document directory containing "localhost:1234", or miss a split log chunk. +let opened = false; +child.on('message', (message) => { + if (message?.type !== 'listening' || noOpen || opened) return; + opened = true; + const url = message.url || `http://localhost:${message.port}/`; + const platform = process.platform; + // `start` is a cmd.exe builtin, not an executable accepted by execFile. + const cmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'explorer.exe' : 'xdg-open'; + execFile(cmd, [url], () => {}); +}); + +for (const signal of ['SIGINT', 'SIGTERM']) { + process.on(signal, () => child.kill(signal)); +} + child.on('error', (err) => { process.stderr.write(`Failed to start DocView server: ${err.message}\n`); process.exit(1); diff --git a/docs/audit-2026-09-13.md b/docs/audit-2026-09-13.md new file mode 100644 index 0000000..c507ab0 --- /dev/null +++ b/docs/audit-2026-09-13.md @@ -0,0 +1,121 @@ +# DocView 不具合監査・修正記録 + +対象: `main` の `563094060805aa542075a25c9e857a5a6e611689`。 +サーバー、CLI、全フロントエンドモジュール、画像解析、既存テスト、テーマ、依存関係、配布設定をレビューした。 +以下は今回確認・修正した問題の一覧であり、未知の不具合が存在しないことを保証するものではない。 + +## サーバー・CLI + +| 問題 | 対応 | +|---|---| +| 不正なパーセントエンコードの raw URL でサーバーが終了 | 400 応答に変換 | +| diagram API の `null`・不正な source 型でサーバーが終了 | JSON 形状・文字列型を検証 | +| 外部描画 CLI の stdin エラー・stderr 詰まり | エラーハンドラーと出力処理を追加 | +| 検索・backlinks・tree・gallery・custom CSS の外部 symlink 漏洩 | 公開ルート内への realpath 解決を検証 | +| IPv6 URL の角括弧によりリモート取得失敗 | 接続ホストを正規化 | +| IPv4-mapped IPv6 と link-local 範囲の private 判定漏れ | 対象表現と範囲を修正 | +| OS opener 不在でプロセスが終了 | 非同期起動失敗を API エラーへ変換 | +| TCP チャンク境界で日本語 JSON が破損 | バイト列結合後に UTF-8 復号 | +| JSON 本文のサイズ上限が文字数依存 | バイト数で制限 | +| ZIP の生成連番と実ファイル名が衝突 | 全エントリーを一意化 | +| `.docviewignore` の `?` による不正な正規表現 | 1 文字ワイルドカードへ変換 | +| `.env` の変更がライブ更新されない | 対象の隠しファイルを監視 | +| 巨大な remote サイズ指定が上限を無効化 | 安全な整数を検証 | +| stdout 内の URL による誤ったブラウザー起動・重複起動 | IPC の ready 通知を使用 | +| Windows の `start` を実行可能ファイルとして起動 | `explorer.exe` を使用 | +| CLI 終了時の子プロセス残存 | SIGINT / SIGTERM を伝搬 | +| 病的な正規表現が HTTP イベントループを停止 | Worker へ隔離し 1 ファイル 1 秒で打ち切り | + +主な回帰テスト: `tests/server-audit.test.mjs`。 + +## ナビゲーション・検索 + +| 問題 | 対応 | +|---|---| +| 古い file / SVG / PDF / Office / remote 応答が新画面を上書き | 読み込み世代を検証 | +| 閉じた分割ペインの応答が左ペインへ描画 | 世代・元 DOM・ペイン状態を検証 | +| 検索を閉じる・消す・モード変更後に旧結果が復活 | 入力変更時点で検索を失効 | +| 遅いファイル一覧取得後に現在の検索が更新されない | 現在の入力を再評価 | +| URL バーで後から作成されたファイルを開けない | 送信時に存在確認を再実行 | +| キャンセルした URL バーの存在確認完了後に遷移 | 入力・表示状態を検証 | +| 遅延 import 失敗が永続キャッシュされる | 失敗時に Promise キャッシュを解放 | +| 相対 URI・`..`・remote 文書内の画像やリンクが誤解決 | 文書の実 URL / パスを基準に解決 | +| 巨大な `line=1-999999999999` でブラウザーが停止 | 実在する DOM 行だけを走査 | +| ゼロ・安全な整数を超える行指定 | 不正な行指定を除外 | +| TOC の ID 内 `&` が実体参照へ変換 | 属性値を適切にエスケープ | +| 破損した recent localStorage で起動失敗 | 配列・文字列の型を検証 | +| 比較終了後のスケルトン・タイトルの不整合 | 元ファイルを再描画 | +| 分割表示・File 入力で動画をテキスト扱い | 動画プレーヤーへ接続 | +| ファイル選択の accept が新形式を含まない | 対応拡張子と整合 | +| Marp 遅延生成・重複・相対画像の不整合 | 世代、オーバーレイ、画像 URL を補正 | + +主な回帰テスト: `tests/navigation-audit.spec.ts`。 + +## 文書・データ表示 + +| 問題 | 対応 | +|---|---| +| HTML の画像保存時にスクリプトが親 DOM / storage へ到達 | 通常プレビューから DOM を受け取り、スクリプト禁止 iframe で撮影 | +| scripts off の HTML でも保存時にスクリプトが実行 | 撮影側では常に scripts を禁止 | +| HTML リソース URL の `%20` / `%25` 二重エンコード | 一度デコードし正規化後に再エンコード | +| Marp のコメント付き true を見落とし、nested true を誤認 | YAML ルートの boolean を検証 | +| Wiki link の fragment / 親ディレクトリのドットを誤処理 | ファイル名だけで拡張子を判定 | +| Mermaid 構文エラーの一時 SVG が画面に残る | 内部エラー描画を無効化 | +| 空 CSV のパスが HTML として挿入 | エスケープ | +| CSV の `00123`・ISO 日時・指数表記が変形 | 原文の文字列を保持 | +| 空欄・指数表記のある数値列のソート不正 | 数値判定と比較を補正 | +| JSONL の空行・不正行後の行番号ずれ | 元の行番号を保持 | +| JSONL の欠落キーから継承プロパティが表示 | own property のみ使用 | +| JSON / YAML の空文字キーが消える | ルート判定と分離 | +| 循環 YAML で表示失敗 | 循環参照を明示 | +| Secret Safe の短い値・引用値・複合キー・コンテナ漏れ | キーと値全体を検証してマスク | +| CSV / YAML の非表示 Source DOM に機密値が残る | 形式別に Source もマスク | +| 大きな表・未知拡張子・title 属性でマスク欠落 | 全表示経路へ適用 | +| chunk 検索で HTML 実体参照が壊れる | 生文字列で一致位置を求め、各部分をエスケープ | +| 古い chunk 検索・ページ応答で表示上書き | 世代と画面の所有状態を検証 | +| chunk 通信失敗で Loading が残る | エラーと移動ボタン状態を更新 | +| 推定行数が少ない大きな JSONL の深いリンクがずれる | 実測件数で調整 | +| CSV / TSV の引用符内改行がページ境界で壊れる | `records=1` で論理レコード単位の range / search | +| CSV 検索でヘッダーがデータ行に混ざる | ヘッダーを別メタデータとして保持 | +| Laravel CRLF ログを形式判定できない | CR を除去 | +| cron コマンド内の空白が潰れる | 本文を保持 | +| コマンドなし・実行不能日の cron を有効扱い | 構文と実行可能性を検証 | +| cron 衝突時刻のタイムゾーン不整合・検査範囲の誤表示 | JST に統一し 35 日 / 1,500 回の上限を明示 | + +主な回帰テスト: `tests/rendering-audit.spec.ts`、`tests/data-audit.spec.ts`。 + +## 画像・アルバム + +| 問題 | 対応 | +|---|---| +| SVG の megapixels `N/A` に数値メソッドを呼び表示失敗 | 型を検証 | +| lossy WebP の寸法が 1 px 大きい | VP8 の値をそのまま使用 | +| 短い VP8L ヘッダーを読めない | 形式別の最小サイズを検証 | +| SVG の `%` 指定を px と誤認 | viewBox にフォールバック | +| 不完全 PNG の寸法・テキストを採用 | チャンク型・長さ・範囲を検証 | +| 非有限寸法で GCD が終了しない | 正で有限の値だけを採用 | +| metadata パネルの再生成で DOM が重複 | 所有要素を削除 | +| クリップボード失敗でも成功表示 | 結果を待ち、失敗を表示 | +| 古い album 応答が現在の画像・キーボード状態を上書き | AbortController と世代・DOM を検証 | +| メディア待機中にライトボックスを閉じられない | 待機前に閉じる操作を登録 | +| 遅い SVG が次の画像を上書き | 移動・終了時に中断 | +| 終了後の動画再生・ズームイベント残存 | メディアとリスナーを解放 | + +主な回帰テスト: `tests/image-meta-audit.test.mjs`、`tests/media-audit.spec.ts`。 + +## テーマ・依存関係・検証 + +- 既存コントラスト検査の 11 失敗を修正。半透明色を背景と合成して検査し、30 テーマ × 10 項目を確認する。 +- DOMPurify、markdown-it、Mermaid、Vite と推移依存を互換範囲で更新。固定依存環境の `npm audit` は 14 件から 0 件。 +- C2PA を任意依存にし、ネイティブ導入失敗時も閲覧とバイト走査を利用可能にした。 +- npm の固定依存を `npm-shrinkwrap.json` で管理する。 +- Node の単体テストと Playwright のテスト探索を分離する。 +- GitHub Actions に Node 20.19.0 / 22 のインストール・ビルド・単体・コントラスト・E2E 検証を追加する。 + +## 検証上の制約と残件 + +- ローカル環境では Chromium が起動時に SIGTRAP で終了し、接続済みブラウザーから localhost へのアクセスも拒否された。ブラウザー E2E の結果は PR の GitHub Actions 結果を参照する。テスト登録・型チェックの成功だけを E2E 成功として扱わない。 +- Windows / macOS の OS アプリ起動は実機未検証。 +- HTML 撮影では読み取り可能な Canvas を画像として保持する。CORS で読み取り不能な Canvas、closed Shadow DOM、属性に反映されないフォーム状態は完全には保持できない。CSP 等で応答を得られない場合は元 HTML の安全な描画へフォールバックする。 +- この検証環境の npm 11.9 では、最終コミットを GitHub または tarball から別プロジェクトの依存として導入すると、同梱 shrinkwrap の修正版解決が適用されず、sharp 0.34.3 / xmldom 0.9.10 が再導入された。ソース・tarball・導入後の shrinkwrap の一致と、実際の node_modules の版を確認した。audit 0 件はリポジトリの `npm ci` 環境に限定される。固定依存を使うにはチェックアウトで `npm ci` 後に `node bin/mdv.mjs /path/to/documents` を実行する。上流固定依存の解消は残件。 +- C2PA native が利用できない環境では暗号署名の検証は行えない。縮退経路は単体テストで確認した。 diff --git a/index.html b/index.html index a2a1168..09aa613 100644 --- a/index.html +++ b/index.html @@ -161,7 +161,7 @@