dobrograd-13-06-2022/garrysmod/addons/util-dlib/lua/dlib/util/http.lua
Jonny_Bro (Nikita) e4d5311906 first commit
2023-11-16 15:01:19 +05:00

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