Global

Members

st200

Description:
  • 六路由之差異表(單一來源)

    供 WConverhpServer 之路由前置(ctxOf / admit)、錯誤回覆(replyOf)與下載欄位檢核(readOutput)查表; 亦供 test/unit-routeSpec.test.mjs 與測試端之路由軸(test/api-axes.mjs)逐欄對照 —— 兩端各自一份, 不互相 import, 不一致即紅

    why 寫成表而非散在各路由之參數列(第十一輪 G6, 兩份複審一致): 差異(apiType 字面、handler 事件之 api 字面、authorization 之來源、錯誤狀態碼、下載欄位之讀取與檢核順序) 原本手寫展開於六條路由之 40 處同型片段, 每加一條規則就得寫六遍而漏其一(第十輪 A5/A6/A9/F10 各改 3–6 處; 第十一輪 N2/N4 皆為「同一規則第二站點沒套」)。 表上少一欄由測試直接對表斷言, 比數 grep 命中數有分辨力

    欄位: apiType: verifyConn 所見之 apiType(對外契約) api: handler 事件所見之 api(對外契約) authFrom: 應用端所見之 authorization 來源 —— 'header' 為請求標頭原樣, token 為確認授權方案前綴相符後切出者(帳本 R15); 'query' 為由 query 之 token 合成 <tokenType> <token>(/dwgf 之下載由瀏覽器導覽, 無標頭可用; 合成為刻意, 使應用端 verifyConn 於六路由所見同一形狀) statusOf: 各種錯誤之 HTTP 狀態碼 —— permission(verifyConn 未通過)、param(請求參數錯誤, 可證明不需重試, 另標示 retryable:false)、app(應用端拒絕或無人接聽)、 output(應用端交出之內容不合契約)、packet(請求本體無法解析)、internal(套件內部失敗, 如回應無法序列化)。 五路由由 JS 解析回應, 一律 200 並以 Return-Type 與錯誤封包表達(帳本 R6); /dwgf 之唯一消費者為瀏覽器下載管理器(只看狀態碼), 以非 2xx 表達(R6 之例外, 第十輪 A9) fields: 下載路由讀取與檢核應用端回傳欄位之順序(契約: 多重形狀錯誤時回哪一個訊息由此決定, 三路由各不相同, test/api-characterization.test.mjs 鎖住); optional 者判定不通過且判定本身未拋錯時視為未給

Source:

六路由之差異表(單一來源)

供 WConverhpServer 之路由前置(ctxOf / admit)、錯誤回覆(replyOf)與下載欄位檢核(readOutput)查表; 亦供 test/unit-routeSpec.test.mjs 與測試端之路由軸(test/api-axes.mjs)逐欄對照 —— 兩端各自一份, 不互相 import, 不一致即紅

why 寫成表而非散在各路由之參數列(第十一輪 G6, 兩份複審一致): 差異(apiType 字面、handler 事件之 api 字面、authorization 之來源、錯誤狀態碼、下載欄位之讀取與檢核順序) 原本手寫展開於六條路由之 40 處同型片段, 每加一條規則就得寫六遍而漏其一(第十輪 A5/A6/A9/F10 各改 3–6 處; 第十一輪 N2/N4 皆為「同一規則第二站點沒套」)。 表上少一欄由測試直接對表斷言, 比數 grep 命中數有分辨力

欄位: apiType: verifyConn 所見之 apiType(對外契約) api: handler 事件所見之 api(對外契約) authFrom: 應用端所見之 authorization 來源 —— 'header' 為請求標頭原樣, token 為確認授權方案前綴相符後切出者(帳本 R15); 'query' 為由 query 之 token 合成 <tokenType> <token>(/dwgf 之下載由瀏覽器導覽, 無標頭可用; 合成為刻意, 使應用端 verifyConn 於六路由所見同一形狀) statusOf: 各種錯誤之 HTTP 狀態碼 —— permission(verifyConn 未通過)、param(請求參數錯誤, 可證明不需重試, 另標示 retryable:false)、app(應用端拒絕或無人接聽)、 output(應用端交出之內容不合契約)、packet(請求本體無法解析)、internal(套件內部失敗, 如回應無法序列化)。 五路由由 JS 解析回應, 一律 200 並以 Return-Type 與錯誤封包表達(帳本 R6); /dwgf 之唯一消費者為瀏覽器下載管理器(只看狀態碼), 以非 2xx 表達(R6 之例外, 第十輪 A9) fields: 下載路由讀取與檢核應用端回傳欄位之順序(契約: 多重形狀錯誤時回哪一個訊息由此決定, 三路由各不相同, test/api-characterization.test.mjs 鎖住); optional 者判定不通過且判定本身未拋錯時視為未給

