feat(wiki): 三個目錄頁改走專用存取庫,比對鍵改用裸 HASH

What:LOG_CONTENTS、LEARN_CONTENTS、REPORT_CONTENTS 改由 wiki-repo CONTENTS
解析並透過 wiki-contents.sh upsert 寫入,內容頁仍各走自己的型別。LOG 與 REPORT
的目錄頁新增一欄裸 HASH 當比對鍵。

Why:比對鍵原本是含網址的儲存格,換主機、換存取庫或 URL 編碼有差就比對不到,
upsert 會走附加分支,同一頁多出第二列而舊列永遠不再更新。REPORT 更脆:存取庫一換,
雜湊與網址同時變,四個期間的列會一次全部重複。

How:worklog-pending.sh 的 valid_hash 放寬成 40 碼、8 碼與 H 加 7 碼三種形狀。
放寬的是長度不是字元集——先剝字元再比長度的順序保留,路徑穿越與換行注入照樣擋下。
年報不得沿用日誌存取庫那條規則收斂到內容頁,目錄頁同住一庫是另一回事。

Who:jsc-log
This commit is contained in:
2026-09-02 11:02:21 +08:00
parent 39bbf3c505
commit aee17e45c7
12 changed files with 165 additions and 61 deletions
+42 -5
View File
@@ -21,7 +21,10 @@
# worklog-pending.sh commit <hash> <claim> 寫入成功後清掉併入清單上的暫存檔
# worklog-pending.sh abort <hash> <claim> 寫入失敗後保留暫存,只丟掉合併檔與併入清單
#
# <hash> 為 jsc-gitea/tools/hash-id 算出的 8 碼工作日誌 hash,也就是 LOG_{HASH} 的 HASH。
# <hash> 為 jsc-gitea/tools/hash-id 算出的工作日誌 hash,也就是 LOG_{HASH} 的 HASH。
# 現行是完整 40 碼大寫十六進位;8 碼那種是舊規則留下的暫存檔,還沒遷移完之前一併收。
# 舊規則的首碼落在 0-9ABC 就改寫成 H 加原前 7 碼,十六個首碼有十三個會命中,所以既有的
# 8 碼暫存大多長成 H1A2B3C4。不收這種,遷移期間存進去的內容就再也取不回來。
# 存放位置: $JSC_HOME/worklog-pending/{hash}/{UTC 時間}-{pid}.md(JSC_HOME 預設 ~/.jsc)
# 合併檔: $JSC_HOME/worklog-pending/.merge/{hash}-{UTC 時間}-{pid}.md
# 併入清單: 同名換副檔名 .claim,一行一個被併走的暫存檔路徑
@@ -55,11 +58,45 @@ EOF
exit 2
}
valid_hash() { # 只收 8 碼大寫英數,擋掉路徑穿越
case "$1" in
[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]) return 0 ;;
*) echo "[jsc][工作日誌暫存][ERR]:hash 須為 8 碼大寫英數,收到「${1:-空值}」。" >&2; exit 2 ;;
# hash 會直接拼進暫存目錄路徑,這裡是整支腳本唯一的格式驗證,擋的是路徑穿越。
# 放寬長度不等於放寬字元集:先確認每一個字都是十六進位,再比對長度。字元集這一關先過,
# 「.」與「/」才進不了路徑;只看長度就會讓 ../ 那類值溜進來。
# 字元檢查逐字剝,不呼叫 grep 之類逐行比對的工具:值裡夾一個換行,逐行比對會拿第一行
# 當整個值判過,那正是路徑穿越要的缺口。
#
# 這份樣式與 jsc-gitea/tools/page-name.sh、jsc-hooks/hooks/comment-scope.sh 是三份各自
# 獨立的定義,故意不共用:hook 與這個暫存區都必須自足,執行期不能相依別的 plugin 的安裝
# 路徑——那個路徑每個 CLI 不一樣,也可能根本沒裝,抓不到就等於整道護欄失效。三份的一致性
# 改在稽核時比對。
valid_hash() {
raw="${1:-}"
# H 開頭先剝掉再驗其餘:舊規則把首碼落在 0-9ABC 的 hash 改寫成 H 加原前 7 碼,
# H 本身不是十六進位字元,不先剝就會被逐字剝那一關擋掉。剝的是固定字面 H,
# 不是一個字元集,所以放進來的字仍然只有 H 與十六進位這兩種。
case "$raw" in
H*) rest="${raw#H}"; hprefixed=1 ;;
*) rest="$raw"; hprefixed=0 ;;
esac
while :; do
case "$rest" in
[0-9A-F]*) rest="${rest#?}" ;;
*) break ;;
esac
done
if [ -z "$rest" ]; then
if [ "$hprefixed" -eq 1 ]; then
case "$raw" in
????????) return 0 ;;
esac
else
case "$raw" in
????????????????????????????????????????) return 0 ;;
????????) return 0 ;;
esac
fi
fi
echo "[jsc][工作日誌暫存][ERR]:hash 須為 40 碼大寫十六進位(現行),或 8 碼大寫十六進位、H 加 7 碼大寫十六進位(尚未遷移的舊暫存),收到「${1:-空值}」。" >&2
exit 2
}
valid_claim() { # 併入清單只認 merge 產出的那一份,擋掉拿別處的清單來刪檔