Status: Draft
Updated: 2025-07-28 20:07 UTC+09:00
Target: macOS 10.15+(Catalina 以降), Universal 2(arm64 / x86_64)
目的:拡張子
.nar(実体は ZIP アーカイブ)をダブルクリックまたは「このAppで開く」で Ourin が受け取り、install.txtの語彙に従って ghost / balloon / shell / plugin / package を所定ディレクトリに安全に展開・登録するための規格。SSP の慣行(D&D/ダブルクリック)を踏襲しつつ、UTF‑8 を既定・CP932 受理・Zip Slip防止を必須とする。
0. 用語・前提
- .nar:Ukagaka 配布パッケージ。実体は ZIP。
- install.txt:アーカイブ最上位に置かれるインストール定義(
type/directory/*.directoryほか)。 - Ourin 標準設置先:
~/Library/Application Support/Ourin/配下。 - 文字コード:UTF‑8 既定、CP932 受理(内部は UTF‑8 正規化)。
1. 適用範囲と非目標
- 対象:Finder のダブルクリック/「このAppで開く」/Ourin ウィンドウへのD&Dでのインストール。
- 非目標:Windows のバイナリ互換再現や、Install UI の完全模倣。プロトコル(install.txt の語彙)互換を重視。
2. 依存仕様と参考
- Apple UTI/Document Types:
.narを custom UTI としてエクスポートし、public.zip-archiveに準拠。CFBundleDocumentTypesで関連付け、App は open handler を実装する。 - NSApplicationDelegate:
application(_:openFiles:)/application(_:open:)で URL/パス受理。 - Ukadoc:install.txt / 配布 / ネットワーク更新などの運用慣行に準拠(
updates2.dau/delete.txt/ドラッグ&ドロップ/ダブルクリック)。
3. 受付と関連付け(UTI/Document Types)
- UTExportedTypeDeclarations に
jp.ourin.narを宣言し、UTTypeConformsTo = public.zip-archive、拡張子narを紐付ける。 - CFBundleDocumentTypes に
LSItemContentTypes = jp.ourin.narを設定(Role は Viewer/Editor いずれでも可)。 - 競合した場合の優先度は LSHandlerRank で調整。
4. 受け口(open handler)
- App 起動時/起動中の両方で呼ばれるよう、
NSApplicationDelegate.application(_:openFiles:)(複数)とapplication(_:open:)(URL)双方を実装。SwiftUI 構成でも AppKit デリゲートをブリッジ。
5. アーカイブ検証と文字コード判定
- MIME/UTType で zip 互換を確認し、先頭レコードもチェック。
install.txtは UTF‑8 を優先して読む。失敗時は CP932 で再試行し、内部は UTF‑8 正規化。BOM・CRLF も受理。
6. install.txt の語彙(受理する要点)
- 必須:
type,<種別>/directory,<設置名>(supplement・packageはdirectory省略可) - 任意:
accept,<識別名>、*.directory,<同梱名>(例:balloon.directory,MyBalloon)、*.source.directoryなど。 - 推奨:
charset,UTF-8を先頭に明記。
6.1 type の受理値と設置先
| type 値 | 受理される表記バリアント | 設置先(<base> = ~/Documents/Ourin) |
|---|---|---|
ghost |
— | <base>/ghost/<directory> |
balloon |
— | <base>/balloon/<directory> |
shell |
— | <base>/ghost/<accept>/shell/<directory> |
plugin |
— | <base>/plugin/<directory> |
headline |
— | <base>/headline/<directory> |
package |
— | <base>/package/<directory> |
supplement |
— | <base>/ghost/<accept>/ (directory は任意) |
calendar/skin |
calendar skin、calendarskin |
<base>/calendar/skin/<directory> |
calendar/plugin |
calendar plugin、calendarplugin |
<base>/calendar/plugin/<directory> |
calendar |
— (skin/plugin 区別が無い旧形式) | <base>/calendar/<directory> |
language |
— | <base>/language/<directory> |
calendar/skin・calendar/plugin はいずれも大文字小文字・スペース・スラッシュの有無を問わず受理される(InstallTxtParser.swift → OurinPaths.installTarget())。旧形式 calendar(区別なし)は後方互換のため維持。language は UKADOC install.txt の language 種別に対応。
shell および supplement は accept フィールドで対象ゴーストのディレクトリ名を指定する必要がある。未指定時は installTxtMissingKey("accept") エラー。
6.2 refreshundeletemask フィールド
refreshundeletemask,ファイル名パターン1:ファイル名パターン2:...
- UKADOC 準拠の区切り文字は コロン(
:)。 - 既存データとの互換性のため カンマ(
,) も許容する(InstallTxtParser.swift:106)。 - 値は正規表現パターンの集合として
refreshUndeleteMask: [String]に格納される。 refresh,1と組み合わせて使用する。refresh,1が指定されたとき、マスクに一致するパスはインストール前削除から除外される。
例:
refresh,1
refreshundeletemask,savedata:*.sav:userconfig.txt
上記ではコロン区切り 3 要素(savedata、*.sav、userconfig.txt)として解析される。
7. 展開ポリシー(安全な unzip と配置)
- 一時ディレクトリに安全に解凍→
install.txtを解釈 → 設置先にコピー。 - 除外:
__MACOSX/、.DS_Store、Thumbs.dbは自動除外。 - ケース整合:ファイル名の大小を厳密に扱い、重複衝突は警告。
8. 競合と更新(accept/delete/homeurl)
- 既存
{directory}がある場合:accept一致なら上書き更新、相違なら別名提案。 delete.txtがあれば不要資産を削除。更新配布はhomeurl+updates2.dauに追従。
9. ログ・失敗時の UX
- 主要フェーズ(検証/解析/展開/事後処理)を
OSLogに記録。 - 代表エラー:
InstallTxtMissing、UnsupportedType、ZipSlipDetected、NameConflict、DecodeFailed(sjis)。
10. セキュリティ(Zip Slip / 隔離属性)
- Zip Slip:展開前にパス正規化し、
../絶対パス/シンボリックリンクによるターゲット外書込みを拒否。 - ダウンロード由来の隔離属性はユーザ操作起点(ダブルクリック/ドロップ)で正規フローと見做す。
11. 互換性メモ(Windows/大小・改行差分)
- Windows 既存配布は Shift_JIS/
\\区切りのまま入ってくることがある。Ourin は 入力受理し、内部を UTF‑8/UNIX パスに正規化する。 - 改行は CRLF/LF どちらも受理。
付録A. Info.plist 宣言例
<!-- Info.plist 抜粋:UTI と Document Types -->
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key><string>jp.ourin.nar</string>
<key>UTTypeDescription</key><string>Ukagaka NAR</string>
<key>UTTypeConformsTo</key>
<array><string>public.zip-archive</string></array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key><array><string>nar</string></array>
<key>public.mime-type</key><string>application/x-ukagaka-nar</string>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key><string>Ukagaka NAR</string>
<key>LSItemContentTypes</key>
<array><string>jp.ourin.nar</string></array>
<key>CFBundleTypeRole</key><string>Viewer</string>
</dict>
</array>
付録B. 成功/失敗フロー
Finder(.nar) → Launch Services → Ourin(app open)
→ open handler
→ validate(zip) OK? → read install.txt → parse {type,directory,...}
→ resolve install path
→ safeExtract → copy/merge → post steps(readme/terms, switch)
→ success toast
NG:
- no install.txt → error: InstallTxtMissing
- unsupported type → error: UnsupportedType
- zip slip → error: ZipSlipDetected
- directory conflict → user prompt(rename/overwrite)
付録C. サンプル構成
OURIN_NAR_INSTALL_1_0M/
NAR_INSTALL_1.0M_SPEC.md
NAR_INSTALL_1.0M_PLAN.md
sample/
Sources/OurinNarInstallerSample/
AppDelegate.swift
SampleNarInstaller.swift
InstallTxtParser.swift
ZipUtil.swift
Paths.swift
Info.plist (example)
実装状況(Implementation Status)
更新日: 2025-10-20
Ourin における実装
- [x] NAR ファイルの関連付け: Info.plist にて
.narの UTI 宣言と関連付けを実装済み - [x] ダブルクリック/D&D 受付: AppDelegate での openFiles ハンドラ実装済み
- [x] ZIP 展開:
ZipUtil.swiftにて実装済み - [x] install.txt 解析:
InstallTxtParser.swiftにて実装済み - [x] 文字コード自動検出: UTF-8 および CP932 の自動検出を実装済み
- [x] Zip Slip 防止:
ZipUtil.secureCopyTree()にて実装済み - [x] 型別インストール:
OurinPaths.installTarget()にて ghost/balloon/shell/supplement/plugin/headline/package/calendar/calendar/skin/calendar/plugin/language の配置先解決を実装済み - [x] 基本的なエラー処理: インストールエラーの検出と報告を実装済み
- [ ] 競合時の UI: accept/delete/homeurl の完全な UI 実装は未完了
- [x] 更新機能:
updates2.dau/updates.txt/update.txtの取得、Version 2/3形式の解析、MD5検証、増分ファイル/NAR適用を実装済み。ベースウェア更新はステージング.appへ適用する。 - [x] 削除機能: ネットワーク更新時の
delete.txt取得・安全な相対パス削除を実装済み(パッケージ内の削除指示にも対応)。
実装済みの機能
-
パッケージ受付 - Finder からのダブルクリック - Ourin ウィンドウへのドラッグ&ドロップ - 「このアプリケーションで開く」からの起動
-
展開と検証 - ZIP ヘッダの検証(PK マジックナンバー) - 一時ディレクトリへの安全な展開 - install.txt の存在確認
-
install.txt 解析 -
typeフィールドの解析(ghost, balloon, shell, supplement, plugin, headline, package, calendar/skin, calendar/plugin, calendar, language) -directoryおよび*.directoryの解析 - UTF-8/CP932 の自動判定とデコード -
安全な配置 - Zip Slip 攻撃の防止(親ディレクトリへの脱出防止) -
~/Library/Application Support/Ourin/配下への配置 - 型別のディレクトリ振り分け(ghost, balloon, shell, supplement, plugin, headline, package, calendar/skin, calendar/plugin, calendar, language) -
エラーハンドリング - NAR 形式エラー(NotZip) - install.txt 欠落(InstallTxtNotFound) - デコードエラー(InstallTxtDecodeFailed) - 未対応型(UnsupportedType) - Zip Slip 検出(ZipSlipDetected) - ディレクトリ衝突(DirectoryConflict)
未実装の機能
-
競合解決 UI -
acceptフィールドによる上書き確認ダイアログ - ユーザーへのリネーム/上書き選択肢の提示 -
削除処理 - アンインストール時のファイル削除 UI
-
高度な機能 - README/利用規約の表示 - インストール後の自動切り替え - homeurl の処理
ソースコード位置
Ourin/NarInstall/LocalNarInstaller.swift: インストーラ本体Ourin/NarInstall/InstallTxtParser.swift: install.txt 解析Ourin/NarInstall/ZipUtil.swift: ZIP 展開ユーティリティOurin/NarInstall/Paths.swift: パス解決(OurinPaths)Ourin/NarInstall/NarInstallViewModel.swift: UI ViewModelOurin/NarInstall/NarInstallView.swift: SwiftUI インストール画面
変更履歴
- 2026-08-14:
updates2.dau/updates.txtのMD5検証付き増分更新とdelete.txtの安全な取得・削除を実装。更新/削除パスのトラバーサル検査と回帰テストを追加。 - 2026-06-28: §6.1 type 受理値テーブルを追加(calendar/skin、calendar/plugin、calendar、language を追記)。§6.2 refreshundeletemask フィールドの区切り文字仕様(コロン主・カンマ互換)を追記。実装状況の type 一覧を更新。
- 2025-10-20: 実装状況セクションを追加
- 2025-07-28 20:07 UTC+09:00: 初版(NAR-INSTALL/1.0M)。
出典と編集について
Ourin / Furin Lab・contributors。文書はCC BY-SA 4.0(第三者の個別表記を除く)。Web版は見出し・リンクの整備とローカルパスの一般化を行っています。更新基準日: 2026-10-30。
元資料: docs/NAR_INSTALL_1.0M_SPEC_ja-jp.md
ソース SHA-256: 0ddef35f601345850e5710100aa4a38c323894833eeec0af41f96c459717bfc2