Methods

attempt(stage, fn) → {Object}

Description:
  • 觸碰應用端可控值之唯一入口

    應用端可用會拋錯之getter或Proxy包裝其交給套件之值(觀測、記錄之常見手法), 而任何屬性讀取都可能拋錯: instanceof 會觸發 Proxy 之 getPrototypeOf trap; typed array 之 .length 對 Proxy receiver 會拋 Method get TypedArray.prototype.length called on incompatible receiver; stream.pipeline 建立時會讀來源之 pipe 等方法。 這些例外若逸出 handler, hapi 只能回裸 HTTP 500, 且套件不發任何 error 事件, 應用端無從得知是自己給錯值。

    本原語把每一次觸碰收斂為 tagged result, 使「公開訊息穩定、內部真因不丟」兩者兼得: 成功回 { ok: true, value };失敗回 { ok: false, stage, cause } stage 供組錯誤訊息, cause 為原始例外訊息 —— 公開訊息維持既有字面(invalid streamRead 等), 真因只進 error 事件供診斷

    取因用 wsemi 之 getErrorMessage 而非 String(err): 後者對 toString 會拋錯之物件、對 null 與 undefined 皆拋錯, 會使 catch 區塊自身拋出而讓例外仍逸出、外層 try 形同虛設。getErrorMessage 以候選階梯取值且整體包 try, 契約上於任何輸入下皆不拋錯且回傳必為字串(wsemi 1.8.90 實測 25 種惡意輸入: 0 拋錯、0 非字串), 但其取不到內容時回空字串(如 new Error()、已撤銷之 Proxy), 故此處另補非空退路

Source:
Parameters:
Name Type Description
stage String

輸入本次觸碰之階段名稱字串, 供錯誤訊息辨識

fn function

輸入實際執行觸碰之函數

Returns:

回傳 { ok: true, value } 或 { ok: false, stage, cause }

Type
Object

buildDownloadSource(streamRead, fileSize, funError, optopt) → {Object}

Description:
  • 將應用端交出之streamRead收斂為可交給hapi之回應本體, 並保證實送位元組數與fileSize一致

    why: 伺服器以fileSize寫Content-Length, 應用端給錯時hapi與node皆不會替套件擋: 缺streamRead、非串流物件 → 本體不完整而連線懸置至前端閒置逾時(預設5分鐘, 再乘retryDownload); 宣告大於實送 → 同樣懸置; 宣告小於實送 → 回200且前端存下被截斷之檔案並判成功(靜默毀損); stream-like、objectMode → hapi拒收回裸500且來源未被銷毀; 已destroy之串流(如瀏覽器兩階段下載重用同一串流) → 懸置

    作法: 可事前具體化者(Buffer、Uint8Array、字串、數值、布林、可JSON化物件, 皆為hapi現已接受之本體型別, 維持相容)先算實際長度, 不符即回錯誤封包(標頭尚未送出); 真串流則以計數串流(pipeline接於其後)包住: 超量立即以錯誤終止, 來源正常結束但不足則於flush產生錯誤 —— 兩者皆令hapi中止回應(res.destroy), 前端收到不完整而失敗, 不會把壞檔當成功; 來源自身出錯者沿用其錯誤不另報; pipeline亦使hapi於前端中斷時銷毀計數串流後連帶銷毀來源(維持原有收尾)

    不採instanceof Readable之白名單: 會擋掉目前可正常下載之Buffer與plain object用法

    四個階段(辨識 / 狀態讀取 / 建立pipeline / 具體化)全部經attempt: 應用端可用Proxy或拋錯之getter包裝其值, 任一階段之屬性讀取皆可能拋錯而逸出成裸HTTP 500(見attempt之說明)

    取捨(須知): 可事前具體化者由本函數以JSON.stringify產生bytes後交hapi, 故該route之json政策(replacer/space/suffix/escape)不套用於下載本體. 代價是外部serverHapi若設有json.replacer(如移除敏感欄位), 對download本體不生效; 換得的是本體長度可於送標頭前確知並與fileSize比對. 應用端若需套用自訂序列化政策, 應自行序列化後以字串或Buffer交出

