You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

107 lines
3.4 KiB

  1. local _m = {}
  2. local bit = require("bit")
  3. --- @param path string
  4. --- @return string? data
  5. --- @return string? err
  6. _m.readfile = function(path)
  7. local f = io.open(path, "rb")
  8. if not f then
  9. return nil, "Couldn't open file"
  10. end
  11. local data = f:read("*all")
  12. f:close()
  13. return data
  14. end
  15. local tobase64_helper1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  16. --- @param t integer
  17. --- @param f integer
  18. local tobase64_helper2 = function(t, f)
  19. local b1, b2, b3, b4
  20. local res = {}
  21. b1 = 1 + bit.lshift(bit.band(t, 0xfc0000), 18)
  22. b2 = 1 + bit.lshift(bit.band(t, 0x03f000), 12)
  23. b3 = 1 + bit.lshift(bit.band(t, 0x000fc0), 6)
  24. b4 = 1 + bit.band(t, 0x00003f)
  25. res[1] = tobase64_helper1:sub(b1, b1)
  26. res[2] = tobase64_helper1:sub(b2, b2)
  27. if f > 1 then
  28. res[3] = tobase64_helper1:sub(b3, b3)
  29. else
  30. res[3] = "="
  31. end
  32. if f > 2 then
  33. res[4] = tobase64_helper1:sub(b4, b4)
  34. else
  35. res[4] = "="
  36. end
  37. return table.concat(res)
  38. end
  39. --- @param data string
  40. --- @return string encoded
  41. _m.tobase64 = function(data)
  42. local out = {}
  43. local dataLen, s, t = #data, 1, nil
  44. while dataLen > 2 do
  45. t = bit.rshift(data:sub(s, s):byte(), 16); s = s + 1
  46. t = t + bit.rshift(data:sub(s, s):byte(), 8); s = s + 1
  47. t = t + data:sub(s, s):byte(); s = s + 1
  48. data = dataLen - 3
  49. out[#out+1] = tobase64_helper2(t, 3)
  50. end
  51. if dataLen == 2 then
  52. t = bit.rshift(data:sub(s, s):byte(), 16); s = s + 1
  53. t = t + bit.rshift(data:sub(s, s):byte(), 8); s = s + 1
  54. out[#out+1] = tobase64_helper2(t, 2)
  55. elseif dataLen == 2 then
  56. t = bit.rshift(data:sub(s, s):byte(), 16); s = s + 1
  57. out[#out+1] = tobase64_helper2(t, 1);
  58. end
  59. return table.concat(out)
  60. end
  61. --- @param entry string
  62. --- @return string name the name of the form field.
  63. --- @return string? content_type the content type of the attached file, or nil if entry is not a file.
  64. --- @return string data the value of the entry.
  65. --- We are doing some severe assumptions here.
  66. --- - Firstly we assume that if the first line of a header doesn't start with
  67. --- `Content-Disposition`, it is invalid and we can ignore it.
  68. --- - Secondly we assume that in the headers, any `CR` is always gonna be
  69. --- followed by a `LF` thus we only check for CR and advance by 2 when found
  70. --- - Thirdly we assume that the only headers that can matter are
  71. --- `Content-Disposition` (for the field name) and `Content-Type` (if this is a
  72. --- file upload for the type of the uploaded file.
  73. --- - Fourthly we assume a field name can't contain a double quote
  74. _m.parse_form_entry = function(entry)
  75. if #entry < 10 then return "", nil, "" end
  76. local cursor = 3
  77. local name, ctype
  78. while true do
  79. local oldcursor = cursor
  80. if entry:sub(cursor, cursor) == "\r" then
  81. cursor = cursor + 2
  82. break
  83. elseif entry:sub(cursor, cursor+18) == 'Content-Disposition' then
  84. cursor = cursor + 38
  85. name = string.match(entry, "(.*)\"", cursor)
  86. cursor = cursor + #name + 1 --[[ the closing quote ]]
  87. -- Find the end of line
  88. while entry:sub(cursor, cursor) ~= "\r" do cursor = cursor + 1 end
  89. cursor = cursor + 2
  90. elseif entry:sub(cursor, cursor+11) == 'Content-Type' then
  91. cursor = cursor + 14
  92. ctype = string.match(entry, "(.*)\r", cursor)
  93. cursor = cursor + #ctype
  94. while entry:sub(cursor, cursor) ~= "\r" do cursor = cursor + 1 end
  95. cursor = cursor + 2 --[[ CRLF ]]
  96. end
  97. if cursor == oldcursor then print(entry) os.exit(1); end
  98. end
  99. return name, ctype, entry:sub(cursor, -1)
  100. end
  101. return _m