357 lines
9.2 KiB
Lua
357 lines
9.2 KiB
Lua
|
|
-- Copyright (C) 2017-2020 DBotThePony
|
|
|
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
-- of this software and associated documentation files (the "Software"), to deal
|
|
-- in the Software without restriction, including without limitation the rights
|
|
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
-- of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
-- subject to the following conditions:
|
|
|
|
-- The above copyright notice and this permission notice shall be included in all copies
|
|
-- or substantial portions of the Software.
|
|
|
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
-- DEALINGS IN THE SOFTWARE.
|
|
|
|
local http = http
|
|
local DLib = DLib
|
|
local HTTP = HTTP
|
|
local assert = assert
|
|
local type = type
|
|
local istable = istable
|
|
local isstring = isstring
|
|
local pairs = pairs
|
|
local table = table
|
|
local error = error
|
|
local string = string
|
|
|
|
local month_map = table.flipIntoHash({
|
|
'jan',
|
|
'feb',
|
|
'mar',
|
|
'apr',
|
|
'may',
|
|
'jun',
|
|
'jul',
|
|
'aug',
|
|
'sep',
|
|
'oct',
|
|
'nov',
|
|
'dec'
|
|
})
|
|
|
|
--[[
|
|
@doc
|
|
@fname http.ParseDate
|
|
@args string input
|
|
|
|
@desc
|
|
Parses date in next format `Tue, 26 Nov 2019 01:18:04 GMT`
|
|
@enddesc
|
|
|
|
@returns
|
|
number: timestamp
|
|
]]
|
|
function http.ParseDate(input)
|
|
local day_of_week, day, month_str, year, hour, minute, second = input:lower():match('([a-z]+),%s+([0-9]+)%s+([a-z]+)%s+([0-9]+)%s+([0-9]+):([0-9]+):([0-9]+)%s+gmt')
|
|
|
|
if not day_of_week then
|
|
day_of_week, day, month_str, year, hour, minute, second = input:lower():match('([a-z]+),%s+([0-9]+)-([a-z]+)-([0-9]+)%s+([0-9]+):([0-9]+):([0-9]+)%s+gmt')
|
|
end
|
|
|
|
return math.dateToTimestamp(year:tonumber(), month_map[month_str] or 1, day:tonumber(), hour:tonumber(), minute:tonumber(), second:tonumber())
|
|
end
|
|
|
|
local sequences = {}
|
|
|
|
do
|
|
for i = 1, 100 do
|
|
table.insert(sequences, CompileString([[
|
|
local math_random = math.random
|
|
local char = string.char
|
|
|
|
local function random()
|
|
local rand = math_random(1, 3)
|
|
|
|
if rand == 1 then
|
|
return math_random(48, 57)
|
|
elseif rand == 2 then
|
|
return math_random(65, 90)
|
|
else
|
|
return math_random(97, 122)
|
|
end
|
|
end
|
|
|
|
return function(len)
|
|
return '--' .. char(]] .. string.rep('random(), ', i + 10):sub(1, -3) .. [[)
|
|
end]], 'http.lua:random')())
|
|
end
|
|
end
|
|
|
|
local function figureoutSeparator(struct, len)
|
|
local separator = assert(sequences[len], 'Unable to construct random separator for multipart form! Uh-oh!')()
|
|
|
|
for _, value in pairs(struct) do
|
|
if istable(value) then
|
|
if assert(isstring(value.body) and value.body, 'Body is missing from table inside struct'):find(separator, 1, true) then
|
|
return figureoutSeparator(struct, len + 1)
|
|
end
|
|
else
|
|
if isnumber(value) then
|
|
value = tostring(value)
|
|
struct[_] = tostring(value)
|
|
end
|
|
|
|
if value:find(separator, 1, true) then
|
|
return figureoutSeparator(struct, len + 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
return separator
|
|
end
|
|
|
|
local function whut(name)
|
|
assert(isstring(name) and not name:find('"', 1, true) and not name:find('\n', 1, true), 'Names should be ONLY strings and they should not contain special characters')
|
|
return name
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname http.EncodeMultipart
|
|
@args table struct
|
|
|
|
@desc
|
|
Encodes input as `multipart/form-data`
|
|
`struct` is a table consist of string `key`s
|
|
and `value`s either of string or table, containing
|
|
`filename`, `mimetype` and `body` values
|
|
@enddesc
|
|
|
|
@returns
|
|
string: encoded string
|
|
string: header value for Content-Type
|
|
]]
|
|
function http.EncodeMultipart(struct)
|
|
assert(istable(struct), 'Structure must be a table!')
|
|
local separator = figureoutSeparator(struct, 1)
|
|
local build = {}
|
|
|
|
table.insert(build, separator)
|
|
|
|
for key, value in pairs(struct) do
|
|
if istable(value) then
|
|
local key = value.key or value.name or key
|
|
local str = 'Content-Disposition: form-data; name="' .. whut(key) .. '"'
|
|
|
|
if value.filename then
|
|
str = str .. '; filename="' .. whut(value.filename) .. '"'
|
|
end
|
|
|
|
table.insert(build, str)
|
|
|
|
if value.mimetype then
|
|
assert(not value.mimetype:find(' ', 1, true), 'mime type should not contain spaces')
|
|
table.insert(build, 'Content-Type: ' .. whut(value.mimetype) .. '\n')
|
|
elseif not value.nomime then
|
|
table.insert(build, 'Content-Type: application/octet-stream\n')
|
|
else
|
|
table.insert(build, '')
|
|
end
|
|
|
|
table.insert(build, tostring(value.body))
|
|
table.insert(build, separator)
|
|
else
|
|
table.insert(build, 'Content-Disposition: form-data; name="' .. whut(key) .. '"\n')
|
|
table.insert(build, tostring(value))
|
|
table.insert(build, separator)
|
|
end
|
|
end
|
|
|
|
return table.concat(build, '\n'), 'multipart/form-data; boundary=' .. separator:sub(3)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname http.EncodeQuery
|
|
@args table struct
|
|
|
|
@desc
|
|
struct is a table of `key -> value`
|
|
Both key and value should be strings
|
|
@enddesc
|
|
|
|
@returns
|
|
string: encoded string
|
|
]]
|
|
function http.EncodeQuery(params)
|
|
assert(istable(params), 'Params input must be a table!')
|
|
|
|
local build = {}
|
|
|
|
for key, value in pairs(params) do
|
|
table.insert(build, http.EncodeComponent(key) .. '=' .. http.EncodeComponent(value))
|
|
end
|
|
|
|
return table.concat(build, '&')
|
|
end
|
|
|
|
local function escapeUnicode(input)
|
|
if #input == 1 then return input end
|
|
|
|
local buf = ''
|
|
|
|
for char in input:gmatch('.') do
|
|
buf = buf .. '%' .. string.format('%X', string.byte(char))
|
|
end
|
|
|
|
return buf
|
|
end
|
|
|
|
local function escape(input)
|
|
return '%' .. string.format('%X', string.byte(input))
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname http.EncodeComponent
|
|
@args string input
|
|
|
|
@desc
|
|
See [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) for details
|
|
@enddesc
|
|
|
|
@returns
|
|
string: encoded string
|
|
]]
|
|
function http.EncodeComponent(component)
|
|
assert(isstring(component), 'Input must be a string!')
|
|
return component:gsub("[\x01-\x26]", escape)
|
|
:gsub("[\x2b-\x2f]", escape)
|
|
:gsub("[\x3a-\x40]", escape)
|
|
:gsub("[\x5b-\x5e]", escape)
|
|
:gsub("\x60", escape)
|
|
:gsub("[\x7b-\x7d]", escape)
|
|
:gsub(utf8.charpattern, escapeUnicode)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname http.Head
|
|
@args string url, function onsuccess, function onfailure, table headers = {}
|
|
|
|
@desc
|
|
Executes a HTTP HEAD request
|
|
onsuccess is a function with `table headers, number code` parameters
|
|
@enddesc
|
|
]]
|
|
function http.Head(url, onsuccess, onfailure, headers)
|
|
local request = {
|
|
url = url,
|
|
method = 'HEAD',
|
|
headers = headers or {},
|
|
}
|
|
|
|
if onsuccess then
|
|
function request.success(code, _, headers)
|
|
return onsuccess(headers, code)
|
|
end
|
|
end
|
|
|
|
if onfailure then
|
|
function request.failed(err)
|
|
return onfailure(err)
|
|
end
|
|
end
|
|
|
|
return HTTP(request)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname http.Put
|
|
@args string url, string body, function onsuccess, function onfailure, table headers = {}
|
|
|
|
@desc
|
|
Executes a HTTP PUT request
|
|
onsuccess is a function with `string body, number size, table headers, number code` parameters
|
|
@enddesc
|
|
]]
|
|
function http.Put(url, body, onsuccess, onfailure, headers)
|
|
local request = {
|
|
url = url,
|
|
body = body,
|
|
method = 'PUT',
|
|
headers = headers or {},
|
|
}
|
|
|
|
-- как же рубат заебал
|
|
-- говорит что не будет добавлять альсы (чтоб избавится от тупых имен функций) и править устаревший код и так далее
|
|
-- но такие костыли вставлять - вставляет
|
|
if headers['Content-Type'] or headers['content-type'] then
|
|
request.type = headers['Content-Type'] or headers['content-type']
|
|
headers['Content-Type'] = nil
|
|
headers['content-type'] = nil
|
|
end
|
|
|
|
if onsuccess then
|
|
function request.success(code, body, headers)
|
|
return onsuccess(body, #body, headers, code)
|
|
end
|
|
end
|
|
|
|
if onfailure then
|
|
function request.failed(err)
|
|
return onfailure(err)
|
|
end
|
|
end
|
|
|
|
return HTTP(request)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname http.PostBody
|
|
@args string url, string body, function onsuccess, function onfailure, table headers = {}
|
|
|
|
@desc
|
|
Executes a HTTP POST request
|
|
onsuccess is a function with `string body, number size, table headers, number code` parameters
|
|
@enddesc
|
|
]]
|
|
function http.PostBody(url, body, onsuccess, onfailure, headers)
|
|
local request = {
|
|
url = url,
|
|
body = body,
|
|
method = 'POST',
|
|
headers = headers or {},
|
|
}
|
|
|
|
-- как же рубат заебал
|
|
-- говорит что не будет добавлять альсы (чтоб избавится от тупых имен функций) и править устаревший код и так далее
|
|
-- но такие костыли вставлять - вставляет
|
|
if headers['Content-Type'] or headers['content-type'] then
|
|
request.type = headers['Content-Type'] or headers['content-type']
|
|
headers['Content-Type'] = nil
|
|
headers['content-type'] = nil
|
|
end
|
|
|
|
if onsuccess then
|
|
function request.success(code, body, headers)
|
|
return onsuccess(body, #body, headers, code)
|
|
end
|
|
end
|
|
|
|
if onfailure then
|
|
function request.failed(err)
|
|
return onfailure(err)
|
|
end
|
|
end
|
|
|
|
return HTTP(request)
|
|
end
|