Source:
Parameters:
Name Type Attributes Default Description
streamRead *

輸入應用端交出之本體來源

fileSize Number

輸入應用端宣告之位元組數(已經呼叫端以isValidFileSize檢核並以cint正規化)

funError function

輸入長度不符時之回報函數, 參數為原因字串

opt Object <optional>
{}

輸入設定物件, 預設{}

Properties
Name Type Attributes Default Description
forHead Boolean <optional>
false

輸入本次是否為HEAD請求布林值, 預設false。HEAD不送本體故不建計數串流, 直接交來源供hapi收尾

Returns:

回傳 { source } 或 { error, reason };error為回前端之公開訊息, reason供error事件

Type
Object

callApp(ev, evEmit, name, args, pm, funError) → {Boolean}

Description:
  • 派發一次應用端呼叫, 並保證該次呼叫必定終結

    why: 本套件之路由層刻意關閉 timeout.server 與 timeout.socket(大檔傳輸本就超過任何固定值), 宿主之兜底因此不存在。無人接聽時該 pm 永無人 settle, 請求即永久懸置且 0 則 error 事件 (實測 tmp/probe_r9_hang.mjs: /main、/dw、/ulctr 三入口皆 6000ms 未回應)。 而該事實其實早就在套件手上 —— eventemitter3 之 emit 對無監聽器回 false, 只是被丟棄。

    why 於派發前以 listenerCount 判定, 而非取 evEmit 之回傳值: wsemi 之 evEmit 對「無監聽器」與「監聽器同步拋錯」皆回 false(其 evEmit.mjs:107 之 catch 分支), 兩者不可分辨(實測 tmp/probe_r9_emitret.mjs)。而拋錯那格已由 evEmit 之 funSettle 拒絕過 pm、 且已發過一則 error 事件 —— 以回傳值判定會對該格再發一則, 即同一次失敗兩則(違反規則帳本 R5)。 派發前判定則兩者分明, 且不受「監聽器於執行中移除自己」影響(判定早於執行)。

    本函數只處理「現在沒有人接聽」這一種狀態。應用端接了卻不回話(忘記 settle pm、 resolve 一個永不 settle 之 promise、verifyConn 回 pending promise)一律不在本函數職責內 —— 那是對無限未來之斷言, 任何有限時點皆與「還沒好」不可分辨, 屬呼叫端責任。判準與否決過的修法見帳本 R12 之分界線。

Source:
Parameters:
Name Type Description
ev Object

輸入事件物件, 為 wsemi 之 evem 所建立之 eventemitter3 實例

evEmit function

輸入本套件之派發函數, 簽章為 (name, ...args)

name String

輸入事件名稱字串

args Array

輸入事件參數陣列(不含 pm)

pm Object

輸入本次呼叫之回覆通道, 會作為事件之最後一個參數交給監聽器

funError function

輸入無人接聽時之回報函數, 參數為原因字串

Returns:

回傳是否已派發; false 代表無人接聽且 pm 已被拒絕

Type
Boolean

canonProtocolValue(v) → {*}

Description:
  • 正規化協定鍵之值, 使該鍵不可能於序列化時消失

    why: JSON對值為undefined、function、symbol之鍵是「靜默丟棄整個鍵」而非拋錯 (見wsemi之obj2stru8arr檔頭所列失真集合), 故以編碼器對 { success: undefined } 編碼會回 state 為 success 但解回 {} —— 連success鍵都不存在。前端只能以無意義之'data does not contain success or error'拒絕, 而伺服器亦不發任何error事件, 應用端無從得知

    於包入協定外殼前先把這三種值轉為null(與procApp對output已有之處置一致), 協定鍵遂於結構上不可能消失, 不需encode後再decode回來檢查(大輸出之代價加倍)

Source:
Parameters:
Name Type Description
v *

輸入協定鍵之值

Returns:

回傳可安全序列化之值

Type
*

destroyStreamRead(v)

