sample.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. --[[
  2. willSendRequestToBbsCgi 関数
  3. 5ch/bbspinkのbbs.cgiにPOSTリクエストが送られる直前にproxy2chから呼び出される
  4. 関数です。引数requestと同じ構造のtable型の変数を返すことで、proxy2chがbbs.cgi
  5. に対して送るリクエストヘッダ/ボディを自由に改変 (ヘッダの追加/削除含む) する
  6. ことができます。--bbscgi-headerオプションとは異なり、板ごとに動作を変更する
  7. ことも可能です。スクリプトは毎回読み込まれるため、proxy2chを起動し直さなくて
  8. も変更は反映されます。
  9. * 引数 request
  10. 次のような構造を持つtable型の変数です。送信される予定のリクエストヘッダ/
  11. ボディが格納されています。
  12. {
  13. "body" = "リクエストボディ",
  14. "headers" = {
  15. "リクエストヘッダ1" = "値1",
  16. "リクエストヘッダ2" = "値2",
  17. :
  18. }
  19. }
  20. この引数をそのまま返せば「何もしない」関数になります。
  21. request.headersは純粋なtableではなく、tableライクなuserdataです。[]を使った
  22. 値のゲット/セットやpairsを使ったキー/値のイテレーションは可能であるため、
  23. 文字列をキーとした連想配列としてアクセスする限りtable型でないことを意識する
  24. 必要はありません。キー名の大文字小文字を区別しない点がtableとは異なります。
  25. * 引数 host
  26. リクエスト先のホスト名が格納されているstring型の変数です。
  27. * 引数 board
  28. リクエストボディのbbsフィールドの値に相当するstring型の変数です。
  29. * 引数 thread
  30. リクエストボディのkeyフィールドの値に相当するstring型の変数です。
  31. * 戻り値
  32. 引数requestと同じ構造を持つtable型の値を返してください。requestそのものを
  33. 改変してもいいですし、新しいtableを1から作ってもいいです。
  34. リクエストボディを改変する場合、application/x-www-form-urlencodedの形式を
  35. 満たしている必要があります。
  36. リクエストヘッダを改変する際、特定のリクエストヘッダを送信したくない場合は
  37. 値をnilに設定して削除するか、あるいは空の文字列""を代入します。
  38. 引数のrequest.headersはtableではないと書きましたが、ここで返すheadersの型は
  39. 純粋なtableでも良いです。request.headersと同じ型のuserdataを1から作りたい
  40. 場合はHttpHeaders.new()という関数が使えます。
  41. このスクリプトの中からはproxy2chというグローバル変数を参照可能であり、いくつかの
  42. ユーティリティ関数や変数が proxy2ch.(関数名/変数名) の形でアクセスできます。
  43. proxy2ch.hmacSHA256(key, message) 関数
  44. HMAC-SHA256に基づいてメッセージダイジェストを算出します。
  45. * 引数 key
  46. HMAC算出用の鍵をstring型で渡します。
  47. * 引数 message
  48. ダイジェストを算出したいメッセージをstring型で渡します。
  49. * 戻り値
  50. 算出したメッセージダイジェストの値をstring型で返します。
  51. hex表記したもの、つまり0-9,a-fで構成される長さ64の文字列になります。
  52. proxy2ch.decodeURIComponent(input [, decodePlus]) 関数
  53. URLエンコードされた文字列をデコードします。
  54. * 引数 input
  55. デコードしたい文字列をstring型で渡します。
  56. * 引数 decodePlus (省略可能)
  57. input中に現れる"+"を" "に置き換えるかどうかをboolean型で渡します。
  58. application/x-www-form-urlencoded形式を想定しているため、この引数を省略した
  59. 場合はtrueが渡されたものとして動作します。
  60. * 戻り値
  61. デコード済み文字列をstring型で返します。
  62. proxy2ch.encodeURIComponent(input [, spaceAsPlus]) 関数
  63. 文字列をURLエンコードします。
  64. * 引数 input
  65. エンコードしたい文字列をstring型で渡します。
  66. * 引数 spaceAsPlus (省略可能)
  67. input中に現れる" "を"%20"ではなく"+"に置き換えるかどうかをboolean型で渡しま
  68. す。application/x-www-form-urlencoded形式を想定しているため、この引数を省略
  69. した場合はtrueが渡されたものとして動作します。
  70. * 戻り値
  71. エンコード済み文字列をstring型で返します。
  72. proxy2ch.convertShiftJISToUTF8(input) 関数
  73. Shift JISでエンコードされた文字列をUTF-8に変換します。
  74. * 引数 input
  75. UTF-8に変換したい文字列をstring型で渡します。
  76. * 戻り値
  77. UTF-8に変換された文字列をstring型で返します。
  78. 変換に失敗した場合はnilを返します。
  79. proxy2ch.isKeyExpired(key) 関数
  80. 指定したMonaKeyが有効期限切れ扱いされたことがあるかどうかをチェックします。
  81. 通常は使う必要はないですが、MonaKeyの管理をproxy2ch.monaKeyに丸投げせずに
  82. スクリプト側で行いたい場合に役に立つ可能性があります。
  83. * 引数 key
  84. チェックしたいMonaKeyをstring型で渡します。
  85. * 戻り値
  86. 有効期限切れ扱いされたことがあるかどうかをboolean型で返します。
  87. proxy2ch.isValidAsUTF8(input) 関数
  88. 指定した文字列がUTF-8エンコーディングで解釈可能かどうかを判定します。
  89. * 引数 input
  90. 判定したい文字列をstring型で渡します。
  91. * 戻り値
  92. UTF-8エンコーディングで解釈可能かどうかをboolean型で返します。
  93. proxy2ch.getMonaKey(userAgent) 関数
  94. 指定したUser-Agentに対して払い出されたMonaKey(仮称)の値を取得します。
  95. * 引数 userAgent
  96. MonaKeyを取得したいUser-Agentをstring型で渡します。
  97. * 戻り値
  98. 指定したUser-Agentに対して払い出されたMonaKeyの値をstring型で返します。
  99. まだ払い出しが行われていない場合は "00000000-0000-0000-0000-000000000000" を
  100. 返します。
  101. proxy2ch.monaKey string型変数
  102. 書き込み時に払い出されたMonaKey(仮称)の値を格納している変数です。
  103. 払い出し前の初期値は便宜上 "00000000-0000-0000-0000-000000000000" です。
  104. proxy2ch.port 数値型変数
  105. proxy2chがクライアントの接続を待ち受けているポート番号を格納している変数です。
  106. 複数のポートで待ち受けている、あるいは複数のproxy2chを同時に動かしている時に
  107. 1つのスクリプトでポートによって動作を変えたい時に参照できます。
  108. ]]
  109. function createTableFromBody(str)
  110. local t = {}
  111. for s in string.gmatch(str, "([^&]+)") do
  112. local idx = string.find(s, "=")
  113. local name = string.sub(s, 1, idx-1)
  114. local value = string.sub(s, idx+1)
  115. t[name] = value
  116. end
  117. return t
  118. end
  119. function createBodyFromTableWithOrder(tBody, order)
  120. order = order or {}
  121. local ret = ""
  122. for i, name in ipairs(order) do
  123. if tBody[name] ~= nil then
  124. if ret ~= "" then
  125. ret = ret .. "&"
  126. end
  127. ret = ret .. name .. "=" .. tBody[name]
  128. tBody[name] = nil
  129. end
  130. end
  131. for name, value in pairs(tBody) do
  132. if ret ~= "" then
  133. ret = ret .. "&"
  134. end
  135. ret = ret .. name .. "=" .. value
  136. end
  137. return ret
  138. end
  139. function generatePostSignatureFromTable(tBody, userAgent, hmacKey)
  140. local bbs = tBody["bbs"] or ""
  141. local key = tBody["key"] or ""
  142. local time = tBody["time"] or ""
  143. local from = tBody["FROM"] or ""
  144. local mail = tBody["mail"] or ""
  145. local message = tBody["MESSAGE"] or ""
  146. local subject = tBody["subject"] or ""
  147. local userAgent = userAgent or ""
  148. local monaKey = proxy2ch.getMonaKey(userAgent)
  149. local unused = ""
  150. local nonce = string.format("%d.%03d", os.time(), math.random(0, 999))
  151. local data = table.concat({bbs, key, time, from, mail, message, subject, userAgent, monaKey, unused, nonce}, "<>")
  152. return proxy2ch.hmacSHA256(hmacKey, data), nonce, monaKey
  153. end
  154. function willSendRequestToBbsCgi(request, host, board, thread)
  155. --リクエストヘッダのUser-Agentの値を変更する
  156. request.headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0"
  157. --リクエストヘッダのRefererの値を変更する
  158. request.headers["Referer"] = "https://" .. host .. "/test/read.cgi/" .. board .. "/" .. thread .. "/l50"
  159. --リクエストボディの末尾に"&oekaki_thread1="を追加する
  160. request.body = request.body .. "&oekaki_thread1="
  161. --リクエストボディをtableに変換する
  162. --"name1=value1&name=value2"形式のstringが{"name1" = "value1", "name2" = "value2"}形式のtableに変換される
  163. local tBody = createTableFromBody(request.body)
  164. --既にリクエストボディがUTF-8になっていると思われる場合はUTF-8への変換を無効にする
  165. --submitフィールドの値がUTF-8で解釈可能かどうかで判定する
  166. local shouldConvertToUTF8 = not proxy2ch.isValidAsUTF8(proxy2ch.decodeURIComponent(tBody["submit"]))
  167. --リクエストボディの各フィールドのURLエンコードを解除し、必要ならUTF-8に変換する
  168. --リクエストボディのUTF-8化が必須かどうかはUser-Agentにより異なる
  169. for name, value in pairs(tBody) do
  170. local decoded = proxy2ch.decodeURIComponent(value)
  171. tBody[name] = shouldConvertToUTF8 and proxy2ch.convertShiftJISToUTF8(decoded) or decoded
  172. end
  173. --リクエストボディとUser-Agentからリクエストのsignatureを生成する
  174. --signatureの生成元となるリクエストボディはURLエンコードを解除しておく必要がある
  175. local postSig, postNonce, monaKey = generatePostSignatureFromTable(tBody, request.headers["User-Agent"], "this is an HMAC key")
  176. --リクエストボディの各フィールドを再度URLエンコードする
  177. for name, value in pairs(tBody) do
  178. tBody[name] = proxy2ch.encodeURIComponent(value)
  179. end
  180. --tableからフィールドの順序を指定して新しいリクエストボディを生成する
  181. request.body = createBodyFromTableWithOrder(tBody, {"FROM", "mail", "MESSAGE", "bbs", "key", "time", "submit"})
  182. --Content-TypeヘッダのcharsetにUTF-8を指定しておく
  183. request.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
  184. --hostがbbspink.comでなければ新しい書き込み仕様に必要なヘッダを設定
  185. if string.sub(host, -#".bbspink.com") ~= ".bbspink.com" then
  186. request.headers["X-PostSig"] = postSig
  187. request.headers["X-APIKey"] = "this is an API key"
  188. request.headers["X-PostNonce"] = postNonce
  189. request.headers["X-MonaKey"] = monaKey
  190. end
  191. return request
  192. end