Description:
  • 清理應用端交出之串流

    只對「像串流(有pipe)且可destroy」者呼叫, 不對任意值(Buffer、字串、數值、plain object)盲呼叫destroy

    why try須包住屬性讀取本身而非只包v.destroy(): 應用端物件之pipe/destroy可能是會拋錯之getter或Proxy trap, 只包v.destroy()時該例外會逸出handler, hapi只能回裸HTTP 500且伺服器不發error事件 —— 與本函數要收斂之情形同型

    本函數不回報成敗: 清理為盡力而為, 失敗不應改變呼叫端之錯誤處置流程

Source:
Parameters:
Name Type Description
v *

輸入應用端交出之值

encodeOut(out, funError) → {Uint8Array|null}

Description:
  • 序列化回應封包之唯一出口

    兩道保護:

    1. 協定鍵之值先經canonProtocolValue正規化, 使該鍵於結構上不可能因JSON之靜默失真而消失
    2. 以wsemi之嚴格模式取狀態, 值無法序列化者(如含BigInt、循環參照)回null並呼叫funError

    why 不用「encode後再decode回來檢查協定鍵」: 該作法對大輸出之代價加倍, 而第1點已於結構上保證

Source:
Parameters:
Name Type Description
out Object

輸入待序列化之封包物件, 其鍵為協定鍵(success或error)

funError function

輸入序列化失敗時之回報函數, 參數為原因字串

Returns:

回傳序列化結果; 失敗時回null(已呼叫funError)

Type
Uint8Array | null

encodeRfc5987(s) → {String}

Description:
  • 將字串編碼為 RFC 5987/8187 之 ext-value 內容, 供 Content-Disposition 之 filename*=UTF-8''<此值> 使用

    與 encodeURIComponent 之差異: 後者不編碼 ' ( ) *, 但此四字元不在 RFC 5987 attr-char 內, 須一併 percent-encoding; 孤立代理對(lone surrogate)會使 encodeURIComponent 拋 URIError, 逐 code point 編碼並以 U+FFFD 取代, 使任何字串皆可安全置入標頭

Source:
Parameters:
Name Type Description
s String

輸入字串(非字串以 cstr 轉換)

Returns:

回傳僅含 attr-char 與 %XX 之字串

Type
String

hasPipe(v) → {Boolean|null}

Description:
  • 安全判定值是否像串流(有可呼叫之pipe)

    回三態, 不回布林二態: true 確定像串流 false 確定不像串流 null 判定失敗(pipe為會拋錯之getter或Proxy trap) —— 其值本就不可用, 由呼叫端收斂為錯誤封包

    why 須區分 false 與 null: 「確定不是串流」者(如plain object)可走具體化路徑, 而「讀不到」者不可當成「不是串流」而繼續處理, 否則後續之JSON.stringify等操作會再次觸發同一個拋錯getter

    why try 須包住屬性讀取本身: 應用端物件之 pipe 可為會拋錯之 getter 或 Proxy trap(帳本 R1)

Source:
Parameters:
Name Type Description
v *

輸入任意值

Returns:

回傳三態判定

Type
Boolean | null

isPathInside(base, target, path) → {Boolean}

Description:
  • 判定target是否位於base資料夾之內(不含base本身)

    以path.relative判定而非startsWith(base + sep): 後者於base為磁碟根目錄(如 C:\ 或 /)時, resolve結果自帶尾分隔符, 再加sep會變成雙分隔符而使合法路徑被誤拒。逸出判準採is-path-inside之作法: relative結果為 '..'、以 '..'+sep 開頭, 或為絕對路徑

    本函式為純字串判定, 不解符號連結亦不存取檔案系統; 呼叫端須先以realpath正規化base, 並自行處理目標為符號連結之情形 path模組由呼叫端傳入: 本模組亦會被打包進瀏覽器端client, 不可於頂層import 'path'; 亦可傳入path.win32/path.posix供跨平台測試

Source:
Parameters:
Name Type Description
base String

輸入基準資料夾絕對路徑

target String

輸入目標絕對路徑

path Object

輸入path模組(或path.win32、path.posix)

Returns:

回傳target是否位於base之內

Type
Boolean

isSafeId(s) → {Boolean}

Description:
  • 檢核前端可控之識別字(fileHash、packageId等)是否安全

    此類字串會直接參與伺服器檔案路徑之組裝(pathUploadTemp下之切片檔、合併檔、.done、.error), 含 . / \ : 即可能以 ../ 逸出資料夾造成路徑穿越, 故僅允許英數字(xxhash為16位hex, 自然符合)並限制長度

Source:
Parameters:
Name Type Description
s String

輸入識別字

Returns:

回傳是否安全

Type
Boolean

isValidFileSize(v) → {Boolean}

Description:
  • 檢核應用端給之fileSize是否可用

    fileSize有兩個用途, 兩者決定了值域:

    1. 原樣(經cint正規化後)寫入Content-Length —— HTTP之訊息分框依據
    2. 與實際位元組數比較(可事前具體化者比 buf.length, 串流則於計數之flush比) —— 故比較前須先以cint轉為數值

    why 須為安全整數: 原以lodash isNumber檢核, 其對NaN、Infinity、負數、小數皆回true, 此類值交給node寫標頭即拋錯, hapi於送標頭階段失敗只能直接斷線, 前端連回應標頭都收不到而伺服器亦不發error事件; 另超出安全整數者(如Number.MAX_SAFE_INTEGER+1或1e21)雖為整數, 但其字串形式帶指數記號(如'1e+21'), 不合Content-Length之1*DIGIT語法, 實測同樣是連線懸置且無回應標頭

    採wsemi之isp0int搭配useLimitSafe, 故數字字串(如'1058915')亦視為有效, 呼叫端須以cint正規化後才可用於比較與寫標頭(帳本R4b)

Source:
Parameters:
Name Type Description
v *

輸入任意值

Returns:

回傳是否為可用之fileSize

Type
Boolean

isValidHeaderValue(name, value) → {Boolean}

Description:
  • 檢核值是否可作為HTTP回應標頭之值

    以node之低階標頭驗證(同setHeader之判定, 禁CR/LF與其他控制字元、禁超出latin1範圍者)檢核應用端或請求端可控之標頭值

    why: 原只以isestr檢核, 含CR/LF或非latin1字元者交給hapi設定標頭時拋錯而回裸HTTP 500, 非套件之錯誤封包, 前端無法解析且伺服器不發error事件

Source:
Parameters:
Name Type Description
name String

輸入標頭名稱字串

value *

輸入待檢核之標頭值

Returns:

回傳是否為合法標頭值

Type
Boolean

normalizeUploadInput(v) → {Blob|Uint8Array|null}

Description:
  • 將 upload 之輸入正規化為兩種表示之一: Blob(含 File)或位元組視圖(Uint8Array, Buffer 原樣保留)

    why: 原本 upload 不正規化, 而大小、切片、雜湊三者對同一輸入各自解讀 —— 大小以 bb.size、bb.length 依序猜, 切片以 bb.slice, 雜湊以 new Blob([inp]); 實測(第十輪 D2): ArrayBuffer 兩個屬性皆無而大小取 1, 雜湊卻以整個 ArrayBuffer 計, 應用端以 success 收到 1 byte; DataView 無 slice 而拋 TypeError; Uint16Array 之 length 為元素數而切出之位元組為 2 倍 → Payload Too Large; 非 ASCII 字串以字元數切、以 UTF-8 送 → 同; null 等不支援之輸入則以看不出原因之 TypeError 失敗且 0 則事件。

    位元組語意與 fetch / axios 之 BodyInit 一致: ArrayBuffer 與所有 ArrayBufferView 取其位元組, 字串取其 UTF-8 位元組。 判定 Blob 以 instanceof 而非 wsemi 之 isblob: File 之 Object.prototype.toString 為 [object File], isblob 對其回 false(nodejs 實測)。 非 Buffer 之視圖以 new Uint8Array(buffer, byteOffset, byteLength) 表達, 其後切片須用會複製之 slice(使 axios 送出之 data.buffer 恰為該片; axios 對非 Buffer 之視圖送 data.buffer, 以 subarray 切出之視圖會連同底層其餘位元組一併送出)。

Source:
Parameters:
Name Type Description
v *

輸入 upload 之輸入

Returns:

回傳正規化後之輸入; 不支援者回 null

Type
Blob | Uint8Array | null

readDownloadFields(r, keys) → {Object}

Description:
  • 讀取應用端download事件回傳物件之欄位

    逐欄各自以attempt收斂, 使某欄之getter拋錯不影響不需該欄之路由: /dwgfn 只需 streamRead 與 filename, 若一併讀 fileSize 而其getter拋錯, 該路由會由成功變失敗(行為改變)

    失敗時一併回傳已成功讀到之欄位(fields), 使呼叫端能清理已取得之資源

    why: streamRead 一律排在 keys 之首, 故凡後續欄位之 getter 拋錯者, 該串流已經在套件手上。 原本只回 { ok:false, field, cause } 而不回 fields, 路由層遂無從清理 —— 應用端交出之串流(常為 fs.createReadStream) 就此失去引用且未被 destroy, 其 fd 持續開啟。實測(tmp/probe_r9_rest.mjs 第1節): 四欄之中後三欄拋錯時 串流皆為 destroyed=false。三條下載路由原有之註解「讀取失敗時尚未取得串流引用, 無從清理」僅對「首欄即拋錯」成立

Source:
Parameters:
Name Type Description
r Object

輸入應用端download事件所resolve之物件

keys Array

輸入本次需要之欄位名稱陣列

Returns:

回傳 { ok: true, fields } 或 { ok: false, field, cause, fields };後者之 fields 為失敗前已讀到者

Type
Object

responseU8aStream(res, u8a, optopt) → {Object}

Description:
  • 以octet-stream回應二進位封包, 並附上本套件之回應協定標頭

    本套件之回應協定有兩層: 本體(obj2u8arr之success/error)與標頭(Return-Type、Return-Msg、Return-Retryable、Content-Disposition)。 下載路徑(client之downloadStream)只讀標頭不解析本體, 故標頭是協定的一部分而非附屬資訊

    Return-Msg之值域檢核(見isValidHeaderValue): 錯誤訊息可能回顯請求端可控之字串(如 invalid mode[] in payload), 該值若含CR/LF或超出latin1範圍(如中文), node於寫標頭時拋錯而hapi只能回裸HTTP 500 —— 非本套件之錯誤封包, 前端無法解析且伺服器不發error事件。 故不合法者不送該標頭而非讓整個回應失敗: 完整訊息仍在本體之error封包內, 前端解析本體即可取得; 下載路徑雖只讀標頭, 但其錯誤訊息皆為套件自產之固定字串, 不受影響

Source:
Parameters:
Name Type Attributes Default Description
res Object

輸入hapi之response toolkit

u8a Uint8Array

輸入待回應之二進位封包

opt Object <optional>
{}

輸入設定物件, 預設{}

Properties
Name Type Attributes Default Description
returnType String <optional>
''

輸入Return-Type標頭值, 為'success'或'error', 空字串代表不送

returnMsg String <optional>
''

輸入Return-Msg標頭值, 空字串或非法標頭值代表不送

Returns:

回傳hapi之response物件

Type
Object

responseU8aStreamWithError(res, msg, optopt) → {Object}

Description:
  • 以本套件之錯誤封包回應

    本函數為所有失敗之最終出口: 其自身之序列化不再以嚴格模式取狀態, 因為輸入之msg恆為套件自產字串 (全部呼叫點皆為字面或模板字串), 且此處若再失敗亦無處可退(帳本R3之刻意不套站點)

    why 與 responseU8aStream 並存而非合一: 前者之本體由呼叫端算好(可能是成功結果), 後者之本體由本函數依 msg 產生 —— 輸入不同, 合一會多一個「這次是不是錯誤」的旗標參數

Source:
Parameters:
Name Type Attributes Default Description
res Object

輸入hapi之response toolkit

msg String

輸入錯誤訊息字串

opt Object <optional>
{}

輸入設定物件, 預設{}

Properties
Name Type Attributes Default Description
retryable Boolean <optional>
true

輸入是否可重試布林值, 預設true。 給false者限「可證明不需重試」之錯誤: 結果僅由client自行建構之請求內容(mode、fileHash、chunkTotal、chunkIndex、packageId、fileId)決定, 重送同一請求必得同一結果; 凡涉及權限(permission denied)、應用端reject、應用端回傳形狀不合、磁碟、網路者皆為狀態不穩, 不得標示, 依重試原則由前端照常重試。 標示同時置於封包(供execute/upload/dwgfn之本體解析)與標頭Return-Retryable(供download之串流路徑, 該路徑只讀標頭不解析本體)

Returns:

回傳hapi之response物件

Type
Object

retryDelay(n) → {Integer}

Description:
  • 取第n次重試前之等待毫秒

    自baseDelay起以ratio指數成長, 第nToPeak次達到maxDelay, 其後維持在maxDelay不再成長

    why 須封頂: 原實作把成長率之校準常數(10)與迴圈上限(呼叫端可設之retry)當成同一件事, 兩者實際無關聯, 故retry大於10時延遲無界 —— 實測第20次單次等待16小時、第24次6.7天; 第27次起更超過32位元帶號整數(2147483647), setTimeout溢位而以1ms觸發, 退避反倒塌陷成熱迴圈打伺服器 (實測setTimeout(2**31)印TimeoutOverflowWarning且4ms即觸發)

    封頂只限制單次等待, 不減少重試次數(次數另由WConverhpClient之maxRetryTimes約束), 與本套件之重試原則相容

Source:
Example
console.log(retryDelay(1), retryDelay(2), retryDelay(10))
// => 1000 1781 180000

console.log(retryDelay(20), retryDelay(100))
// => 180000 180000
Parameters:
Name Type Description
n Integer

輸入第幾次重試正整數, 自1起算

Returns:

回傳等待毫秒整數, 必不超過maxDelay

Type
Integer

sanitizeFilename(name, defopt) → {String}

Description:
  • 將來源不可信之檔名(如伺服器回傳之Content-Disposition)整理為可安全落地之單一檔名

    依瀏覽器對下載檔名之處理慣例:

    • 只取最末路徑段(同時處理 / 與 ), 去除 . 與 .. 段
    • 去除控制字元(含NUL)與各平台非法字元 < > : " | ? *; 其中冒號亦擋掉Windows磁碟機相對路徑(如 C:evil, 無分隔符卻能逸出當前資料夾)
    • 去除結尾之點與空白(Windows會忽略, 避免判定與實際落點不一致)
    • Windows保留裝置名(CON/PRN/AUX/NUL/COM1-9/LPT1-9, 不分大小寫且不論副檔名)前置底線 不做路徑包含判定, 該判定需path模組, 由nodejs端另行以isPathInside處理
Source:
Parameters:
Name Type Attributes Default Description
name String

輸入不可信之檔名

def String <optional>
'unknown'

輸入整理後為空時之替代檔名, 預設'unknown'

Returns:

回傳整理後之檔名

Type
String

validDownloadField(name, v) → {Object}

Description:
  • 判定並正規化應用端 download 事件回傳之單一欄位(filename、fileSize、fileType)

    why 判定本身須在 attempt 內: 欄位之擷取已由 readDownloadFields 收進 attempt, 而擷取到的值之型別判定原本在其外 —— wsemi 之 isestr / isValidFileSize 皆經 Object.prototype.toString.call, 會觸發值之 Symbol.toStringTag getter; 該 getter 拋錯時三條下載路由皆回裸 HTTP 500 且 0 則事件, /dw 之應用端串流且未被銷毀(實測第十輪 A5, 7 格)。與帳本 R1 之 #32 同型。

    why 須正規化為字串基本型: 通過 isestr 之物件(帶 Symbol.toStringTag='String')其 toString 可拋錯, 原本 /dwgfn 有 attempt + cstr 而 /dw、/dwgf 無 —— 同一個 filename 於 /dwgfn 回錯誤 + 1 則事件, 於另兩路由靜默送出空檔名(實測第十輪 A6)。收斂於此, 三路由同一判定。

    why filename 須取代孤立代理對: /dw 以 wsemi 之 str2b64 編碼, 其寬鬆模式吞掉 URIError 而回空字串, 檔名整個消失; /dwgf 之 encodeRfc5987 則以 U+FFFD 取代(實測第十輪 A7)。正規化於此, 兩種編碼器得到同一個檔名。

Source:
Parameters:
Name Type Description
name String

輸入欄位名稱, 為 'filename'、'fileSize' 或 'fileType'

v *

輸入應用端交出之欄位值

Returns:

回傳 { ok, value, cause }: ok 為是否可用; value 為正規化後之值(fileSize 為數值, 其餘為字串基本型); cause 為判定時拋出之原因(未拋錯者為空字串)

Type
Object