├── README.md ├── bblibs ├── utils.lua ├── socket │ ├── mime.lua │ ├── mbox.lua │ ├── headers.lua │ ├── tp.lua │ ├── socket.lua │ ├── smtp.lua │ ├── ltn12.lua │ ├── ftp.lua │ ├── url.lua │ └── http.lua ├── pos.lua ├── UI.lua ├── md5.lua ├── JSON.lua └── StrUtilsAPI.lua ├── badboy.lua └── main.lua /README.md: -------------------------------------------------------------------------------- 1 | #Badboy是专为叉叉脚本引擎开发的工具类,代码全部以开源的方式提供。 2 | 3 | ##模块清单: 4 | 5 | ##JSON 6 | newArray 新建数组对象 7 | newObject 新建对象 8 | decode json字符串转成talbe对象 9 | encode table对象转换成压缩的json字符 10 | encode_pretty table对象转换成优雅的json字符 11 | 12 | ##StringUtils 13 | toCharTable 从字符串转字符数组 14 | fromCharTable 从字符数组转字符串 15 | toByteTable 从字符串转字节数组 16 | fromByteTale 从字节数组转字符串 17 | contains 是否包含子串 18 | startWith 是否以某个子串开头 19 | endsWith 是否以某个子中结束 20 | ...(等等,自己看去探索) 21 | 22 | ##UI 23 | RootView:create 构造UI根对象 24 | RootView:addView 添加子view 25 | RootView:removeView 删除子view 26 | RootView:removeViewByID 删除子view 27 | Page:create 构建Page控件 28 | Page:addView 添加子view 29 | Page:removeView 删除子view 30 | Page:removeViewByID 删除子view 31 | Image:create 构造Image控件 32 | Edit:create 构造Edit控件 33 | Lable:create 构造Lable控件 34 | ...(所有属性都可以直接通过对象访问) 35 | 36 | ##POS 37 | distanceBetween 计算距离 38 | click 单击 39 | touchMoveTo 精确滑动 40 | angleBetween 计算角度 41 | polarProjection 根据角度和距离找点 42 | isColorClick 根据颜色进行点击 43 | 44 | ##utils 45 | sysLogFmt 格式化字符串输出 46 | sysLogLst 任意内容输出 47 | tap 模拟一次点击 48 | swip 模拟一次滑动 49 | cmpColor 指定颜色对比 50 | 51 | ##luasocket 52 | 详细使用说明,main.lua中,API参考见http://w3.impa.br/~diego/software/luasocket/reference.html 53 | 54 | ##用法 55 | 1. 进入https://github.com/boyliang/lua_badboy,下载项目源码; 56 | 2. 把项目当中内容,复制到src目录下; 57 | 3. 在main.lua中加入你的脚本逻辑; 58 | 4. 使用xsp打包工具即完成out.xsp 59 | 60 | 61 | #Badboy库目前还是初级阶段,还有很多不完善,如果遇到不明白的地方,请及时反馈。如果你觉得有好的开源项目,也可以告诉我们,我们会评估是否集成到Badboy库。 62 | -------------------------------------------------------------------------------- /bblibs/utils.lua: -------------------------------------------------------------------------------- 1 | -- 作者boyliang 2 | -- 时间: 2015-11-26 3 | 4 | -- 格式化输出 5 | function sysLogFmt(fmt, ...) 6 | sysLog(string.format(fmt, ...)) 7 | end 8 | 9 | -- 任意输出 10 | function sysLogLst(...) 11 | local msg = '' 12 | for k,v in pairs({...}) do 13 | msg = string.format('%s %s ', msg, tostring(v)) 14 | end 15 | sysLog(msg) 16 | end 17 | 18 | -- 模拟一次点击 19 | function tap(x, y) 20 | math.randomseed(tostring(os.time()):reverse():sub(1, 6)) --设置随机数种子 21 | local index = math.random(1,5) 22 | x = x + math.random(-2,2) 23 | y = y + math.random(-2,2) 24 | touchDown(index,x, y) 25 | mSleep(math.random(60,80)) --某些特殊情况需要增大延迟才能模拟点击效果 26 | touchUp(index, x, y) 27 | mSleep(20) 28 | end 29 | 30 | -- 模拟滑动操作,从点(x1, y1)划到到(x2, y2) 31 | function swip(x1,y1,x2,y2) 32 | local step, x, y, index = 20, x1 , y1, math.random(1,5) 33 | touchDown(index, x, y) 34 | 35 | local function move(from, to) 36 | if from > to then 37 | do 38 | return -1 * step 39 | end 40 | else 41 | return step 42 | end 43 | end 44 | 45 | while (math.abs(x-x2) >= step) or (math.abs(y-y2) >= step) do 46 | if math.abs(x-x2) >= step then x = x + move(x1,x2) end 47 | if math.abs(y-y2) >= step then y = y + move(y1,y2) end 48 | touchMove(index, x, y) 49 | mSleep(20) 50 | end 51 | 52 | touchMove(index, x2, y2) 53 | mSleep(30) 54 | touchUp(index, x2, y2) 55 | end 56 | 57 | -- 多点颜色对比,格式为{{x,y,color},{x,y,color}...} 58 | function cmpColor(array, s, isKeepScreen) 59 | s = s or 90 60 | s = math.floor(0xff * (100 - s) * 0.01) 61 | isKeepScreen = isKeepScreen or false 62 | 63 | local lockscreen = function(flag) 64 | if isKeepScreen == true then 65 | keepScreen(flag) 66 | end 67 | end 68 | 69 | lockscreen(true) 70 | for i = 1, #array do 71 | local lr,lg,lb = getColorRGB(array[i][1], array[i][2]) 72 | local rgb = array[i][3] 73 | 74 | local r = math.floor(rgb/0x10000) 75 | local g = math.floor(rgb%0x10000/0x100) 76 | local b = math.floor(rgb%0x100) 77 | 78 | if math.abs(lr-r) > s or math.abs(lg-g) > s or math.abs(lb-b) > s then 79 | lockscreen(false) 80 | return false 81 | end 82 | end 83 | 84 | lockscreen(false) 85 | return true 86 | end -------------------------------------------------------------------------------- /badboy.lua: -------------------------------------------------------------------------------- 1 | local badboy = {} 2 | 3 | -- JSON模块 4 | function badboy.getJSON() 5 | if badboy.json == nil then 6 | local lo_json = {} 7 | local obj = require('bblibs.JSON') 8 | 9 | lo_json.decode = function(x) return obj:decode(x) end 10 | lo_json.encode = function(x) return obj:encode(x) end 11 | lo_json.encode_pretty = function(x) return obj:encode_pretty(x) end; 12 | 13 | badboy.json = lo_json 14 | end 15 | return badboy.json 16 | end 17 | 18 | -- StrignUtils模块 19 | function badboy.getStrUtils() 20 | if badboy.strutils == nil then 21 | local lo_strutils = require('bblibs.StrUtilsAPI') 22 | local lo_md5 = require('bblibs.md5') 23 | lo_strutils.md5 = function(str) return lo_md5.sumhexa(str) end 24 | 25 | --以下对string进行扩展,也可以直接使用badboy.strutils,功能强大10倍 26 | function string:tohex() return lo_strutils.toHex(self) end 27 | function string:fromhex() return lo_strutils.fromHex(self) end 28 | function string:sha1() return lo_strutils.SHA1(self) end 29 | function string:md5() return lo_strutils.md5(self) end 30 | function string:base64_encode() return lo_strutils.toBase64(self) end 31 | function string:base64_decode() return lo_strutils.fromBase64(self) end 32 | function string:encrypt(key) return lo_strutils.encrypt(self, key) end 33 | function string:decrypt(key) return lo_strutils.decrypt(self, key) end 34 | function string:split(divider) return lo_strutils.seperate(self, divider) end 35 | function string:trim() return lo_strutils.trim(self) end 36 | badboy.strutils = lo_strutils 37 | end 38 | 39 | return badboy.strutils 40 | end 41 | 42 | local function loadlib(flag, name) 43 | if badboy[flag] == nil then 44 | require(name) 45 | badboy[flag] = true 46 | end 47 | end 48 | 49 | local function getlib(flag, name) 50 | if badboy[flag] == nil then 51 | badboy[flag] = require(name) 52 | end 53 | 54 | return badboy[flag] 55 | end 56 | 57 | -- UI动态构建模块 58 | function badboy.loaduilib() loadlib('__ui', 'bblibs.UI') end 59 | 60 | -- Utils模块 61 | function badboy.loadutilslib() loadlib('__utils', 'bblibs.utils') end 62 | 63 | -- POS模块 64 | function badboy.loadpos() return getlib('pos', 'bblibs.pos') end 65 | 66 | -- luasocket 67 | function badboy.loadluasocket() 68 | 69 | flag_names = { 70 | ftp = 'bblibs.socket.ftp', 71 | http = 'bblibs.socket.http', 72 | smtp = 'bblibs.socket.smtp', 73 | socket = 'bblibs.socket.socket', 74 | ltn12 = 'bblibs.socket.ltn12' 75 | } 76 | 77 | for k, v in pairs(flag_names) do 78 | getlib(k, v) 79 | end 80 | 81 | end 82 | 83 | return badboy 84 | -------------------------------------------------------------------------------- /bblibs/socket/mime.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- MIME support for the Lua language. 3 | -- Author: Diego Nehab 4 | -- Conforming to RFCs 2045-2049 5 | ----------------------------------------------------------------------------- 6 | 7 | ----------------------------------------------------------------------------- 8 | -- Declare module and import dependencies 9 | ----------------------------------------------------------------------------- 10 | local base = _G 11 | local ltn12 = require("bblibs.socket.ltn12") 12 | local mime = require("mime.core") 13 | local io = require("io") 14 | local string = require("string") 15 | local _M = mime 16 | 17 | -- encode, decode and wrap algorithm tables 18 | local encodet, decodet, wrapt = {},{},{} 19 | 20 | _M.encodet = encodet 21 | _M.decodet = decodet 22 | _M.wrapt = wrapt 23 | 24 | -- creates a function that chooses a filter by name from a given table 25 | local function choose(table) 26 | return function(name, opt1, opt2) 27 | if base.type(name) ~= "string" then 28 | name, opt1, opt2 = "default", name, opt1 29 | end 30 | local f = table[name or "nil"] 31 | if not f then 32 | base.error("unknown key (" .. base.tostring(name) .. ")", 3) 33 | else return f(opt1, opt2) end 34 | end 35 | end 36 | 37 | -- define the encoding filters 38 | encodet['base64'] = function() 39 | return ltn12.filter.cycle(_M.b64, "") 40 | end 41 | 42 | encodet['quoted-printable'] = function(mode) 43 | return ltn12.filter.cycle(_M.qp, "", 44 | (mode == "binary") and "=0D=0A" or "\r\n") 45 | end 46 | 47 | -- define the decoding filters 48 | decodet['base64'] = function() 49 | return ltn12.filter.cycle(_M.unb64, "") 50 | end 51 | 52 | decodet['quoted-printable'] = function() 53 | return ltn12.filter.cycle(_M.unqp, "") 54 | end 55 | 56 | local function format(chunk) 57 | if chunk then 58 | if chunk == "" then return "''" 59 | else return string.len(chunk) end 60 | else return "nil" end 61 | end 62 | 63 | -- define the line-wrap filters 64 | wrapt['text'] = function(length) 65 | length = length or 76 66 | return ltn12.filter.cycle(_M.wrp, length, length) 67 | end 68 | wrapt['base64'] = wrapt['text'] 69 | wrapt['default'] = wrapt['text'] 70 | 71 | wrapt['quoted-printable'] = function() 72 | return ltn12.filter.cycle(_M.qpwrp, 76, 76) 73 | end 74 | 75 | -- function that choose the encoding, decoding or wrap algorithm 76 | _M.encode = choose(encodet) 77 | _M.decode = choose(decodet) 78 | _M.wrap = choose(wrapt) 79 | 80 | -- define the end-of-line normalization filter 81 | function _M.normalize(marker) 82 | return ltn12.filter.cycle(_M.eol, 0, marker) 83 | end 84 | 85 | -- high level stuffing filter 86 | function _M.stuff() 87 | return ltn12.filter.cycle(_M.dot, 2) 88 | end 89 | 90 | return _M -------------------------------------------------------------------------------- /bblibs/socket/mbox.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | if module then 4 | mbox = _M 5 | end 6 | 7 | function _M.split_message(message_s) 8 | local message = {} 9 | message_s = string.gsub(message_s, "\r\n", "\n") 10 | string.gsub(message_s, "^(.-\n)\n", function (h) message.headers = h end) 11 | string.gsub(message_s, "^.-\n\n(.*)", function (b) message.body = b end) 12 | if not message.body then 13 | string.gsub(message_s, "^\n(.*)", function (b) message.body = b end) 14 | end 15 | if not message.headers and not message.body then 16 | message.headers = message_s 17 | end 18 | return message.headers or "", message.body or "" 19 | end 20 | 21 | function _M.split_headers(headers_s) 22 | local headers = {} 23 | headers_s = string.gsub(headers_s, "\r\n", "\n") 24 | headers_s = string.gsub(headers_s, "\n[ ]+", " ") 25 | string.gsub("\n" .. headers_s, "\n([^\n]+)", function (h) table.insert(headers, h) end) 26 | return headers 27 | end 28 | 29 | function _M.parse_header(header_s) 30 | header_s = string.gsub(header_s, "\n[ ]+", " ") 31 | header_s = string.gsub(header_s, "\n+", "") 32 | local _, __, name, value = string.find(header_s, "([^%s:]-):%s*(.*)") 33 | return name, value 34 | end 35 | 36 | function _M.parse_headers(headers_s) 37 | local headers_t = _M.split_headers(headers_s) 38 | local headers = {} 39 | for i = 1, #headers_t do 40 | local name, value = _M.parse_header(headers_t[i]) 41 | if name then 42 | name = string.lower(name) 43 | if headers[name] then 44 | headers[name] = headers[name] .. ", " .. value 45 | else headers[name] = value end 46 | end 47 | end 48 | return headers 49 | end 50 | 51 | function _M.parse_from(from) 52 | local _, __, name, address = string.find(from, "^%s*(.-)%s*%<(.-)%>") 53 | if not address then 54 | _, __, address = string.find(from, "%s*(.+)%s*") 55 | end 56 | name = name or "" 57 | address = address or "" 58 | if name == "" then name = address end 59 | name = string.gsub(name, '"', "") 60 | return name, address 61 | end 62 | 63 | function _M.split_mbox(mbox_s) 64 | mbox = {} 65 | mbox_s = string.gsub(mbox_s, "\r\n", "\n") .."\n\nFrom \n" 66 | local nj, i, j = 1, 1, 1 67 | while 1 do 68 | i, nj = string.find(mbox_s, "\n\nFrom .-\n", j) 69 | if not i then break end 70 | local message = string.sub(mbox_s, j, i-1) 71 | table.insert(mbox, message) 72 | j = nj+1 73 | end 74 | return mbox 75 | end 76 | 77 | function _M.parse(mbox_s) 78 | local mbox = _M.split_mbox(mbox_s) 79 | for i = 1, #mbox do 80 | mbox[i] = _M.parse_message(mbox[i]) 81 | end 82 | return mbox 83 | end 84 | 85 | function _M.parse_message(message_s) 86 | local message = {} 87 | message.headers, message.body = _M.split_message(message_s) 88 | message.headers = _M.parse_headers(message.headers) 89 | return message 90 | end 91 | 92 | return _M 93 | -------------------------------------------------------------------------------- /bblibs/pos.lua: -------------------------------------------------------------------------------- 1 | local pos = {} 2 | 3 | pos.degree = function(color1, color2) 4 | local fl = math.floor 5 | local r,g,b = fl(color1/0x10000),fl(color1%0x10000/0x100),fl(color1%0x100) 6 | local r1,g1,b1 = fl(color2/0x10000),fl(color2%0x10000/0x100),fl(color2%0x100) 7 | 8 | return math.ceil(((1 - math.abs(r - r1) / 0xff) / 3 + (1 - math.abs(g - g1) / 0xff) / 3 + (1 - math.abs(b - b1) / 0xff) / 3) * 100) 9 | end 10 | 11 | --newpos对象 12 | function pos:new(x, y, color) 13 | local options = {} 14 | options.x = x or 0 15 | options.y = y or 0 16 | options.color = color or 0x000000 17 | setmetatable(options, self) 18 | self.__index = self 19 | return options 20 | end 21 | 22 | --距离 23 | function pos:distanceBetween(t) 24 | return math.sqrt(math.abs(self.x - t.x) * math.abs(self.x - t.x) + math.abs(self.y - t.y) * math.abs(self.y - t.y)) 25 | end 26 | 27 | --点击 28 | function pos:click(sleep, slepp1) 29 | math.randomseed(tostring(os.time()):reverse():sub(1, 6)) --设置随机数种子 30 | local index = math.random(1,5) 31 | local x = self.x + math.random(-2,2) 32 | local y = self.y + math.random(-2,2) 33 | touchDown(index, x, y) 34 | 35 | if sleep ~= nil then 36 | mSleep(sleep) 37 | else 38 | mSleep(math.random(60,80)) --某些特殊情况需要增大延迟才能模拟点击效果 39 | end 40 | 41 | touchUp(index, x, y) 42 | if sleep1 ~= nil then 43 | mSleep(sleep1) 44 | else 45 | mSleep(20) 46 | end 47 | end 48 | 49 | --滑动 50 | function pos:touchMoveTo(t, step, sleep1, sleep2) 51 | local step = step or 20 52 | local sleep1 = sleep1 or 500 53 | local sleep2 = sleep2 or 20 54 | local x, y, x2, y2, index = self.x, self.y, t.x , t.y, math.random(1,5) 55 | touchDown(index, x, y) 56 | 57 | local function move(from, to) 58 | if from > to then 59 | do 60 | return -1 * step 61 | end 62 | else 63 | return step 64 | end 65 | end 66 | 67 | 68 | while (math.abs(x-x2) >= step) or (math.abs(y-y2) >= step) do 69 | if math.abs(x-x2) >= step then 70 | x = x + move(x,x2) 71 | end 72 | if math.abs(y-y2) >= step then 73 | y = y + move(y,y2) 74 | end 75 | touchMove(index, x, y) 76 | mSleep(sleep2) 77 | end 78 | 79 | touchMove(index, x2, y2) 80 | mSleep(sleep1) 81 | touchUp(index, x2, y2) 82 | end 83 | 84 | --角度 85 | function pos:angleBetween(t) 86 | return math.asin(math.abs(self.x - t.x) / math.abs(self.y - t.y)) * 180 / math.pi 87 | end 88 | 89 | --根据角度和距离找点 90 | function pos:polarProjection(distance, angle) 91 | local addX = math.cos(angle * math.pi / 180) * distance 92 | local addY = math.sin(angle * math.pi / 180) * distance 93 | return pos:new(self.x + addX, self.y + addY) 94 | end 95 | 96 | 97 | --单点颜色点击 98 | function pos:isColorClick(s) 99 | local fl,abs = math.floor,math.abs 100 | local c = self.color 101 | s = s or 90 102 | s = fl(0xff*(100-s)*0.01) 103 | local r,g,b = fl(c/0x10000),fl(c%0x10000/0x100),fl(c%0x100) 104 | local rr,gg,bb = getColorRGB(self.x,self.y) 105 | if abs(r-rr)', 153 | '', 154 | '', 155 | } 156 | mesgt = { 157 | headers = { 158 | to = 'youmail@gmail.com', -- 收件人 159 | cc = '', -- 抄送 160 | subject = "This is Mail Title" 161 | }, 162 | body = "邮件内容" 163 | } 164 | 165 | r, e = smtp.send{ 166 | server = "smtp.126.com", --smtp服务器地址 167 | user = "youmail@126.com",--smtp验证用户名 168 | password = "******", --smtp验证密码 169 | from = from, 170 | rcpt = rcpt, 171 | source = smtp.message(mesgt) 172 | } 173 | 174 | if not r then 175 | dialog(e, 0) 176 | else 177 | dialog('发送成功!', 0) 178 | end 179 | 180 | -- 实现获取网络时间 181 | server_ip = { 182 | "132.163.4.101", 183 | "132.163.4.102", 184 | "132.163.4.103", 185 | "128.138.140.44", 186 | "192.43.244.18", 187 | "131.107.1.10", 188 | "66.243.43.21", 189 | "216.200.93.8", 190 | "208.184.49.9", 191 | "207.126.98.204", 192 | "207.200.81.113", 193 | "205.188.185.33" 194 | } 195 | 196 | local function nstol(str) 197 | assert(str and #str == 4) 198 | local t = {str:byte(1,-1)} 199 | local n = 0 200 | for k = 1, #t do 201 | n= n*256 + t[k] 202 | end 203 | return n 204 | end 205 | 206 | local function gettime(ip) 207 | local tcp = socket.tcp() 208 | tcp:settimeout(10) 209 | tcp:connect(ip, 37) 210 | success, time = pcall(nstol, tcp:receive(4)) 211 | tcp:close() 212 | return success and time or nil 213 | end 214 | 215 | local function nettime() 216 | for _, ip in pairs(server_ip) do 217 | time = gettime(ip) 218 | if time then 219 | return time 220 | end 221 | end 222 | end 223 | 224 | dialog(nettime(),0) 225 | 226 | -- 统计毫秒精度时间 227 | local function sleep(sec) 228 | socket.select(nil,nil,sec); 229 | end 230 | local t0 = socket.gettime() 231 | sleep(0.4); 232 | local t1 = socket.gettime() 233 | dialog(t1 - t0, 0) 234 | 235 | -- FTP 测试 236 | -- [ftp://][[:]@][:][/][type=a|i] 237 | -- The following constants in the namespace can be set to control the default behavior of the FTP module: 238 | -- PASSWORD: default anonymous password. 239 | -- PORT: default port used for the control connection; 240 | -- TIMEOUT: sets the timeout for all I/O operations; 241 | -- USER: default anonymous user; 242 | 243 | local ftp = bb.ftp 244 | 245 | -- Log as user "anonymous" on server "ftp.tecgraf.puc-rio.br", 246 | -- and get file "lua.tar.gz" from directory "pub/lua" as binary. 247 | f, e = ftp.get("ftp://ftp.tecgraf.puc-rio.br/pub/lua/lua.tar.gz;type=i") 248 | 249 | -- Log as user "fulano" on server "ftp.example.com", 250 | -- using password "silva", and store a file "README" with contents 251 | -- "wrong password, of course" 252 | f, e = ftp.put("ftp://fulano:silva@ftp.example.com/README", "wrong password, of course") 253 | 254 | -- Log as user "fulano" on server "ftp.example.com", 255 | -- using password "silva", and append to the remote file "LOG", sending the 256 | -- contents of the local file "LOCAL-LOG" 257 | f, e = ftp.put{ 258 | host = "ftp.example.com", 259 | user = "fulano", 260 | password = "silva", 261 | command = "appe", 262 | argument = "LOG", 263 | source = ltn12.source.file(io.open("LOCAL-LOG", "r")) 264 | } 265 | 266 | -------------------------------------------------------------------------------- /bblibs/socket/smtp.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- SMTP client support for the Lua language. 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | ----------------------------------------------------------------------------- 6 | 7 | ----------------------------------------------------------------------------- 8 | -- Declare module and import dependencies 9 | ----------------------------------------------------------------------------- 10 | local base = _G 11 | local coroutine = require("coroutine") 12 | local string = require("string") 13 | local math = require("math") 14 | local os = require("os") 15 | local socket = require("bblibs.socket.socket") 16 | local tp = require("bblibs.socket.tp") 17 | local ltn12 = require("bblibs.socket.ltn12") 18 | local headers = require("bblibs.socket.headers") 19 | local mime = require("bblibs.socket.mime") 20 | 21 | socket.smtp = {} 22 | local _M = socket.smtp 23 | 24 | ----------------------------------------------------------------------------- 25 | -- Program constants 26 | ----------------------------------------------------------------------------- 27 | -- timeout for connection 28 | _M.TIMEOUT = 60 29 | -- default server used to send e-mails 30 | _M.SERVER = "localhost" 31 | -- default port 32 | _M.PORT = 25 33 | -- domain used in HELO command and default sendmail 34 | -- If we are under a CGI, try to get from environment 35 | _M.DOMAIN = os.getenv("SERVER_NAME") or "localhost" 36 | -- default time zone (means we don't know) 37 | _M.ZONE = "-0000" 38 | 39 | --------------------------------------------------------------------------- 40 | -- Low level SMTP API 41 | ----------------------------------------------------------------------------- 42 | local metat = { __index = {} } 43 | 44 | function metat.__index:greet(domain) 45 | self.try(self.tp:check("2..")) 46 | self.try(self.tp:command("EHLO", domain or _M.DOMAIN)) 47 | return socket.skip(1, self.try(self.tp:check("2.."))) 48 | end 49 | 50 | function metat.__index:mail(from) 51 | self.try(self.tp:command("MAIL", "FROM:" .. from)) 52 | return self.try(self.tp:check("2..")) 53 | end 54 | 55 | function metat.__index:rcpt(to) 56 | self.try(self.tp:command("RCPT", "TO:" .. to)) 57 | return self.try(self.tp:check("2..")) 58 | end 59 | 60 | function metat.__index:data(src, step) 61 | self.try(self.tp:command("DATA")) 62 | self.try(self.tp:check("3..")) 63 | self.try(self.tp:source(src, step)) 64 | self.try(self.tp:send("\r\n.\r\n")) 65 | return self.try(self.tp:check("2..")) 66 | end 67 | 68 | function metat.__index:quit() 69 | self.try(self.tp:command("QUIT")) 70 | return self.try(self.tp:check("2..")) 71 | end 72 | 73 | function metat.__index:close() 74 | return self.tp:close() 75 | end 76 | 77 | function metat.__index:login(user, password) 78 | self.try(self.tp:command("AUTH", "LOGIN")) 79 | self.try(self.tp:check("3..")) 80 | self.try(self.tp:send(mime.b64(user) .. "\r\n")) 81 | self.try(self.tp:check("3..")) 82 | self.try(self.tp:send(mime.b64(password) .. "\r\n")) 83 | return self.try(self.tp:check("2..")) 84 | end 85 | 86 | function metat.__index:plain(user, password) 87 | local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) 88 | self.try(self.tp:command("AUTH", auth)) 89 | return self.try(self.tp:check("2..")) 90 | end 91 | 92 | function metat.__index:auth(user, password, ext) 93 | if not user or not password then return 1 end 94 | if string.find(ext, "AUTH[^\n]+LOGIN") then 95 | return self:login(user, password) 96 | elseif string.find(ext, "AUTH[^\n]+PLAIN") then 97 | return self:plain(user, password) 98 | else 99 | self.try(nil, "authentication not supported") 100 | end 101 | end 102 | 103 | -- send message or throw an exception 104 | function metat.__index:send(mailt) 105 | self:mail(mailt.from) 106 | if base.type(mailt.rcpt) == "table" then 107 | for i,v in base.ipairs(mailt.rcpt) do 108 | self:rcpt(v) 109 | end 110 | else 111 | self:rcpt(mailt.rcpt) 112 | end 113 | self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) 114 | end 115 | 116 | function _M.open(server, port, create) 117 | local tp = socket.try(tp.connect(server or _M.SERVER, port or _M.PORT, 118 | _M.TIMEOUT, create)) 119 | local s = base.setmetatable({tp = tp}, metat) 120 | -- make sure tp is closed if we get an exception 121 | s.try = socket.newtry(function() 122 | s:close() 123 | end) 124 | return s 125 | end 126 | 127 | -- convert headers to lowercase 128 | local function lower_headers(headers) 129 | local lower = {} 130 | for i,v in base.pairs(headers or lower) do 131 | lower[string.lower(i)] = v 132 | end 133 | return lower 134 | end 135 | 136 | --------------------------------------------------------------------------- 137 | -- Multipart message source 138 | ----------------------------------------------------------------------------- 139 | -- returns a hopefully unique mime boundary 140 | local seqno = 0 141 | local function newboundary() 142 | seqno = seqno + 1 143 | return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), 144 | math.random(0, 99999), seqno) 145 | end 146 | 147 | -- send_message forward declaration 148 | local send_message 149 | 150 | -- yield the headers all at once, it's faster 151 | local function send_headers(tosend) 152 | local canonic = headers.canonic 153 | local h = "\r\n" 154 | for f,v in base.pairs(tosend) do 155 | h = (canonic[f] or f) .. ': ' .. v .. "\r\n" .. h 156 | end 157 | coroutine.yield(h) 158 | end 159 | 160 | -- yield multipart message body from a multipart message table 161 | local function send_multipart(mesgt) 162 | -- make sure we have our boundary and send headers 163 | local bd = newboundary() 164 | local headers = lower_headers(mesgt.headers or {}) 165 | headers['content-type'] = headers['content-type'] or 'multipart/mixed' 166 | headers['content-type'] = headers['content-type'] .. 167 | '; boundary="' .. bd .. '"' 168 | send_headers(headers) 169 | -- send preamble 170 | if mesgt.body.preamble then 171 | coroutine.yield(mesgt.body.preamble) 172 | coroutine.yield("\r\n") 173 | end 174 | -- send each part separated by a boundary 175 | for i, m in base.ipairs(mesgt.body) do 176 | coroutine.yield("\r\n--" .. bd .. "\r\n") 177 | send_message(m) 178 | end 179 | -- send last boundary 180 | coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") 181 | -- send epilogue 182 | if mesgt.body.epilogue then 183 | coroutine.yield(mesgt.body.epilogue) 184 | coroutine.yield("\r\n") 185 | end 186 | end 187 | 188 | -- yield message body from a source 189 | local function send_source(mesgt) 190 | -- make sure we have a content-type 191 | local headers = lower_headers(mesgt.headers or {}) 192 | headers['content-type'] = headers['content-type'] or 193 | 'text/plain; charset="iso-8859-1"' 194 | send_headers(headers) 195 | -- send body from source 196 | while true do 197 | local chunk, err = mesgt.body() 198 | if err then coroutine.yield(nil, err) 199 | elseif chunk then coroutine.yield(chunk) 200 | else break end 201 | end 202 | end 203 | 204 | -- yield message body from a string 205 | local function send_string(mesgt) 206 | -- make sure we have a content-type 207 | local headers = lower_headers(mesgt.headers or {}) 208 | headers['content-type'] = headers['content-type'] or 209 | 'text/plain; charset="iso-8859-1"' 210 | send_headers(headers) 211 | -- send body from string 212 | coroutine.yield(mesgt.body) 213 | end 214 | 215 | -- message source 216 | function send_message(mesgt) 217 | if base.type(mesgt.body) == "table" then send_multipart(mesgt) 218 | elseif base.type(mesgt.body) == "function" then send_source(mesgt) 219 | else send_string(mesgt) end 220 | end 221 | 222 | -- set defaul headers 223 | local function adjust_headers(mesgt) 224 | local lower = lower_headers(mesgt.headers) 225 | lower["date"] = lower["date"] or 226 | os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or _M.ZONE) 227 | lower["x-mailer"] = lower["x-mailer"] or socket._VERSION 228 | -- this can't be overriden 229 | lower["mime-version"] = "1.0" 230 | return lower 231 | end 232 | 233 | function _M.message(mesgt) 234 | mesgt.headers = adjust_headers(mesgt) 235 | -- create and return message source 236 | local co = coroutine.create(function() send_message(mesgt) end) 237 | return function() 238 | local ret, a, b = coroutine.resume(co) 239 | if ret then return a, b 240 | else return nil, a end 241 | end 242 | end 243 | 244 | --------------------------------------------------------------------------- 245 | -- High level SMTP API 246 | ----------------------------------------------------------------------------- 247 | _M.send = socket.protect(function(mailt) 248 | local s = _M.open(mailt.server, mailt.port, mailt.create) 249 | local ext = s:greet(mailt.domain) 250 | s:auth(mailt.user, mailt.password, ext) 251 | s:send(mailt) 252 | s:quit() 253 | return s:close() 254 | end) 255 | 256 | return _M -------------------------------------------------------------------------------- /bblibs/socket/ltn12.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- LTN12 - Filters, sources, sinks and pumps. 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | ----------------------------------------------------------------------------- 6 | 7 | ----------------------------------------------------------------------------- 8 | -- Declare module 9 | ----------------------------------------------------------------------------- 10 | local string = require("string") 11 | local table = require("table") 12 | local base = _G 13 | local _M = {} 14 | if module then -- heuristic for exporting a global package table 15 | ltn12 = _M 16 | end 17 | local filter,source,sink,pump = {},{},{},{} 18 | 19 | _M.filter = filter 20 | _M.source = source 21 | _M.sink = sink 22 | _M.pump = pump 23 | 24 | -- 2048 seems to be better in windows... 25 | _M.BLOCKSIZE = 2048 26 | _M._VERSION = "LTN12 1.0.3" 27 | 28 | ----------------------------------------------------------------------------- 29 | -- Filter stuff 30 | ----------------------------------------------------------------------------- 31 | -- returns a high level filter that cycles a low-level filter 32 | function filter.cycle(low, ctx, extra) 33 | base.assert(low) 34 | return function(chunk) 35 | local ret 36 | ret, ctx = low(ctx, chunk, extra) 37 | return ret 38 | end 39 | end 40 | 41 | -- chains a bunch of filters together 42 | -- (thanks to Wim Couwenberg) 43 | function filter.chain(...) 44 | local arg = {...} 45 | local n = select('#',...) 46 | local top, index = 1, 1 47 | local retry = "" 48 | return function(chunk) 49 | retry = chunk and retry 50 | while true do 51 | if index == top then 52 | chunk = arg[index](chunk) 53 | if chunk == "" or top == n then return chunk 54 | elseif chunk then index = index + 1 55 | else 56 | top = top+1 57 | index = top 58 | end 59 | else 60 | chunk = arg[index](chunk or "") 61 | if chunk == "" then 62 | index = index - 1 63 | chunk = retry 64 | elseif chunk then 65 | if index == n then return chunk 66 | else index = index + 1 end 67 | else base.error("filter returned inappropriate nil") end 68 | end 69 | end 70 | end 71 | end 72 | 73 | ----------------------------------------------------------------------------- 74 | -- Source stuff 75 | ----------------------------------------------------------------------------- 76 | -- create an empty source 77 | local function empty() 78 | return nil 79 | end 80 | 81 | function source.empty() 82 | return empty 83 | end 84 | 85 | -- returns a source that just outputs an error 86 | function source.error(err) 87 | return function() 88 | return nil, err 89 | end 90 | end 91 | 92 | -- creates a file source 93 | function source.file(handle, io_err) 94 | if handle then 95 | return function() 96 | local chunk = handle:read(_M.BLOCKSIZE) 97 | if not chunk then handle:close() end 98 | return chunk 99 | end 100 | else return source.error(io_err or "unable to open file") end 101 | end 102 | 103 | -- turns a fancy source into a simple source 104 | function source.simplify(src) 105 | base.assert(src) 106 | return function() 107 | local chunk, err_or_new = src() 108 | src = err_or_new or src 109 | if not chunk then return nil, err_or_new 110 | else return chunk end 111 | end 112 | end 113 | 114 | -- creates string source 115 | function source.string(s) 116 | if s then 117 | local i = 1 118 | return function() 119 | local chunk = string.sub(s, i, i+_M.BLOCKSIZE-1) 120 | i = i + _M.BLOCKSIZE 121 | if chunk ~= "" then return chunk 122 | else return nil end 123 | end 124 | else return source.empty() end 125 | end 126 | 127 | -- creates rewindable source 128 | function source.rewind(src) 129 | base.assert(src) 130 | local t = {} 131 | return function(chunk) 132 | if not chunk then 133 | chunk = table.remove(t) 134 | if not chunk then return src() 135 | else return chunk end 136 | else 137 | table.insert(t, chunk) 138 | end 139 | end 140 | end 141 | 142 | -- chains a source with one or several filter(s) 143 | function source.chain(src, f, ...) 144 | if ... then f=filter.chain(f, ...) end 145 | base.assert(src and f) 146 | local last_in, last_out = "", "" 147 | local state = "feeding" 148 | local err 149 | return function() 150 | if not last_out then 151 | base.error('source is empty!', 2) 152 | end 153 | while true do 154 | if state == "feeding" then 155 | last_in, err = src() 156 | if err then return nil, err end 157 | last_out = f(last_in) 158 | if not last_out then 159 | if last_in then 160 | base.error('filter returned inappropriate nil') 161 | else 162 | return nil 163 | end 164 | elseif last_out ~= "" then 165 | state = "eating" 166 | if last_in then last_in = "" end 167 | return last_out 168 | end 169 | else 170 | last_out = f(last_in) 171 | if last_out == "" then 172 | if last_in == "" then 173 | state = "feeding" 174 | else 175 | base.error('filter returned ""') 176 | end 177 | elseif not last_out then 178 | if last_in then 179 | base.error('filter returned inappropriate nil') 180 | else 181 | return nil 182 | end 183 | else 184 | return last_out 185 | end 186 | end 187 | end 188 | end 189 | end 190 | 191 | -- creates a source that produces contents of several sources, one after the 192 | -- other, as if they were concatenated 193 | -- (thanks to Wim Couwenberg) 194 | function source.cat(...) 195 | local arg = {...} 196 | local src = table.remove(arg, 1) 197 | return function() 198 | while src do 199 | local chunk, err = src() 200 | if chunk then return chunk end 201 | if err then return nil, err end 202 | src = table.remove(arg, 1) 203 | end 204 | end 205 | end 206 | 207 | ----------------------------------------------------------------------------- 208 | -- Sink stuff 209 | ----------------------------------------------------------------------------- 210 | -- creates a sink that stores into a table 211 | function sink.table(t) 212 | t = t or {} 213 | local f = function(chunk, err) 214 | if chunk then table.insert(t, chunk) end 215 | return 1 216 | end 217 | return f, t 218 | end 219 | 220 | -- turns a fancy sink into a simple sink 221 | function sink.simplify(snk) 222 | base.assert(snk) 223 | return function(chunk, err) 224 | local ret, err_or_new = snk(chunk, err) 225 | if not ret then return nil, err_or_new end 226 | snk = err_or_new or snk 227 | return 1 228 | end 229 | end 230 | 231 | -- creates a file sink 232 | function sink.file(handle, io_err) 233 | if handle then 234 | return function(chunk, err) 235 | if not chunk then 236 | handle:close() 237 | return 1 238 | else return handle:write(chunk) end 239 | end 240 | else return sink.error(io_err or "unable to open file") end 241 | end 242 | 243 | -- creates a sink that discards data 244 | local function null() 245 | return 1 246 | end 247 | 248 | function sink.null() 249 | return null 250 | end 251 | 252 | -- creates a sink that just returns an error 253 | function sink.error(err) 254 | return function() 255 | return nil, err 256 | end 257 | end 258 | 259 | -- chains a sink with one or several filter(s) 260 | function sink.chain(f, snk, ...) 261 | if ... then 262 | local args = { f, snk, ... } 263 | snk = table.remove(args, #args) 264 | f = filter.chain(unpack(args)) 265 | end 266 | base.assert(f and snk) 267 | return function(chunk, err) 268 | if chunk ~= "" then 269 | local filtered = f(chunk) 270 | local done = chunk and "" 271 | while true do 272 | local ret, snkerr = snk(filtered, err) 273 | if not ret then return nil, snkerr end 274 | if filtered == done then return 1 end 275 | filtered = f(done) 276 | end 277 | else return 1 end 278 | end 279 | end 280 | 281 | ----------------------------------------------------------------------------- 282 | -- Pump stuff 283 | ----------------------------------------------------------------------------- 284 | -- pumps one chunk from the source to the sink 285 | function pump.step(src, snk) 286 | local chunk, src_err = src() 287 | local ret, snk_err = snk(chunk, src_err) 288 | if chunk and ret then return 1 289 | else return nil, src_err or snk_err end 290 | end 291 | 292 | -- pumps all data from a source to a sink, using a step function 293 | function pump.all(src, snk, step) 294 | base.assert(src and snk) 295 | step = step or pump.step 296 | while true do 297 | local ret, err = step(src, snk) 298 | if not ret then 299 | if err then return nil, err 300 | else return 1 end 301 | end 302 | end 303 | end 304 | 305 | return _M 306 | -------------------------------------------------------------------------------- /bblibs/socket/ftp.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- FTP support for the Lua language 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | ----------------------------------------------------------------------------- 6 | 7 | ----------------------------------------------------------------------------- 8 | -- Declare module and import dependencies 9 | ----------------------------------------------------------------------------- 10 | local base = _G 11 | local table = require("table") 12 | local string = require("string") 13 | local math = require("math") 14 | local socket = require("bblibs.socket.socket") 15 | local url = require("bblibs.socket.url") 16 | local tp = require("bblibs.socket.tp") 17 | local ltn12 = require("bblibs.socket.ltn12") 18 | socket.ftp = {} 19 | local _M = socket.ftp 20 | ----------------------------------------------------------------------------- 21 | -- Program constants 22 | ----------------------------------------------------------------------------- 23 | -- timeout in seconds before the program gives up on a connection 24 | _M.TIMEOUT = 60 25 | -- default port for ftp service 26 | _M.PORT = 21 27 | -- this is the default anonymous password. used when no password is 28 | -- provided in url. should be changed to your e-mail. 29 | _M.USER = "ftp" 30 | _M.PASSWORD = "anonymous@anonymous.org" 31 | 32 | ----------------------------------------------------------------------------- 33 | -- Low level FTP API 34 | ----------------------------------------------------------------------------- 35 | local metat = { __index = {} } 36 | 37 | function _M.open(server, port, create) 38 | local tp = socket.try(tp.connect(server, port or _M.PORT, _M.TIMEOUT, create)) 39 | local f = base.setmetatable({ tp = tp }, metat) 40 | -- make sure everything gets closed in an exception 41 | f.try = socket.newtry(function() f:close() end) 42 | return f 43 | end 44 | 45 | function metat.__index:portconnect() 46 | self.try(self.server:settimeout(_M.TIMEOUT)) 47 | self.data = self.try(self.server:accept()) 48 | self.try(self.data:settimeout(_M.TIMEOUT)) 49 | end 50 | 51 | function metat.__index:pasvconnect() 52 | self.data = self.try(socket.tcp()) 53 | self.try(self.data:settimeout(_M.TIMEOUT)) 54 | self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) 55 | end 56 | 57 | function metat.__index:login(user, password) 58 | self.try(self.tp:command("user", user or _M.USER)) 59 | local code, reply = self.try(self.tp:check{"2..", 331}) 60 | if code == 331 then 61 | self.try(self.tp:command("pass", password or _M.PASSWORD)) 62 | self.try(self.tp:check("2..")) 63 | end 64 | return 1 65 | end 66 | 67 | function metat.__index:pasv() 68 | self.try(self.tp:command("pasv")) 69 | local code, reply = self.try(self.tp:check("2..")) 70 | local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" 71 | local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) 72 | self.try(a and b and c and d and p1 and p2, reply) 73 | self.pasvt = { 74 | ip = string.format("%d.%d.%d.%d", a, b, c, d), 75 | port = p1*256 + p2 76 | } 77 | if self.server then 78 | self.server:close() 79 | self.server = nil 80 | end 81 | return self.pasvt.ip, self.pasvt.port 82 | end 83 | 84 | function metat.__index:port(ip, port) 85 | self.pasvt = nil 86 | if not ip then 87 | ip, port = self.try(self.tp:getcontrol():getsockname()) 88 | self.server = self.try(socket.bind(ip, 0)) 89 | ip, port = self.try(self.server:getsockname()) 90 | self.try(self.server:settimeout(_M.TIMEOUT)) 91 | end 92 | local pl = math.mod(port, 256) 93 | local ph = (port - pl)/256 94 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") 95 | self.try(self.tp:command("port", arg)) 96 | self.try(self.tp:check("2..")) 97 | return 1 98 | end 99 | 100 | function metat.__index:send(sendt) 101 | self.try(self.pasvt or self.server, "need port or pasv first") 102 | -- if there is a pasvt table, we already sent a PASV command 103 | -- we just get the data connection into self.data 104 | if self.pasvt then self:pasvconnect() end 105 | -- get the transfer argument and command 106 | local argument = sendt.argument or 107 | url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) 108 | if argument == "" then argument = nil end 109 | local command = sendt.command or "stor" 110 | -- send the transfer command and check the reply 111 | self.try(self.tp:command(command, argument)) 112 | local code, reply = self.try(self.tp:check{"2..", "1.."}) 113 | -- if there is not a a pasvt table, then there is a server 114 | -- and we already sent a PORT command 115 | if not self.pasvt then self:portconnect() end 116 | -- get the sink, source and step for the transfer 117 | local step = sendt.step or ltn12.pump.step 118 | local readt = {self.tp.c} 119 | local checkstep = function(src, snk) 120 | -- check status in control connection while downloading 121 | local readyt = socket.select(readt, nil, 0) 122 | if readyt[tp] then code = self.try(self.tp:check("2..")) end 123 | return step(src, snk) 124 | end 125 | local sink = socket.sink("close-when-done", self.data) 126 | -- transfer all data and check error 127 | self.try(ltn12.pump.all(sendt.source, sink, checkstep)) 128 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end 129 | -- done with data connection 130 | self.data:close() 131 | -- find out how many bytes were sent 132 | local sent = socket.skip(1, self.data:getstats()) 133 | self.data = nil 134 | return sent 135 | end 136 | 137 | function metat.__index:receive(recvt) 138 | self.try(self.pasvt or self.server, "need port or pasv first") 139 | if self.pasvt then self:pasvconnect() end 140 | local argument = recvt.argument or 141 | url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) 142 | if argument == "" then argument = nil end 143 | local command = recvt.command or "retr" 144 | self.try(self.tp:command(command, argument)) 145 | local code,reply = self.try(self.tp:check{"1..", "2.."}) 146 | if (code >= 200) and (code <= 299) then 147 | recvt.sink(reply) 148 | return 1 149 | end 150 | if not self.pasvt then self:portconnect() end 151 | local source = socket.source("until-closed", self.data) 152 | local step = recvt.step or ltn12.pump.step 153 | self.try(ltn12.pump.all(source, recvt.sink, step)) 154 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end 155 | self.data:close() 156 | self.data = nil 157 | return 1 158 | end 159 | 160 | function metat.__index:cwd(dir) 161 | self.try(self.tp:command("cwd", dir)) 162 | self.try(self.tp:check(250)) 163 | return 1 164 | end 165 | 166 | function metat.__index:type(type) 167 | self.try(self.tp:command("type", type)) 168 | self.try(self.tp:check(200)) 169 | return 1 170 | end 171 | 172 | function metat.__index:greet() 173 | local code = self.try(self.tp:check{"1..", "2.."}) 174 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end 175 | return 1 176 | end 177 | 178 | function metat.__index:quit() 179 | self.try(self.tp:command("quit")) 180 | self.try(self.tp:check("2..")) 181 | return 1 182 | end 183 | 184 | function metat.__index:close() 185 | if self.data then self.data:close() end 186 | if self.server then self.server:close() end 187 | return self.tp:close() 188 | end 189 | 190 | ----------------------------------------------------------------------------- 191 | -- High level FTP API 192 | ----------------------------------------------------------------------------- 193 | local function override(t) 194 | if t.url then 195 | local u = url.parse(t.url) 196 | for i,v in base.pairs(t) do 197 | u[i] = v 198 | end 199 | return u 200 | else return t end 201 | end 202 | 203 | local function tput(putt) 204 | putt = override(putt) 205 | socket.try(putt.host, "missing hostname") 206 | local f = _M.open(putt.host, putt.port, putt.create) 207 | f:greet() 208 | f:login(putt.user, putt.password) 209 | if putt.type then f:type(putt.type) end 210 | f:pasv() 211 | local sent = f:send(putt) 212 | f:quit() 213 | f:close() 214 | return sent 215 | end 216 | 217 | local default = { 218 | path = "/", 219 | scheme = "ftp" 220 | } 221 | 222 | local function parse(u) 223 | local t = socket.try(url.parse(u, default)) 224 | socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") 225 | socket.try(t.host, "missing hostname") 226 | local pat = "^type=(.)$" 227 | if t.params then 228 | t.type = socket.skip(2, string.find(t.params, pat)) 229 | socket.try(t.type == "a" or t.type == "i", 230 | "invalid type '" .. t.type .. "'") 231 | end 232 | return t 233 | end 234 | 235 | local function sput(u, body) 236 | local putt = parse(u) 237 | putt.source = ltn12.source.string(body) 238 | return tput(putt) 239 | end 240 | 241 | _M.put = socket.protect(function(putt, body) 242 | if base.type(putt) == "string" then return sput(putt, body) 243 | else return tput(putt) end 244 | end) 245 | 246 | local function tget(gett) 247 | gett = override(gett) 248 | socket.try(gett.host, "missing hostname") 249 | local f = _M.open(gett.host, gett.port, gett.create) 250 | f:greet() 251 | f:login(gett.user, gett.password) 252 | if gett.type then f:type(gett.type) end 253 | f:pasv() 254 | f:receive(gett) 255 | f:quit() 256 | return f:close() 257 | end 258 | 259 | local function sget(u) 260 | local gett = parse(u) 261 | local t = {} 262 | gett.sink = ltn12.sink.table(t) 263 | tget(gett) 264 | return table.concat(t) 265 | end 266 | 267 | _M.command = socket.protect(function(cmdt) 268 | cmdt = override(cmdt) 269 | socket.try(cmdt.host, "missing hostname") 270 | socket.try(cmdt.command, "missing command") 271 | local f = _M.open(cmdt.host, cmdt.port, cmdt.create) 272 | f:greet() 273 | f:login(cmdt.user, cmdt.password) 274 | f.try(f.tp:command(cmdt.command, cmdt.argument)) 275 | if cmdt.check then f.try(f.tp:check(cmdt.check)) end 276 | f:quit() 277 | return f:close() 278 | end) 279 | 280 | _M.get = socket.protect(function(gett) 281 | if base.type(gett) == "string" then return sget(gett) 282 | else return tget(gett) end 283 | end) 284 | 285 | return _M -------------------------------------------------------------------------------- /bblibs/md5.lua: -------------------------------------------------------------------------------- 1 | local md5 = { 2 | _VERSION = "md5.lua 1.0.2", 3 | _DESCRIPTION = "MD5 computation in Lua (5.1-3, LuaJIT)", 4 | _URL = "https://github.com/kikito/md5.lua", 5 | _LICENSE = [[ 6 | MIT LICENSE 7 | 8 | Copyright (c) 2013 Enrique García Cota + Adam Baldwin + hanzao + Equi 4 Software 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a 11 | copy of this software and associated documentation files (the 12 | "Software"), to deal in the Software without restriction, including 13 | without limitation the rights to use, copy, modify, merge, publish, 14 | distribute, sublicense, and/or sell copies of the Software, and to 15 | permit persons to whom the Software is furnished to do so, subject to 16 | the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included 19 | in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 22 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | ]] 29 | } 30 | 31 | -- bit lib implementions 32 | 33 | local char, byte, format, rep, sub = 34 | string.char, string.byte, string.format, string.rep, string.sub 35 | local bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift 36 | 37 | local ok, bit = pcall(require, 'bit') 38 | if ok then 39 | bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift = bit.bor, bit.band, bit.bnot, bit.bxor, bit.rshift, bit.lshift 40 | else 41 | ok, bit = pcall(require, 'bit32') 42 | 43 | if ok then 44 | 45 | bit_not = bit.bnot 46 | 47 | local tobit = function(n) 48 | return n <= 0x7fffffff and n or -(bit_not(n) + 1) 49 | end 50 | 51 | local normalize = function(f) 52 | return function(a,b) return tobit(f(tobit(a), tobit(b))) end 53 | end 54 | 55 | bit_or, bit_and, bit_xor = normalize(bit.bor), normalize(bit.band), normalize(bit.bxor) 56 | bit_rshift, bit_lshift = normalize(bit.rshift), normalize(bit.lshift) 57 | 58 | else 59 | 60 | local function tbl2number(tbl) 61 | local result = 0 62 | local power = 1 63 | for i = 1, #tbl do 64 | result = result + tbl[i] * power 65 | power = power * 2 66 | end 67 | return result 68 | end 69 | 70 | local function expand(t1, t2) 71 | local big, small = t1, t2 72 | if(#big < #small) then 73 | big, small = small, big 74 | end 75 | -- expand small 76 | for i = #small + 1, #big do 77 | small[i] = 0 78 | end 79 | end 80 | 81 | local to_bits -- needs to be declared before bit_not 82 | 83 | bit_not = function(n) 84 | local tbl = to_bits(n) 85 | local size = math.max(#tbl, 32) 86 | for i = 1, size do 87 | if(tbl[i] == 1) then 88 | tbl[i] = 0 89 | else 90 | tbl[i] = 1 91 | end 92 | end 93 | return tbl2number(tbl) 94 | end 95 | 96 | -- defined as local above 97 | to_bits = function (n) 98 | if(n < 0) then 99 | -- negative 100 | return to_bits(bit_not(math.abs(n)) + 1) 101 | end 102 | -- to bits table 103 | local tbl = {} 104 | local cnt = 1 105 | local last 106 | while n > 0 do 107 | last = n % 2 108 | tbl[cnt] = last 109 | n = (n-last)/2 110 | cnt = cnt + 1 111 | end 112 | 113 | return tbl 114 | end 115 | 116 | bit_or = function(m, n) 117 | local tbl_m = to_bits(m) 118 | local tbl_n = to_bits(n) 119 | expand(tbl_m, tbl_n) 120 | 121 | local tbl = {} 122 | for i = 1, #tbl_m do 123 | if(tbl_m[i]== 0 and tbl_n[i] == 0) then 124 | tbl[i] = 0 125 | else 126 | tbl[i] = 1 127 | end 128 | end 129 | 130 | return tbl2number(tbl) 131 | end 132 | 133 | bit_and = function(m, n) 134 | local tbl_m = to_bits(m) 135 | local tbl_n = to_bits(n) 136 | expand(tbl_m, tbl_n) 137 | 138 | local tbl = {} 139 | for i = 1, #tbl_m do 140 | if(tbl_m[i]== 0 or tbl_n[i] == 0) then 141 | tbl[i] = 0 142 | else 143 | tbl[i] = 1 144 | end 145 | end 146 | 147 | return tbl2number(tbl) 148 | end 149 | 150 | bit_xor = function(m, n) 151 | local tbl_m = to_bits(m) 152 | local tbl_n = to_bits(n) 153 | expand(tbl_m, tbl_n) 154 | 155 | local tbl = {} 156 | for i = 1, #tbl_m do 157 | if(tbl_m[i] ~= tbl_n[i]) then 158 | tbl[i] = 1 159 | else 160 | tbl[i] = 0 161 | end 162 | end 163 | 164 | return tbl2number(tbl) 165 | end 166 | 167 | bit_rshift = function(n, bits) 168 | local high_bit = 0 169 | if(n < 0) then 170 | -- negative 171 | n = bit_not(math.abs(n)) + 1 172 | high_bit = 0x80000000 173 | end 174 | 175 | local floor = math.floor 176 | 177 | for i=1, bits do 178 | n = n/2 179 | n = bit_or(floor(n), high_bit) 180 | end 181 | return floor(n) 182 | end 183 | 184 | bit_lshift = function(n, bits) 185 | if(n < 0) then 186 | -- negative 187 | n = bit_not(math.abs(n)) + 1 188 | end 189 | 190 | for i=1, bits do 191 | n = n*2 192 | end 193 | return bit_and(n, 0xFFFFFFFF) 194 | end 195 | end 196 | end 197 | 198 | -- convert little-endian 32-bit int to a 4-char string 199 | local function lei2str(i) 200 | local f=function (s) return char( bit_and( bit_rshift(i, s), 255)) end 201 | return f(0)..f(8)..f(16)..f(24) 202 | end 203 | 204 | -- convert raw string to big-endian int 205 | local function str2bei(s) 206 | local v=0 207 | for i=1, #s do 208 | v = v * 256 + byte(s, i) 209 | end 210 | return v 211 | end 212 | 213 | -- convert raw string to little-endian int 214 | local function str2lei(s) 215 | local v=0 216 | for i = #s,1,-1 do 217 | v = v*256 + byte(s, i) 218 | end 219 | return v 220 | end 221 | 222 | -- cut up a string in little-endian ints of given size 223 | local function cut_le_str(s,...) 224 | local o, r = 1, {} 225 | local args = {...} 226 | for i=1, #args do 227 | table.insert(r, str2lei(sub(s, o, o + args[i] - 1))) 228 | o = o + args[i] 229 | end 230 | return r 231 | end 232 | 233 | local swap = function (w) return str2bei(lei2str(w)) end 234 | 235 | local function hex2binaryaux(hexval) 236 | return char(tonumber(hexval, 16)) 237 | end 238 | 239 | local function hex2binary(hex) 240 | local result, _ = hex:gsub('..', hex2binaryaux) 241 | return result 242 | end 243 | 244 | -- An MD5 mplementation in Lua, requires bitlib (hacked to use LuaBit from above, ugh) 245 | -- 10/02/2001 jcw@equi4.com 246 | 247 | local CONSTS = { 248 | 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 249 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 250 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 251 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 252 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 253 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 254 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 255 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 256 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 257 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 258 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 259 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 260 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 261 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 262 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 263 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, 264 | 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 265 | } 266 | 267 | local f=function (x,y,z) return bit_or(bit_and(x,y),bit_and(-x-1,z)) end 268 | local g=function (x,y,z) return bit_or(bit_and(x,z),bit_and(y,-z-1)) end 269 | local h=function (x,y,z) return bit_xor(x,bit_xor(y,z)) end 270 | local i=function (x,y,z) return bit_xor(y,bit_or(x,-z-1)) end 271 | local z=function (f,a,b,c,d,x,s,ac) 272 | a=bit_and(a+f(b,c,d)+x+ac,0xFFFFFFFF) 273 | -- be *very* careful that left shift does not cause rounding! 274 | return bit_or(bit_lshift(bit_and(a,bit_rshift(0xFFFFFFFF,s)),s),bit_rshift(a,32-s))+b 275 | end 276 | 277 | local function transform(A,B,C,D,X) 278 | local a,b,c,d=A,B,C,D 279 | local t=CONSTS 280 | 281 | a=z(f,a,b,c,d,X[ 0], 7,t[ 1]) 282 | d=z(f,d,a,b,c,X[ 1],12,t[ 2]) 283 | c=z(f,c,d,a,b,X[ 2],17,t[ 3]) 284 | b=z(f,b,c,d,a,X[ 3],22,t[ 4]) 285 | a=z(f,a,b,c,d,X[ 4], 7,t[ 5]) 286 | d=z(f,d,a,b,c,X[ 5],12,t[ 6]) 287 | c=z(f,c,d,a,b,X[ 6],17,t[ 7]) 288 | b=z(f,b,c,d,a,X[ 7],22,t[ 8]) 289 | a=z(f,a,b,c,d,X[ 8], 7,t[ 9]) 290 | d=z(f,d,a,b,c,X[ 9],12,t[10]) 291 | c=z(f,c,d,a,b,X[10],17,t[11]) 292 | b=z(f,b,c,d,a,X[11],22,t[12]) 293 | a=z(f,a,b,c,d,X[12], 7,t[13]) 294 | d=z(f,d,a,b,c,X[13],12,t[14]) 295 | c=z(f,c,d,a,b,X[14],17,t[15]) 296 | b=z(f,b,c,d,a,X[15],22,t[16]) 297 | 298 | a=z(g,a,b,c,d,X[ 1], 5,t[17]) 299 | d=z(g,d,a,b,c,X[ 6], 9,t[18]) 300 | c=z(g,c,d,a,b,X[11],14,t[19]) 301 | b=z(g,b,c,d,a,X[ 0],20,t[20]) 302 | a=z(g,a,b,c,d,X[ 5], 5,t[21]) 303 | d=z(g,d,a,b,c,X[10], 9,t[22]) 304 | c=z(g,c,d,a,b,X[15],14,t[23]) 305 | b=z(g,b,c,d,a,X[ 4],20,t[24]) 306 | a=z(g,a,b,c,d,X[ 9], 5,t[25]) 307 | d=z(g,d,a,b,c,X[14], 9,t[26]) 308 | c=z(g,c,d,a,b,X[ 3],14,t[27]) 309 | b=z(g,b,c,d,a,X[ 8],20,t[28]) 310 | a=z(g,a,b,c,d,X[13], 5,t[29]) 311 | d=z(g,d,a,b,c,X[ 2], 9,t[30]) 312 | c=z(g,c,d,a,b,X[ 7],14,t[31]) 313 | b=z(g,b,c,d,a,X[12],20,t[32]) 314 | 315 | a=z(h,a,b,c,d,X[ 5], 4,t[33]) 316 | d=z(h,d,a,b,c,X[ 8],11,t[34]) 317 | c=z(h,c,d,a,b,X[11],16,t[35]) 318 | b=z(h,b,c,d,a,X[14],23,t[36]) 319 | a=z(h,a,b,c,d,X[ 1], 4,t[37]) 320 | d=z(h,d,a,b,c,X[ 4],11,t[38]) 321 | c=z(h,c,d,a,b,X[ 7],16,t[39]) 322 | b=z(h,b,c,d,a,X[10],23,t[40]) 323 | a=z(h,a,b,c,d,X[13], 4,t[41]) 324 | d=z(h,d,a,b,c,X[ 0],11,t[42]) 325 | c=z(h,c,d,a,b,X[ 3],16,t[43]) 326 | b=z(h,b,c,d,a,X[ 6],23,t[44]) 327 | a=z(h,a,b,c,d,X[ 9], 4,t[45]) 328 | d=z(h,d,a,b,c,X[12],11,t[46]) 329 | c=z(h,c,d,a,b,X[15],16,t[47]) 330 | b=z(h,b,c,d,a,X[ 2],23,t[48]) 331 | 332 | a=z(i,a,b,c,d,X[ 0], 6,t[49]) 333 | d=z(i,d,a,b,c,X[ 7],10,t[50]) 334 | c=z(i,c,d,a,b,X[14],15,t[51]) 335 | b=z(i,b,c,d,a,X[ 5],21,t[52]) 336 | a=z(i,a,b,c,d,X[12], 6,t[53]) 337 | d=z(i,d,a,b,c,X[ 3],10,t[54]) 338 | c=z(i,c,d,a,b,X[10],15,t[55]) 339 | b=z(i,b,c,d,a,X[ 1],21,t[56]) 340 | a=z(i,a,b,c,d,X[ 8], 6,t[57]) 341 | d=z(i,d,a,b,c,X[15],10,t[58]) 342 | c=z(i,c,d,a,b,X[ 6],15,t[59]) 343 | b=z(i,b,c,d,a,X[13],21,t[60]) 344 | a=z(i,a,b,c,d,X[ 4], 6,t[61]) 345 | d=z(i,d,a,b,c,X[11],10,t[62]) 346 | c=z(i,c,d,a,b,X[ 2],15,t[63]) 347 | b=z(i,b,c,d,a,X[ 9],21,t[64]) 348 | 349 | return A+a,B+b,C+c,D+d 350 | end 351 | 352 | ---------------------------------------------------------------- 353 | 354 | function md5.sumhexa(s) 355 | local msgLen = #s 356 | local padLen = 56 - msgLen % 64 357 | 358 | if msgLen % 64 > 56 then padLen = padLen + 64 end 359 | 360 | if padLen == 0 then padLen = 64 end 361 | 362 | s = s .. char(128) .. rep(char(0),padLen-1) .. lei2str(8*msgLen) .. lei2str(0) 363 | 364 | assert(#s % 64 == 0) 365 | 366 | local t = CONSTS 367 | local a,b,c,d = t[65],t[66],t[67],t[68] 368 | 369 | for i=1,#s,64 do 370 | local X = cut_le_str(sub(s,i,i+63),4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4) 371 | assert(#X == 16) 372 | X[0] = table.remove(X,1) -- zero based! 373 | a,b,c,d = transform(a,b,c,d,X) 374 | end 375 | 376 | return format("%08x%08x%08x%08x",swap(a),swap(b),swap(c),swap(d)) 377 | end 378 | 379 | function md5.sum(s) 380 | return hex2binary(md5.sumhexa(s)) 381 | end 382 | 383 | return md5 -------------------------------------------------------------------------------- /bblibs/socket/url.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- URI parsing, composition and relative URL resolution 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | ----------------------------------------------------------------------------- 6 | 7 | ----------------------------------------------------------------------------- 8 | -- Declare module 9 | ----------------------------------------------------------------------------- 10 | local string = require("string") 11 | local base = _G 12 | local table = require("table") 13 | local socket = require("bblibs.socket.socket") 14 | 15 | socket.url = {} 16 | local _M = socket.url 17 | 18 | ----------------------------------------------------------------------------- 19 | -- Module version 20 | ----------------------------------------------------------------------------- 21 | _M._VERSION = "URL 1.0.3" 22 | 23 | ----------------------------------------------------------------------------- 24 | -- Encodes a string into its escaped hexadecimal representation 25 | -- Input 26 | -- s: binary string to be encoded 27 | -- Returns 28 | -- escaped representation of string binary 29 | ----------------------------------------------------------------------------- 30 | function _M.escape(s) 31 | return (string.gsub(s, "([^A-Za-z0-9_])", function(c) 32 | return string.format("%%%02x", string.byte(c)) 33 | end)) 34 | end 35 | 36 | ----------------------------------------------------------------------------- 37 | -- Protects a path segment, to prevent it from interfering with the 38 | -- url parsing. 39 | -- Input 40 | -- s: binary string to be encoded 41 | -- Returns 42 | -- escaped representation of string binary 43 | ----------------------------------------------------------------------------- 44 | local function make_set(t) 45 | local s = {} 46 | for i,v in base.ipairs(t) do 47 | s[t[i]] = 1 48 | end 49 | return s 50 | end 51 | 52 | -- these are allowed withing a path segment, along with alphanum 53 | -- other characters must be escaped 54 | local segment_set = make_set { 55 | "-", "_", ".", "!", "~", "*", "'", "(", 56 | ")", ":", "@", "&", "=", "+", "$", ",", 57 | } 58 | 59 | local function protect_segment(s) 60 | return string.gsub(s, "([^A-Za-z0-9_])", function (c) 61 | if segment_set[c] then return c 62 | else return string.format("%%%02x", string.byte(c)) end 63 | end) 64 | end 65 | 66 | ----------------------------------------------------------------------------- 67 | -- Encodes a string into its escaped hexadecimal representation 68 | -- Input 69 | -- s: binary string to be encoded 70 | -- Returns 71 | -- escaped representation of string binary 72 | ----------------------------------------------------------------------------- 73 | function _M.unescape(s) 74 | return (string.gsub(s, "%%(%x%x)", function(hex) 75 | return string.char(base.tonumber(hex, 16)) 76 | end)) 77 | end 78 | 79 | ----------------------------------------------------------------------------- 80 | -- Builds a path from a base path and a relative path 81 | -- Input 82 | -- base_path 83 | -- relative_path 84 | -- Returns 85 | -- corresponding absolute path 86 | ----------------------------------------------------------------------------- 87 | local function absolute_path(base_path, relative_path) 88 | if string.sub(relative_path, 1, 1) == "/" then return relative_path end 89 | local path = string.gsub(base_path, "[^/]*$", "") 90 | path = path .. relative_path 91 | path = string.gsub(path, "([^/]*%./)", function (s) 92 | if s ~= "./" then return s else return "" end 93 | end) 94 | path = string.gsub(path, "/%.$", "/") 95 | local reduced 96 | while reduced ~= path do 97 | reduced = path 98 | path = string.gsub(reduced, "([^/]*/%.%./)", function (s) 99 | if s ~= "../../" then return "" else return s end 100 | end) 101 | end 102 | path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) 103 | if s ~= "../.." then return "" else return s end 104 | end) 105 | return path 106 | end 107 | 108 | ----------------------------------------------------------------------------- 109 | -- Parses a url and returns a table with all its parts according to RFC 2396 110 | -- The following grammar describes the names given to the URL parts 111 | -- ::= :///;?# 112 | -- ::= @: 113 | -- ::= [:] 114 | -- :: = {/} 115 | -- Input 116 | -- url: uniform resource locator of request 117 | -- default: table with default values for each field 118 | -- Returns 119 | -- table with the following fields, where RFC naming conventions have 120 | -- been preserved: 121 | -- scheme, authority, userinfo, user, password, host, port, 122 | -- path, params, query, fragment 123 | -- Obs: 124 | -- the leading '/' in {/} is considered part of 125 | ----------------------------------------------------------------------------- 126 | function _M.parse(url, default) 127 | -- initialize default parameters 128 | local parsed = {} 129 | for i,v in base.pairs(default or parsed) do parsed[i] = v end 130 | -- empty url is parsed to nil 131 | if not url or url == "" then return nil, "invalid url" end 132 | -- remove whitespace 133 | -- url = string.gsub(url, "%s", "") 134 | -- get fragment 135 | url = string.gsub(url, "#(.*)$", function(f) 136 | parsed.fragment = f 137 | return "" 138 | end) 139 | -- get scheme 140 | url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", 141 | function(s) parsed.scheme = s; return "" end) 142 | -- get authority 143 | url = string.gsub(url, "^//([^/]*)", function(n) 144 | parsed.authority = n 145 | return "" 146 | end) 147 | -- get query string 148 | url = string.gsub(url, "%?(.*)", function(q) 149 | parsed.query = q 150 | return "" 151 | end) 152 | -- get params 153 | url = string.gsub(url, "%;(.*)", function(p) 154 | parsed.params = p 155 | return "" 156 | end) 157 | -- path is whatever was left 158 | if url ~= "" then parsed.path = url end 159 | local authority = parsed.authority 160 | if not authority then return parsed end 161 | authority = string.gsub(authority,"^([^@]*)@", 162 | function(u) parsed.userinfo = u; return "" end) 163 | authority = string.gsub(authority, ":([^:%]]*)$", 164 | function(p) parsed.port = p; return "" end) 165 | if authority ~= "" then 166 | -- IPv6? 167 | parsed.host = string.match(authority, "^%[(.+)%]$") or authority 168 | end 169 | local userinfo = parsed.userinfo 170 | if not userinfo then return parsed end 171 | userinfo = string.gsub(userinfo, ":([^:]*)$", 172 | function(p) parsed.password = p; return "" end) 173 | parsed.user = userinfo 174 | return parsed 175 | end 176 | 177 | ----------------------------------------------------------------------------- 178 | -- Rebuilds a parsed URL from its components. 179 | -- Components are protected if any reserved or unallowed characters are found 180 | -- Input 181 | -- parsed: parsed URL, as returned by parse 182 | -- Returns 183 | -- a stringing with the corresponding URL 184 | ----------------------------------------------------------------------------- 185 | function _M.build(parsed) 186 | local ppath = _M.parse_path(parsed.path or "") 187 | local url = _M.build_path(ppath) 188 | if parsed.params then url = url .. ";" .. parsed.params end 189 | if parsed.query then url = url .. "?" .. parsed.query end 190 | local authority = parsed.authority 191 | if parsed.host then 192 | authority = parsed.host 193 | if string.find(authority, ":") then -- IPv6? 194 | authority = "[" .. authority .. "]" 195 | end 196 | if parsed.port then authority = authority .. ":" .. parsed.port end 197 | local userinfo = parsed.userinfo 198 | if parsed.user then 199 | userinfo = parsed.user 200 | if parsed.password then 201 | userinfo = userinfo .. ":" .. parsed.password 202 | end 203 | end 204 | if userinfo then authority = userinfo .. "@" .. authority end 205 | end 206 | if authority then url = "//" .. authority .. url end 207 | if parsed.scheme then url = parsed.scheme .. ":" .. url end 208 | if parsed.fragment then url = url .. "#" .. parsed.fragment end 209 | -- url = string.gsub(url, "%s", "") 210 | return url 211 | end 212 | 213 | ----------------------------------------------------------------------------- 214 | -- Builds a absolute URL from a base and a relative URL according to RFC 2396 215 | -- Input 216 | -- base_url 217 | -- relative_url 218 | -- Returns 219 | -- corresponding absolute url 220 | ----------------------------------------------------------------------------- 221 | function _M.absolute(base_url, relative_url) 222 | if base.type(base_url) == "table" then 223 | base_parsed = base_url 224 | base_url = _M.build(base_parsed) 225 | else 226 | base_parsed = _M.parse(base_url) 227 | end 228 | local relative_parsed = _M.parse(relative_url) 229 | if not base_parsed then return relative_url 230 | elseif not relative_parsed then return base_url 231 | elseif relative_parsed.scheme then return relative_url 232 | else 233 | relative_parsed.scheme = base_parsed.scheme 234 | if not relative_parsed.authority then 235 | relative_parsed.authority = base_parsed.authority 236 | if not relative_parsed.path then 237 | relative_parsed.path = base_parsed.path 238 | if not relative_parsed.params then 239 | relative_parsed.params = base_parsed.params 240 | if not relative_parsed.query then 241 | relative_parsed.query = base_parsed.query 242 | end 243 | end 244 | else 245 | relative_parsed.path = absolute_path(base_parsed.path or "", 246 | relative_parsed.path) 247 | end 248 | end 249 | return _M.build(relative_parsed) 250 | end 251 | end 252 | 253 | ----------------------------------------------------------------------------- 254 | -- Breaks a path into its segments, unescaping the segments 255 | -- Input 256 | -- path 257 | -- Returns 258 | -- segment: a table with one entry per segment 259 | ----------------------------------------------------------------------------- 260 | function _M.parse_path(path) 261 | local parsed = {} 262 | path = path or "" 263 | --path = string.gsub(path, "%s", "") 264 | string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) 265 | for i = 1, #parsed do 266 | parsed[i] = _M.unescape(parsed[i]) 267 | end 268 | if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end 269 | if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end 270 | return parsed 271 | end 272 | 273 | ----------------------------------------------------------------------------- 274 | -- Builds a path component from its segments, escaping protected characters. 275 | -- Input 276 | -- parsed: path segments 277 | -- unsafe: if true, segments are not protected before path is built 278 | -- Returns 279 | -- path: corresponding path stringing 280 | ----------------------------------------------------------------------------- 281 | function _M.build_path(parsed, unsafe) 282 | local path = "" 283 | local n = #parsed 284 | if unsafe then 285 | for i = 1, n-1 do 286 | path = path .. parsed[i] 287 | path = path .. "/" 288 | end 289 | if n > 0 then 290 | path = path .. parsed[n] 291 | if parsed.is_directory then path = path .. "/" end 292 | end 293 | else 294 | for i = 1, n-1 do 295 | path = path .. protect_segment(parsed[i]) 296 | path = path .. "/" 297 | end 298 | if n > 0 then 299 | path = path .. protect_segment(parsed[n]) 300 | if parsed.is_directory then path = path .. "/" end 301 | end 302 | end 303 | if parsed.is_absolute then path = "/" .. path end 304 | return path 305 | end 306 | 307 | return _M 308 | -------------------------------------------------------------------------------- /bblibs/socket/http.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- HTTP/1.1 client support for the Lua language. 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | ----------------------------------------------------------------------------- 6 | 7 | ----------------------------------------------------------------------------- 8 | -- Declare module and import dependencies 9 | ------------------------------------------------------------------------------- 10 | local socket = require("bblibs.socket.socket") 11 | local url = require("bblibs.socket.url") 12 | local ltn12 = require("bblibs.socket.ltn12") 13 | local mime = require("bblibs.socket.mime") 14 | local string = require("string") 15 | local headers = require("bblibs.socket.headers") 16 | local base = _G 17 | local table = require("table") 18 | socket.http = {} 19 | local _M = socket.http 20 | 21 | ----------------------------------------------------------------------------- 22 | -- Program constants 23 | ----------------------------------------------------------------------------- 24 | -- connection timeout in seconds 25 | _M.TIMEOUT = 60 26 | -- default port for document retrieval 27 | _M.PORT = 80 28 | -- user agent field sent in request 29 | _M.USERAGENT = socket._VERSION 30 | 31 | ----------------------------------------------------------------------------- 32 | -- Reads MIME headers from a connection, unfolding where needed 33 | ----------------------------------------------------------------------------- 34 | local function receiveheaders(sock, headers) 35 | local line, name, value, err 36 | headers = headers or {} 37 | -- get first line 38 | line, err = sock:receive() 39 | if err then return nil, err end 40 | -- headers go until a blank line is found 41 | while line ~= "" do 42 | -- get field-name and value 43 | name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) 44 | if not (name and value) then return nil, "malformed reponse headers" end 45 | name = string.lower(name) 46 | -- get next line (value might be folded) 47 | line, err = sock:receive() 48 | if err then return nil, err end 49 | -- unfold any folded values 50 | while string.find(line, "^%s") do 51 | value = value .. line 52 | line = sock:receive() 53 | if err then return nil, err end 54 | end 55 | -- save pair in table 56 | if headers[name] then headers[name] = headers[name] .. ", " .. value 57 | else headers[name] = value end 58 | end 59 | return headers 60 | end 61 | 62 | ----------------------------------------------------------------------------- 63 | -- Extra sources and sinks 64 | ----------------------------------------------------------------------------- 65 | socket.sourcet["http-chunked"] = function(sock, headers) 66 | return base.setmetatable({ 67 | getfd = function() return sock:getfd() end, 68 | dirty = function() return sock:dirty() end 69 | }, { 70 | __call = function() 71 | -- get chunk size, skip extention 72 | local line, err = sock:receive() 73 | if err then return nil, err end 74 | local size = base.tonumber(string.gsub(line, ";.*", ""), 16) 75 | if not size then return nil, "invalid chunk size" end 76 | -- was it the last chunk? 77 | if size > 0 then 78 | -- if not, get chunk and skip terminating CRLF 79 | local chunk, err, part = sock:receive(size) 80 | if chunk then sock:receive() end 81 | return chunk, err 82 | else 83 | -- if it was, read trailers into headers table 84 | headers, err = receiveheaders(sock, headers) 85 | if not headers then return nil, err end 86 | end 87 | end 88 | }) 89 | end 90 | 91 | socket.sinkt["http-chunked"] = function(sock) 92 | return base.setmetatable({ 93 | getfd = function() return sock:getfd() end, 94 | dirty = function() return sock:dirty() end 95 | }, { 96 | __call = function(self, chunk, err) 97 | if not chunk then return sock:send("0\r\n\r\n") end 98 | local size = string.format("%X\r\n", string.len(chunk)) 99 | return sock:send(size .. chunk .. "\r\n") 100 | end 101 | }) 102 | end 103 | 104 | ----------------------------------------------------------------------------- 105 | -- Low level HTTP API 106 | ----------------------------------------------------------------------------- 107 | local metat = { __index = {} } 108 | 109 | function _M.open(host, port, create) 110 | -- create socket with user connect function, or with default 111 | local c = socket.try((create or socket.tcp)()) 112 | local h = base.setmetatable({ c = c }, metat) 113 | -- create finalized try 114 | h.try = socket.newtry(function() h:close() end) 115 | -- set timeout before connecting 116 | h.try(c:settimeout(_M.TIMEOUT)) 117 | h.try(c:connect(host, port or _M.PORT)) 118 | -- here everything worked 119 | return h 120 | end 121 | 122 | function metat.__index:sendrequestline(method, uri) 123 | local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) 124 | return self.try(self.c:send(reqline)) 125 | end 126 | 127 | function metat.__index:sendheaders(tosend) 128 | local canonic = headers.canonic 129 | local h = "\r\n" 130 | for f, v in base.pairs(tosend) do 131 | h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h 132 | end 133 | self.try(self.c:send(h)) 134 | return 1 135 | end 136 | 137 | function metat.__index:sendbody(headers, source, step) 138 | source = source or ltn12.source.empty() 139 | step = step or ltn12.pump.step 140 | -- if we don't know the size in advance, send chunked and hope for the best 141 | local mode = "http-chunked" 142 | if headers["content-length"] then mode = "keep-open" end 143 | return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) 144 | end 145 | 146 | function metat.__index:receivestatusline() 147 | local status = self.try(self.c:receive(5)) 148 | -- identify HTTP/0.9 responses, which do not contain a status line 149 | -- this is just a heuristic, but is what the RFC recommends 150 | if status ~= "HTTP/" then return nil, status end 151 | -- otherwise proceed reading a status line 152 | status = self.try(self.c:receive("*l", status)) 153 | local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) 154 | return self.try(base.tonumber(code), status) 155 | end 156 | 157 | function metat.__index:receiveheaders() 158 | return self.try(receiveheaders(self.c)) 159 | end 160 | 161 | function metat.__index:receivebody(headers, sink, step) 162 | sink = sink or ltn12.sink.null() 163 | step = step or ltn12.pump.step 164 | local length = base.tonumber(headers["content-length"]) 165 | local t = headers["transfer-encoding"] -- shortcut 166 | local mode = "default" -- connection close 167 | if t and t ~= "identity" then mode = "http-chunked" 168 | elseif base.tonumber(headers["content-length"]) then mode = "by-length" end 169 | return self.try(ltn12.pump.all(socket.source(mode, self.c, length), 170 | sink, step)) 171 | end 172 | 173 | function metat.__index:receive09body(status, sink, step) 174 | local source = ltn12.source.rewind(socket.source("until-closed", self.c)) 175 | source(status) 176 | return self.try(ltn12.pump.all(source, sink, step)) 177 | end 178 | 179 | function metat.__index:close() 180 | return self.c:close() 181 | end 182 | 183 | ----------------------------------------------------------------------------- 184 | -- High level HTTP API 185 | ----------------------------------------------------------------------------- 186 | local function adjusturi(reqt) 187 | local u = reqt 188 | -- if there is a proxy, we need the full url. otherwise, just a part. 189 | if not reqt.proxy and not _M.PROXY then 190 | u = { 191 | path = socket.try(reqt.path, "invalid path 'nil'"), 192 | params = reqt.params, 193 | query = reqt.query, 194 | fragment = reqt.fragment 195 | } 196 | end 197 | return url.build(u) 198 | end 199 | 200 | local function adjustproxy(reqt) 201 | local proxy = reqt.proxy or _M.PROXY 202 | if proxy then 203 | proxy = url.parse(proxy) 204 | return proxy.host, proxy.port or 3128 205 | else 206 | return reqt.host, reqt.port 207 | end 208 | end 209 | 210 | local function adjustheaders(reqt) 211 | -- default headers 212 | local host = string.gsub(reqt.authority, "^.-@", "") 213 | local lower = { 214 | ["user-agent"] = _M.USERAGENT, 215 | ["host"] = host, 216 | ["connection"] = "close, TE", 217 | ["te"] = "trailers" 218 | } 219 | -- if we have authentication information, pass it along 220 | if reqt.user and reqt.password then 221 | lower["authorization"] = 222 | "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) 223 | end 224 | -- if we have proxy authentication information, pass it along 225 | local proxy = reqt.proxy or _M.PROXY 226 | if proxy then 227 | proxy = url.parse(proxy) 228 | if proxy.user and proxy.password then 229 | lower["proxy-authorization"] = 230 | "Basic " .. (mime.b64(proxy.user .. ":" .. proxy.password)) 231 | end 232 | end 233 | -- override with user headers 234 | for i,v in base.pairs(reqt.headers or lower) do 235 | lower[string.lower(i)] = v 236 | end 237 | return lower 238 | end 239 | 240 | -- default url parts 241 | local default = { 242 | host = "", 243 | port = _M.PORT, 244 | path ="/", 245 | scheme = "http" 246 | } 247 | 248 | local function adjustrequest(reqt) 249 | -- parse url if provided 250 | local nreqt = reqt.url and url.parse(reqt.url, default) or {} 251 | -- explicit components override url 252 | for i,v in base.pairs(reqt) do nreqt[i] = v end 253 | if nreqt.port == "" then nreqt.port = 80 end 254 | socket.try(nreqt.host and nreqt.host ~= "", 255 | "invalid host '" .. base.tostring(nreqt.host) .. "'") 256 | -- compute uri if user hasn't overriden 257 | nreqt.uri = reqt.uri or adjusturi(nreqt) 258 | -- adjust headers in request 259 | nreqt.headers = adjustheaders(nreqt) 260 | -- ajust host and port if there is a proxy 261 | nreqt.host, nreqt.port = adjustproxy(nreqt) 262 | return nreqt 263 | end 264 | 265 | local function shouldredirect(reqt, code, headers) 266 | return headers.location and 267 | string.gsub(headers.location, "%s", "") ~= "" and 268 | (reqt.redirect ~= false) and 269 | (code == 301 or code == 302 or code == 303 or code == 307) and 270 | (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") 271 | and (not reqt.nredirects or reqt.nredirects < 5) 272 | end 273 | 274 | local function shouldreceivebody(reqt, code) 275 | if reqt.method == "HEAD" then return nil end 276 | if code == 204 or code == 304 then return nil end 277 | if code >= 100 and code < 200 then return nil end 278 | return 1 279 | end 280 | 281 | -- forward declarations 282 | local trequest, tredirect 283 | 284 | --[[local]] function tredirect(reqt, location) 285 | local result, code, headers, status = trequest { 286 | -- the RFC says the redirect URL has to be absolute, but some 287 | -- servers do not respect that 288 | url = url.absolute(reqt.url, location), 289 | source = reqt.source, 290 | sink = reqt.sink, 291 | headers = reqt.headers, 292 | proxy = reqt.proxy, 293 | nredirects = (reqt.nredirects or 0) + 1, 294 | create = reqt.create 295 | } 296 | -- pass location header back as a hint we redirected 297 | headers = headers or {} 298 | headers.location = headers.location or location 299 | return result, code, headers, status 300 | end 301 | 302 | --[[local]] function trequest(reqt) 303 | -- we loop until we get what we want, or 304 | -- until we are sure there is no way to get it 305 | local nreqt = adjustrequest(reqt) 306 | local h = _M.open(nreqt.host, nreqt.port, nreqt.create) 307 | -- send request line and headers 308 | h:sendrequestline(nreqt.method, nreqt.uri) 309 | h:sendheaders(nreqt.headers) 310 | -- if there is a body, send it 311 | if nreqt.source then 312 | h:sendbody(nreqt.headers, nreqt.source, nreqt.step) 313 | end 314 | local code, status = h:receivestatusline() 315 | -- if it is an HTTP/0.9 server, simply get the body and we are done 316 | if not code then 317 | h:receive09body(status, nreqt.sink, nreqt.step) 318 | return 1, 200 319 | end 320 | local headers 321 | -- ignore any 100-continue messages 322 | while code == 100 do 323 | headers = h:receiveheaders() 324 | code, status = h:receivestatusline() 325 | end 326 | headers = h:receiveheaders() 327 | -- at this point we should have a honest reply from the server 328 | -- we can't redirect if we already used the source, so we report the error 329 | if shouldredirect(nreqt, code, headers) and not nreqt.source then 330 | h:close() 331 | return tredirect(reqt, headers.location) 332 | end 333 | -- here we are finally done 334 | if shouldreceivebody(nreqt, code) then 335 | h:receivebody(headers, nreqt.sink, nreqt.step) 336 | end 337 | h:close() 338 | return 1, code, headers, status 339 | end 340 | 341 | local function srequest(u, b) 342 | local t = {} 343 | local reqt = { 344 | url = u, 345 | sink = ltn12.sink.table(t) 346 | } 347 | if b then 348 | reqt.source = ltn12.source.string(b) 349 | reqt.headers = { 350 | ["content-length"] = string.len(b), 351 | ["content-type"] = "application/x-www-form-urlencoded" 352 | } 353 | reqt.method = "POST" 354 | end 355 | local code, headers, status = socket.skip(1, trequest(reqt)) 356 | return table.concat(t), code, headers, status 357 | end 358 | 359 | _M.request = socket.protect(function(reqt, body) 360 | if base.type(reqt) == "string" then return srequest(reqt, body) 361 | else return trequest(reqt) end 362 | end) 363 | 364 | return _M 365 | -------------------------------------------------------------------------------- /bblibs/JSON.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- 3 | -- Simple JSON encoding and decoding in pure Lua. 4 | -- 5 | -- Copyright 2010-2014 Jeffrey Friedl 6 | -- http://regex.info/blog/ 7 | -- 8 | -- Latest version: http://regex.info/blog/lua/json 9 | -- 10 | -- This code is released under a Creative Commons CC-BY "Attribution" License: 11 | -- http://creativecommons.org/licenses/by/3.0/deed.en_US 12 | -- 13 | -- It can be used for any purpose so long as the copyright notice above, 14 | -- the web-page links above, and the 'AUTHOR_NOTE' string below are 15 | -- maintained. Enjoy. 16 | -- 17 | local VERSION = 20141223.14 -- version history at end of file 18 | local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20141223.14 ]-" 19 | 20 | -- 21 | -- The 'AUTHOR_NOTE' variable exists so that information about the source 22 | -- of the package is maintained even in compiled versions. It's also 23 | -- included in OBJDEF below mostly to quiet warnings about unused variables. 24 | -- 25 | local OBJDEF = { 26 | VERSION = VERSION, 27 | AUTHOR_NOTE = AUTHOR_NOTE, 28 | } 29 | 30 | 31 | -- 32 | -- Simple JSON encoding and decoding in pure Lua. 33 | -- http://www.json.org/ 34 | -- 35 | -- 36 | -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines 37 | -- 38 | -- local lua_value = JSON:decode(raw_json_text) 39 | -- 40 | -- local raw_json_text = JSON:encode(lua_table_or_value) 41 | -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability 42 | -- 43 | -- 44 | -- 45 | -- DECODING (from a JSON string to a Lua table) 46 | -- 47 | -- 48 | -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines 49 | -- 50 | -- local lua_value = JSON:decode(raw_json_text) 51 | -- 52 | -- If the JSON text is for an object or an array, e.g. 53 | -- { "what": "books", "count": 3 } 54 | -- or 55 | -- [ "Larry", "Curly", "Moe" ] 56 | -- 57 | -- the result is a Lua table, e.g. 58 | -- { what = "books", count = 3 } 59 | -- or 60 | -- { "Larry", "Curly", "Moe" } 61 | -- 62 | -- 63 | -- The encode and decode routines accept an optional second argument, 64 | -- "etc", which is not used during encoding or decoding, but upon error 65 | -- is passed along to error handlers. It can be of any type (including nil). 66 | -- 67 | -- 68 | -- 69 | -- ERROR HANDLING 70 | -- 71 | -- With most errors during decoding, this code calls 72 | -- 73 | -- JSON:onDecodeError(message, text, location, etc) 74 | -- 75 | -- with a message about the error, and if known, the JSON text being 76 | -- parsed and the byte count where the problem was discovered. You can 77 | -- replace the default JSON:onDecodeError() with your own function. 78 | -- 79 | -- The default onDecodeError() merely augments the message with data 80 | -- about the text and the location if known (and if a second 'etc' 81 | -- argument had been provided to decode(), its value is tacked onto the 82 | -- message as well), and then calls JSON.assert(), which itself defaults 83 | -- to Lua's built-in assert(), and can also be overridden. 84 | -- 85 | -- For example, in an Adobe Lightroom plugin, you might use something like 86 | -- 87 | -- function JSON:onDecodeError(message, text, location, etc) 88 | -- LrErrors.throwUserError("Internal Error: invalid JSON data") 89 | -- end 90 | -- 91 | -- or even just 92 | -- 93 | -- function JSON.assert(message) 94 | -- LrErrors.throwUserError("Internal Error: " .. message) 95 | -- end 96 | -- 97 | -- If JSON:decode() is passed a nil, this is called instead: 98 | -- 99 | -- JSON:onDecodeOfNilError(message, nil, nil, etc) 100 | -- 101 | -- and if JSON:decode() is passed HTML instead of JSON, this is called: 102 | -- 103 | -- JSON:onDecodeOfHTMLError(message, text, nil, etc) 104 | -- 105 | -- The use of the fourth 'etc' argument allows stronger coordination 106 | -- between decoding and error reporting, especially when you provide your 107 | -- own error-handling routines. Continuing with the the Adobe Lightroom 108 | -- plugin example: 109 | -- 110 | -- function JSON:onDecodeError(message, text, location, etc) 111 | -- local note = "Internal Error: invalid JSON data" 112 | -- if type(etc) = 'table' and etc.photo then 113 | -- note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName') 114 | -- end 115 | -- LrErrors.throwUserError(note) 116 | -- end 117 | -- 118 | -- : 119 | -- : 120 | -- 121 | -- for i, photo in ipairs(photosToProcess) do 122 | -- : 123 | -- : 124 | -- local data = JSON:decode(someJsonText, { photo = photo }) 125 | -- : 126 | -- : 127 | -- end 128 | -- 129 | -- 130 | -- 131 | -- 132 | -- 133 | -- DECODING AND STRICT TYPES 134 | -- 135 | -- Because both JSON objects and JSON arrays are converted to Lua tables, 136 | -- it's not normally possible to tell which original JSON type a 137 | -- particular Lua table was derived from, or guarantee decode-encode 138 | -- round-trip equivalency. 139 | -- 140 | -- However, if you enable strictTypes, e.g. 141 | -- 142 | -- JSON = assert(loadfile "JSON.lua")() --load the routines 143 | -- JSON.strictTypes = true 144 | -- 145 | -- then the Lua table resulting from the decoding of a JSON object or 146 | -- JSON array is marked via Lua metatable, so that when re-encoded with 147 | -- JSON:encode() it ends up as the appropriate JSON type. 148 | -- 149 | -- (This is not the default because other routines may not work well with 150 | -- tables that have a metatable set, for example, Lightroom API calls.) 151 | -- 152 | -- 153 | -- ENCODING (from a lua table to a JSON string) 154 | -- 155 | -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines 156 | -- 157 | -- local raw_json_text = JSON:encode(lua_table_or_value) 158 | -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability 159 | -- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = "| ", align_keys = false }) 160 | -- 161 | -- On error during encoding, this code calls: 162 | -- 163 | -- JSON:onEncodeError(message, etc) 164 | -- 165 | -- which you can override in your local JSON object. 166 | -- 167 | -- The 'etc' in the error call is the second argument to encode() 168 | -- and encode_pretty(), or nil if it wasn't provided. 169 | -- 170 | -- 171 | -- PRETTY-PRINTING 172 | -- 173 | -- An optional third argument, a table of options, allows a bit of 174 | -- configuration about how the encoding takes place: 175 | -- 176 | -- pretty = JSON:encode(val, etc, { 177 | -- pretty = true, -- if false, no other options matter 178 | -- indent = " ", -- this provides for a three-space indent per nesting level 179 | -- align_keys = false, -- see below 180 | -- }) 181 | -- 182 | -- encode() and encode_pretty() are identical except that encode_pretty() 183 | -- provides a default options table if none given in the call: 184 | -- 185 | -- { pretty = true, align_keys = false, indent = " " } 186 | -- 187 | -- For example, if 188 | -- 189 | -- JSON:encode(data) 190 | -- 191 | -- produces: 192 | -- 193 | -- {"city":"Kyoto","climate":{"avg_temp":16,"humidity":"high","snowfall":"minimal"},"country":"Japan","wards":11} 194 | -- 195 | -- then 196 | -- 197 | -- JSON:encode_pretty(data) 198 | -- 199 | -- produces: 200 | -- 201 | -- { 202 | -- "city": "Kyoto", 203 | -- "climate": { 204 | -- "avg_temp": 16, 205 | -- "humidity": "high", 206 | -- "snowfall": "minimal" 207 | -- }, 208 | -- "country": "Japan", 209 | -- "wards": 11 210 | -- } 211 | -- 212 | -- The following three lines return identical results: 213 | -- JSON:encode_pretty(data) 214 | -- JSON:encode_pretty(data, nil, { pretty = true, align_keys = false, indent = " " }) 215 | -- JSON:encode (data, nil, { pretty = true, align_keys = false, indent = " " }) 216 | -- 217 | -- An example of setting your own indent string: 218 | -- 219 | -- JSON:encode_pretty(data, nil, { pretty = true, indent = "| " }) 220 | -- 221 | -- produces: 222 | -- 223 | -- { 224 | -- | "city": "Kyoto", 225 | -- | "climate": { 226 | -- | | "avg_temp": 16, 227 | -- | | "humidity": "high", 228 | -- | | "snowfall": "minimal" 229 | -- | }, 230 | -- | "country": "Japan", 231 | -- | "wards": 11 232 | -- } 233 | -- 234 | -- An example of setting align_keys to true: 235 | -- 236 | -- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = true }) 237 | -- 238 | -- produces: 239 | -- 240 | -- { 241 | -- "city": "Kyoto", 242 | -- "climate": { 243 | -- "avg_temp": 16, 244 | -- "humidity": "high", 245 | -- "snowfall": "minimal" 246 | -- }, 247 | -- "country": "Japan", 248 | -- "wards": 11 249 | -- } 250 | -- 251 | -- which I must admit is kinda ugly, sorry. This was the default for 252 | -- encode_pretty() prior to version 20141223.14. 253 | -- 254 | -- 255 | -- AMBIGUOUS SITUATIONS DURING THE ENCODING 256 | -- 257 | -- During the encode, if a Lua table being encoded contains both string 258 | -- and numeric keys, it fits neither JSON's idea of an object, nor its 259 | -- idea of an array. To get around this, when any string key exists (or 260 | -- when non-positive numeric keys exist), numeric keys are converted to 261 | -- strings. 262 | -- 263 | -- For example, 264 | -- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) 265 | -- produces the JSON object 266 | -- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} 267 | -- 268 | -- To prohibit this conversion and instead make it an error condition, set 269 | -- JSON.noKeyConversion = true 270 | -- 271 | 272 | 273 | 274 | 275 | -- 276 | -- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT 277 | -- 278 | -- assert 279 | -- onDecodeError 280 | -- onDecodeOfNilError 281 | -- onDecodeOfHTMLError 282 | -- onEncodeError 283 | -- 284 | -- If you want to create a separate Lua JSON object with its own error handlers, 285 | -- you can reload JSON.lua or use the :new() method. 286 | -- 287 | --------------------------------------------------------------------------- 288 | 289 | local default_pretty_indent = " " 290 | local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent } 291 | 292 | local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray 293 | local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject 294 | 295 | 296 | function OBJDEF:newArray(tbl) 297 | return setmetatable(tbl or {}, isArray) 298 | end 299 | 300 | function OBJDEF:newObject(tbl) 301 | return setmetatable(tbl or {}, isObject) 302 | end 303 | 304 | local function unicode_codepoint_as_utf8(codepoint) 305 | -- 306 | -- codepoint is a number 307 | -- 308 | if codepoint <= 127 then 309 | return string.char(codepoint) 310 | 311 | elseif codepoint <= 2047 then 312 | -- 313 | -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 314 | -- 315 | local highpart = math.floor(codepoint / 0x40) 316 | local lowpart = codepoint - (0x40 * highpart) 317 | return string.char(0xC0 + highpart, 318 | 0x80 + lowpart) 319 | 320 | elseif codepoint <= 65535 then 321 | -- 322 | -- 1110yyyy 10yyyyxx 10xxxxxx 323 | -- 324 | local highpart = math.floor(codepoint / 0x1000) 325 | local remainder = codepoint - 0x1000 * highpart 326 | local midpart = math.floor(remainder / 0x40) 327 | local lowpart = remainder - 0x40 * midpart 328 | 329 | highpart = 0xE0 + highpart 330 | midpart = 0x80 + midpart 331 | lowpart = 0x80 + lowpart 332 | 333 | -- 334 | -- Check for an invalid character (thanks Andy R. at Adobe). 335 | -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 336 | -- 337 | if ( highpart == 0xE0 and midpart < 0xA0 ) or 338 | ( highpart == 0xED and midpart > 0x9F ) or 339 | ( highpart == 0xF0 and midpart < 0x90 ) or 340 | ( highpart == 0xF4 and midpart > 0x8F ) 341 | then 342 | return "?" 343 | else 344 | return string.char(highpart, 345 | midpart, 346 | lowpart) 347 | end 348 | 349 | else 350 | -- 351 | -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx 352 | -- 353 | local highpart = math.floor(codepoint / 0x40000) 354 | local remainder = codepoint - 0x40000 * highpart 355 | local midA = math.floor(remainder / 0x1000) 356 | remainder = remainder - 0x1000 * midA 357 | local midB = math.floor(remainder / 0x40) 358 | local lowpart = remainder - 0x40 * midB 359 | 360 | return string.char(0xF0 + highpart, 361 | 0x80 + midA, 362 | 0x80 + midB, 363 | 0x80 + lowpart) 364 | end 365 | end 366 | 367 | function OBJDEF:onDecodeError(message, text, location, etc) 368 | if text then 369 | if location then 370 | message = string.format("%s at char %d of: %s", message, location, text) 371 | else 372 | message = string.format("%s: %s", message, text) 373 | end 374 | end 375 | 376 | if etc ~= nil then 377 | message = message .. " (" .. OBJDEF:encode(etc) .. ")" 378 | end 379 | 380 | if self.assert then 381 | self.assert(false, message) 382 | else 383 | assert(false, message) 384 | end 385 | end 386 | 387 | OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError 388 | OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError 389 | 390 | function OBJDEF:onEncodeError(message, etc) 391 | if etc ~= nil then 392 | message = message .. " (" .. OBJDEF:encode(etc) .. ")" 393 | end 394 | 395 | if self.assert then 396 | self.assert(false, message) 397 | else 398 | assert(false, message) 399 | end 400 | end 401 | 402 | local function grok_number(self, text, start, etc) 403 | -- 404 | -- Grab the integer part 405 | -- 406 | local integer_part = text:match('^-?[1-9]%d*', start) 407 | or text:match("^-?0", start) 408 | 409 | if not integer_part then 410 | self:onDecodeError("expected number", text, start, etc) 411 | end 412 | 413 | local i = start + integer_part:len() 414 | 415 | -- 416 | -- Grab an optional decimal part 417 | -- 418 | local decimal_part = text:match('^%.%d+', i) or "" 419 | 420 | i = i + decimal_part:len() 421 | 422 | -- 423 | -- Grab an optional exponential part 424 | -- 425 | local exponent_part = text:match('^[eE][-+]?%d+', i) or "" 426 | 427 | i = i + exponent_part:len() 428 | 429 | local full_number_text = integer_part .. decimal_part .. exponent_part 430 | local as_number = tonumber(full_number_text) 431 | 432 | if not as_number then 433 | self:onDecodeError("bad number", text, start, etc) 434 | end 435 | 436 | return as_number, i 437 | end 438 | 439 | 440 | local function grok_string(self, text, start, etc) 441 | 442 | if text:sub(start,start) ~= '"' then 443 | self:onDecodeError("expected string's opening quote", text, start, etc) 444 | end 445 | 446 | local i = start + 1 -- +1 to bypass the initial quote 447 | local text_len = text:len() 448 | local VALUE = "" 449 | while i <= text_len do 450 | local c = text:sub(i,i) 451 | if c == '"' then 452 | return VALUE, i + 1 453 | end 454 | if c ~= '\\' then 455 | VALUE = VALUE .. c 456 | i = i + 1 457 | elseif text:match('^\\b', i) then 458 | VALUE = VALUE .. "\b" 459 | i = i + 2 460 | elseif text:match('^\\f', i) then 461 | VALUE = VALUE .. "\f" 462 | i = i + 2 463 | elseif text:match('^\\n', i) then 464 | VALUE = VALUE .. "\n" 465 | i = i + 2 466 | elseif text:match('^\\r', i) then 467 | VALUE = VALUE .. "\r" 468 | i = i + 2 469 | elseif text:match('^\\t', i) then 470 | VALUE = VALUE .. "\t" 471 | i = i + 2 472 | else 473 | local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) 474 | if hex then 475 | i = i + 6 -- bypass what we just read 476 | 477 | -- We have a Unicode codepoint. It could be standalone, or if in the proper range and 478 | -- followed by another in a specific range, it'll be a two-code surrogate pair. 479 | local codepoint = tonumber(hex, 16) 480 | if codepoint >= 0xD800 and codepoint <= 0xDBFF then 481 | -- it's a hi surrogate... see whether we have a following low 482 | local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) 483 | if lo_surrogate then 484 | i = i + 6 -- bypass the low surrogate we just read 485 | codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) 486 | else 487 | -- not a proper low, so we'll just leave the first codepoint as is and spit it out. 488 | end 489 | end 490 | VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) 491 | 492 | else 493 | 494 | -- just pass through what's escaped 495 | VALUE = VALUE .. text:match('^\\(.)', i) 496 | i = i + 2 497 | end 498 | end 499 | end 500 | 501 | self:onDecodeError("unclosed string", text, start, etc) 502 | end 503 | 504 | local function skip_whitespace(text, start) 505 | 506 | local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2 507 | if match_end then 508 | return match_end + 1 509 | else 510 | return start 511 | end 512 | end 513 | 514 | local grok_one -- assigned later 515 | 516 | local function grok_object(self, text, start, etc) 517 | if text:sub(start,start) ~= '{' then 518 | self:onDecodeError("expected '{'", text, start, etc) 519 | end 520 | 521 | local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' 522 | 523 | local VALUE = self.strictTypes and self:newObject { } or { } 524 | 525 | if text:sub(i,i) == '}' then 526 | return VALUE, i + 1 527 | end 528 | local text_len = text:len() 529 | while i <= text_len do 530 | local key, new_i = grok_string(self, text, i, etc) 531 | 532 | i = skip_whitespace(text, new_i) 533 | 534 | if text:sub(i, i) ~= ':' then 535 | self:onDecodeError("expected colon", text, i, etc) 536 | end 537 | 538 | i = skip_whitespace(text, i + 1) 539 | 540 | local new_val, new_i = grok_one(self, text, i) 541 | 542 | VALUE[key] = new_val 543 | 544 | -- 545 | -- Expect now either '}' to end things, or a ',' to allow us to continue. 546 | -- 547 | i = skip_whitespace(text, new_i) 548 | 549 | local c = text:sub(i,i) 550 | 551 | if c == '}' then 552 | return VALUE, i + 1 553 | end 554 | 555 | if text:sub(i, i) ~= ',' then 556 | self:onDecodeError("expected comma or '}'", text, i, etc) 557 | end 558 | 559 | i = skip_whitespace(text, i + 1) 560 | end 561 | 562 | self:onDecodeError("unclosed '{'", text, start, etc) 563 | end 564 | 565 | local function grok_array(self, text, start, etc) 566 | if text:sub(start,start) ~= '[' then 567 | self:onDecodeError("expected '['", text, start, etc) 568 | end 569 | 570 | local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' 571 | local VALUE = self.strictTypes and self:newArray { } or { } 572 | if text:sub(i,i) == ']' then 573 | return VALUE, i + 1 574 | end 575 | 576 | local VALUE_INDEX = 1 577 | 578 | local text_len = text:len() 579 | while i <= text_len do 580 | local val, new_i = grok_one(self, text, i) 581 | 582 | -- can't table.insert(VALUE, val) here because it's a no-op if val is nil 583 | VALUE[VALUE_INDEX] = val 584 | VALUE_INDEX = VALUE_INDEX + 1 585 | 586 | i = skip_whitespace(text, new_i) 587 | 588 | -- 589 | -- Expect now either ']' to end things, or a ',' to allow us to continue. 590 | -- 591 | local c = text:sub(i,i) 592 | if c == ']' then 593 | return VALUE, i + 1 594 | end 595 | if text:sub(i, i) ~= ',' then 596 | self:onDecodeError("expected comma or '['", text, i, etc) 597 | end 598 | i = skip_whitespace(text, i + 1) 599 | end 600 | self:onDecodeError("unclosed '['", text, start, etc) 601 | end 602 | 603 | 604 | grok_one = function(self, text, start, etc) 605 | -- Skip any whitespace 606 | start = skip_whitespace(text, start) 607 | 608 | if start > text:len() then 609 | self:onDecodeError("unexpected end of string", text, nil, etc) 610 | end 611 | 612 | if text:find('^"', start) then 613 | return grok_string(self, text, start, etc) 614 | 615 | elseif text:find('^[-0123456789 ]', start) then 616 | return grok_number(self, text, start, etc) 617 | 618 | elseif text:find('^%{', start) then 619 | return grok_object(self, text, start, etc) 620 | 621 | elseif text:find('^%[', start) then 622 | return grok_array(self, text, start, etc) 623 | 624 | elseif text:find('^true', start) then 625 | return true, start + 4 626 | 627 | elseif text:find('^false', start) then 628 | return false, start + 5 629 | 630 | elseif text:find('^null', start) then 631 | return nil, start + 4 632 | 633 | else 634 | self:onDecodeError("can't parse JSON", text, start, etc) 635 | end 636 | end 637 | 638 | function OBJDEF:decode(text, etc) 639 | if type(self) ~= 'table' or self.__index ~= OBJDEF then 640 | OBJDEF:onDecodeError("JSON:decode must be called in method format", nil, nil, etc) 641 | end 642 | 643 | if text == nil then 644 | self:onDecodeOfNilError(string.format("nil passed to JSON:decode()"), nil, nil, etc) 645 | elseif type(text) ~= 'string' then 646 | self:onDecodeError(string.format("expected string argument to JSON:decode(), got %s", type(text)), nil, nil, etc) 647 | end 648 | 649 | if text:match('^%s*$') then 650 | return nil 651 | end 652 | 653 | if text:match('^%s*<') then 654 | -- Can't be JSON... we'll assume it's HTML 655 | self:onDecodeOfHTMLError(string.format("html passed to JSON:decode()"), text, nil, etc) 656 | end 657 | 658 | -- 659 | -- Ensure that it's not UTF-32 or UTF-16. 660 | -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), 661 | -- but this package can't handle them. 662 | -- 663 | if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then 664 | self:onDecodeError("JSON package groks only UTF-8, sorry", text, nil, etc) 665 | end 666 | 667 | local success, value = pcall(grok_one, self, text, 1, etc) 668 | 669 | if success then 670 | return value 671 | else 672 | -- if JSON:onDecodeError() didn't abort out of the pcall, we'll have received the error message here as "value", so pass it along as an assert. 673 | if self.assert then 674 | self.assert(false, value) 675 | else 676 | assert(false, value) 677 | end 678 | -- and if we're still here, return a nil and throw the error message on as a second arg 679 | return nil, value 680 | end 681 | end 682 | 683 | local function backslash_replacement_function(c) 684 | if c == "\n" then 685 | return "\\n" 686 | elseif c == "\r" then 687 | return "\\r" 688 | elseif c == "\t" then 689 | return "\\t" 690 | elseif c == "\b" then 691 | return "\\b" 692 | elseif c == "\f" then 693 | return "\\f" 694 | elseif c == '"' then 695 | return '\\"' 696 | elseif c == '\\' then 697 | return '\\\\' 698 | else 699 | return string.format("\\u%04x", c:byte()) 700 | end 701 | end 702 | 703 | local chars_to_be_escaped_in_JSON_string 704 | = '[' 705 | .. '"' -- class sub-pattern to match a double quote 706 | .. '%\\' -- class sub-pattern to match a backslash 707 | .. '%z' -- class sub-pattern to match a null 708 | .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters 709 | .. ']' 710 | 711 | local function json_string_literal(value) 712 | local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function) 713 | return '"' .. newval .. '"' 714 | end 715 | 716 | local function object_or_array(self, T, etc) 717 | -- 718 | -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON 719 | -- object. If there are only numbers, it's a JSON array. 720 | -- 721 | -- If we'll be converting to a JSON object, we'll want to sort the keys so that the 722 | -- end result is deterministic. 723 | -- 724 | local string_keys = { } 725 | local number_keys = { } 726 | local number_keys_must_be_strings = false 727 | local maximum_number_key 728 | 729 | for key in pairs(T) do 730 | if type(key) == 'string' then 731 | table.insert(string_keys, key) 732 | elseif type(key) == 'number' then 733 | table.insert(number_keys, key) 734 | if key <= 0 or key >= math.huge then 735 | number_keys_must_be_strings = true 736 | elseif not maximum_number_key or key > maximum_number_key then 737 | maximum_number_key = key 738 | end 739 | else 740 | self:onEncodeError("can't encode table with a key of type " .. type(key), etc) 741 | end 742 | end 743 | 744 | if #string_keys == 0 and not number_keys_must_be_strings then 745 | -- 746 | -- An empty table, or a numeric-only array 747 | -- 748 | if #number_keys > 0 then 749 | return nil, maximum_number_key -- an array 750 | elseif tostring(T) == "JSON array" then 751 | return nil 752 | elseif tostring(T) == "JSON object" then 753 | return { } 754 | else 755 | -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects 756 | return nil 757 | end 758 | end 759 | 760 | table.sort(string_keys) 761 | 762 | local map 763 | if #number_keys > 0 then 764 | -- 765 | -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array 766 | -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object. 767 | -- 768 | 769 | if self.noKeyConversion then 770 | self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc) 771 | end 772 | 773 | -- 774 | -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings 775 | -- 776 | map = { } 777 | for key, val in pairs(T) do 778 | map[key] = val 779 | end 780 | 781 | table.sort(number_keys) 782 | 783 | -- 784 | -- Throw numeric keys in there as strings 785 | -- 786 | for _, number_key in ipairs(number_keys) do 787 | local string_key = tostring(number_key) 788 | if map[string_key] == nil then 789 | table.insert(string_keys , string_key) 790 | map[string_key] = T[number_key] 791 | else 792 | self:onEncodeError("conflict converting table with mixed-type keys into a JSON object: key " .. number_key .. " exists both as a string and a number.", etc) 793 | end 794 | end 795 | end 796 | 797 | return string_keys, nil, map 798 | end 799 | 800 | -- 801 | -- Encode 802 | -- 803 | -- 'options' is nil, or a table with possible keys: 804 | -- pretty -- if true, return a pretty-printed version 805 | -- indent -- a string (usually of spaces) used to indent each nested level 806 | -- align_keys -- if true, align all the keys when formatting a table 807 | -- 808 | local encode_value -- must predeclare because it calls itself 809 | function encode_value(self, value, parents, etc, options, indent) 810 | 811 | if value == nil then 812 | return 'null' 813 | 814 | elseif type(value) == 'string' then 815 | return json_string_literal(value) 816 | 817 | elseif type(value) == 'number' then 818 | if value ~= value then 819 | -- 820 | -- NaN (Not a Number). 821 | -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option. 822 | -- 823 | return "null" 824 | elseif value >= math.huge then 825 | -- 826 | -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should 827 | -- really be a package option. Note: at least with some implementations, positive infinity 828 | -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is. 829 | -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">=" 830 | -- case first. 831 | -- 832 | return "1e+9999" 833 | elseif value <= -math.huge then 834 | -- 835 | -- Negative infinity. 836 | -- JSON has no INF, so we have to fudge the best we can. This should really be a package option. 837 | -- 838 | return "-1e+9999" 839 | else 840 | return tostring(value) 841 | end 842 | 843 | elseif type(value) == 'boolean' then 844 | return tostring(value) 845 | 846 | elseif type(value) == 'function' then 847 | return 'null' 848 | 849 | elseif type(value) ~= 'table' then 850 | self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) 851 | 852 | else 853 | -- 854 | -- A table to be converted to either a JSON object or array. 855 | -- 856 | local T = value 857 | 858 | if type(options) ~= 'table' then 859 | options = {} 860 | end 861 | if type(indent) ~= 'string' then 862 | indent = "" 863 | end 864 | 865 | if parents[T] then 866 | self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) 867 | else 868 | parents[T] = true 869 | end 870 | 871 | local result_value 872 | 873 | local object_keys, maximum_number_key, map = object_or_array(self, T, etc) 874 | if maximum_number_key then 875 | -- 876 | -- An array... 877 | -- 878 | local ITEMS = { } 879 | for i = 1, maximum_number_key do 880 | table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent)) 881 | end 882 | 883 | if options.pretty then 884 | result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]" 885 | else 886 | result_value = "[" .. table.concat(ITEMS, ",") .. "]" 887 | end 888 | 889 | elseif object_keys then 890 | -- 891 | -- An object 892 | -- 893 | local TT = map or T 894 | 895 | if options.pretty then 896 | 897 | local KEYS = { } 898 | local max_key_length = 0 899 | for _, key in ipairs(object_keys) do 900 | local encoded = encode_value(self, tostring(key), parents, etc, options, indent) 901 | if options.align_keys then 902 | max_key_length = math.max(max_key_length, #encoded) 903 | end 904 | table.insert(KEYS, encoded) 905 | end 906 | local key_indent = indent .. tostring(options.indent or "") 907 | local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "") 908 | local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s" 909 | 910 | local COMBINED_PARTS = { } 911 | for i, key in ipairs(object_keys) do 912 | local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent) 913 | table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val)) 914 | end 915 | result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}" 916 | 917 | else 918 | 919 | local PARTS = { } 920 | for _, key in ipairs(object_keys) do 921 | local encoded_val = encode_value(self, TT[key], parents, etc, options, indent) 922 | local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent) 923 | table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val)) 924 | end 925 | result_value = "{" .. table.concat(PARTS, ",") .. "}" 926 | 927 | end 928 | else 929 | -- 930 | -- An empty array/object... we'll treat it as an array, though it should really be an option 931 | -- 932 | result_value = "[]" 933 | end 934 | 935 | parents[T] = false 936 | return result_value 937 | end 938 | end 939 | 940 | 941 | function OBJDEF:encode(value, etc, options) 942 | if type(self) ~= 'table' or self.__index ~= OBJDEF then 943 | OBJDEF:onEncodeError("JSON:encode must be called in method format", etc) 944 | end 945 | return encode_value(self, value, {}, etc, options or nil) 946 | end 947 | 948 | function OBJDEF:encode_pretty(value, etc, options) 949 | if type(self) ~= 'table' or self.__index ~= OBJDEF then 950 | OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc) 951 | end 952 | return encode_value(self, value, {}, etc, options or default_pretty_options) 953 | end 954 | 955 | function OBJDEF.__tostring() 956 | return "JSON encode/decode package" 957 | end 958 | 959 | OBJDEF.__index = OBJDEF 960 | 961 | function OBJDEF:new(args) 962 | local new = { } 963 | 964 | if args then 965 | for key, val in pairs(args) do 966 | new[key] = val 967 | end 968 | end 969 | 970 | return setmetatable(new, OBJDEF) 971 | end 972 | 973 | return OBJDEF:new() 974 | 975 | -- 976 | -- Version history: 977 | -- 978 | -- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really 979 | -- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines 980 | -- more flexible, and changed the default encode_pretty() to be more generally useful. 981 | -- 982 | -- Added a third 'options' argument to the encode() and encode_pretty() routines, to control 983 | -- how the encoding takes place. 984 | -- 985 | -- Updated docs to add assert() call to the loadfile() line, just as good practice so that 986 | -- if there is a problem loading JSON.lua, the appropriate error message will percolate up. 987 | -- 988 | -- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string, 989 | -- so that the source of the package, and its version number, are visible in compiled copies. 990 | -- 991 | -- 20140911.12 Minor lua cleanup. 992 | -- Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'. 993 | -- (Thanks to SmugMug's David Parry for these.) 994 | -- 995 | -- 20140418.11 JSON nulls embedded within an array were being ignored, such that 996 | -- ["1",null,null,null,null,null,"seven"], 997 | -- would return 998 | -- {1,"seven"} 999 | -- It's now fixed to properly return 1000 | -- {1, nil, nil, nil, nil, nil, "seven"} 1001 | -- Thanks to "haddock" for catching the error. 1002 | -- 1003 | -- 20140116.10 The user's JSON.assert() wasn't always being used. Thanks to "blue" for the heads up. 1004 | -- 1005 | -- 20131118.9 Update for Lua 5.3... it seems that tostring(2/1) produces "2.0" instead of "2", 1006 | -- and this caused some problems. 1007 | -- 1008 | -- 20131031.8 Unified the code for encode() and encode_pretty(); they had been stupidly separate, 1009 | -- and had of course diverged (encode_pretty didn't get the fixes that encode got, so 1010 | -- sometimes produced incorrect results; thanks to Mattie for the heads up). 1011 | -- 1012 | -- Handle encoding tables with non-positive numeric keys (unlikely, but possible). 1013 | -- 1014 | -- If a table has both numeric and string keys, or its numeric keys are inappropriate 1015 | -- (such as being non-positive or infinite), the numeric keys are turned into 1016 | -- string keys appropriate for a JSON object. So, as before, 1017 | -- JSON:encode({ "one", "two", "three" }) 1018 | -- produces the array 1019 | -- ["one","two","three"] 1020 | -- but now something with mixed key types like 1021 | -- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) 1022 | -- instead of throwing an error produces an object: 1023 | -- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} 1024 | -- 1025 | -- To maintain the prior throw-an-error semantics, set 1026 | -- JSON.noKeyConversion = true 1027 | -- 1028 | -- 20131004.7 Release under a Creative Commons CC-BY license, which I should have done from day one, sorry. 1029 | -- 1030 | -- 20130120.6 Comment update: added a link to the specific page on my blog where this code can 1031 | -- be found, so that folks who come across the code outside of my blog can find updates 1032 | -- more easily. 1033 | -- 1034 | -- 20111207.5 Added support for the 'etc' arguments, for better error reporting. 1035 | -- 1036 | -- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent. 1037 | -- 1038 | -- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules: 1039 | -- 1040 | -- * When encoding lua for JSON, Sparse numeric arrays are now handled by 1041 | -- spitting out full arrays, such that 1042 | -- JSON:encode({"one", "two", [10] = "ten"}) 1043 | -- returns 1044 | -- ["one","two",null,null,null,null,null,null,null,"ten"] 1045 | -- 1046 | -- In 20100810.2 and earlier, only up to the first non-null value would have been retained. 1047 | -- 1048 | -- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999". 1049 | -- Version 20100810.2 and earlier created invalid JSON in both cases. 1050 | -- 1051 | -- * Unicode surrogate pairs are now detected when decoding JSON. 1052 | -- 1053 | -- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding 1054 | -- 1055 | -- 20100731.1 initial public release 1056 | -- 1057 | -------------------------------------------------------------------------------- /bblibs/StrUtilsAPI.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2012 Thomas Farr a.k.a tomass1996 [farr.thomas@gmail.com] 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 5 | associated documentation files (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | copies of the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | -Visible credit is given to the original author. 12 | -The software is distributed in a non-profit way. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 15 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 17 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | --]] 19 | 20 | local floor,modf, insert = math.floor,math.modf, table.insert 21 | local char,format,rep = string.char,string.format,string.rep 22 | 23 | local function basen(n,b) 24 | if n < 0 then 25 | n = -n 26 | end 27 | local t = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~" 28 | if n < b then 29 | local ret = "" 30 | ret = ret..string.sub(t, (n%b)+1,(n%b)+1) 31 | return ret 32 | else 33 | local tob = tostring(basen(math.floor(n/b), b)) 34 | local ret = tob..t:sub((n%b)+1,(n%b)+1) 35 | return ret 36 | end 37 | end 38 | 39 | local Base64 = {} 40 | Base64["lsh"] = function(value,shift) 41 | return (value*(2^shift)) % 256 42 | end 43 | Base64["rsh"] = function(value,shift) 44 | return math.floor(value/2^shift) % 256 45 | end 46 | Base64["bit"] = function(x,b) 47 | return (x % 2^b - x % 2^(b-1) > 0) 48 | end 49 | Base64["lor"] = function(x,y) 50 | local result = 0 51 | for p=1,8 do result = result + (((Base64.bit(x,p) or Base64.bit(y,p)) == true) and 2^(p-1) or 0) end 52 | return result 53 | end 54 | Base64["base64chars"] = { 55 | [0]='A',[1]='B',[2]='C',[3]='D',[4]='E',[5]='F',[6]='G',[7]='H',[8]='I',[9]='J',[10]='K', 56 | [11]='L',[12]='M',[13]='N',[14]='O',[15]='P',[16]='Q',[17]='R',[18]='S',[19]='T',[20]='U', 57 | [21]='V',[22]='W',[23]='X',[24]='Y',[25]='Z',[26]='a',[27]='b',[28]='c',[29]='d',[30]='e', 58 | [31]='f',[32]='g',[33]='h',[34]='i',[35]='j',[36]='k',[37]='l',[38]='m',[39]='n',[40]='o', 59 | [41]='p',[42]='q',[43]='r',[44]='s',[45]='t',[46]='u',[47]='v',[48]='w',[49]='x',[50]='y', 60 | [51]='z',[52]='0',[53]='1',[54]='2',[55]='3',[56]='4',[57]='5',[58]='6',[59]='7',[60]='8', 61 | [61]='9',[62]='-',[63]='_'} 62 | Base64["base64bytes"] = { 63 | ['A']=0,['B']=1,['C']=2,['D']=3,['E']=4,['F']=5,['G']=6,['H']=7,['I']=8,['J']=9,['K']=10, 64 | ['L']=11,['M']=12,['N']=13,['O']=14,['P']=15,['Q']=16,['R']=17,['S']=18,['T']=19,['U']=20, 65 | ['V']=21,['W']=22,['X']=23,['Y']=24,['Z']=25,['a']=26,['b']=27,['c']=28,['d']=29,['e']=30, 66 | ['f']=31,['g']=32,['h']=33,['i']=34,['j']=35,['k']=36,['l']=37,['m']=38,['n']=39,['o']=40, 67 | ['p']=41,['q']=42,['r']=43,['s']=44,['t']=45,['u']=46,['v']=47,['w']=48,['x']=49,['y']=50, 68 | ['z']=51,['0']=52,['1']=53,['2']=54,['3']=55,['4']=56,['5']=57,['6']=58,['7']=59,['8']=60, 69 | ['9']=61,['-']=62,['_']=63,['=']=nil} 70 | 71 | local base32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" 72 | 73 | local tSHA1 = {} 74 | tSHA1["bytes_to_w32"] = function(a,b,c,d) return a*0x1000000+b*0x10000+c*0x100+d end 75 | tSHA1["w32_to_bytes"] = function(i) return floor(i/0x1000000)%0x100,floor(i/0x10000)%0x100,floor(i/0x100)%0x100,i%0x100 end 76 | tSHA1["w32_rot"] = function(bits,a) 77 | local b2 = 2^(32-bits) 78 | local a,b = modf(a/b2) 79 | return a+b*b2*(2^(bits)) 80 | end 81 | tSHA1["byte_to_bits"] = function(b) 82 | local b = function (n) 83 | local b = floor(b/n) 84 | return b%2==1 85 | end 86 | return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128) 87 | end 88 | tSHA1["bits_to_byte"] = function(a,b,c,d,e,f,g,h) 89 | local function n(b,x) return b and x or 0 end 90 | return n(a,1)+n(b,2)+n(c,4)+n(d,8)+n(e,16)+n(f,32)+n(g,64)+n(h,128) 91 | end 92 | tSHA1["bits_to_string"] = function(a,b,c,d,e,f,g,h) 93 | local function x(b) return b and "1" or "0" end 94 | return ("%s%s%s%s %s%s%s%s"):format(x(a),x(b),x(c),x(d),x(e),x(f),x(g),x(h)) 95 | end 96 | tSHA1["byte_to_bit_string"] = function(b) return tSHA1.bits_to_string(byte_to_bits(b)) end 97 | tSHA1["w32_to_bit_string"] = function(a) 98 | if type(a) == "string" then return a end 99 | local aa,ab,ac,ad = tSHA1.w32_to_bytes(a) 100 | local s = tSHA1.byte_to_bit_string 101 | return ("%s %s %s %s"):format(s(aa):reverse(),s(ab):reverse(),s(ac):reverse(),s(ad):reverse()):reverse() 102 | end 103 | tSHA1["band"] = function(a,b) 104 | local A,B,C,D,E,F,G,H = tSHA1.byte_to_bits(b) 105 | local a,b,c,d,e,f,g,h = tSHA1.byte_to_bits(a) 106 | return tSHA1.bits_to_byte( 107 | A and a, B and b, C and c, D and d, 108 | E and e, F and f, G and g, H and h) 109 | end 110 | tSHA1["bor"] = function(a,b) 111 | local A,B,C,D,E,F,G,H = tSHA1.byte_to_bits(b) 112 | local a,b,c,d,e,f,g,h = tSHA1.byte_to_bits(a) 113 | return tSHA1.bits_to_byte( 114 | A or a, B or b, C or c, D or d, 115 | E or e, F or f, G or g, H or h) 116 | end 117 | tSHA1["bxor"] = function(a,b) 118 | local A,B,C,D,E,F,G,H = tSHA1.byte_to_bits(b) 119 | local a,b,c,d,e,f,g,h = tSHA1.byte_to_bits(a) 120 | return tSHA1.bits_to_byte( 121 | A ~= a, B ~= b, C ~= c, D ~= d, 122 | E ~= e, F ~= f, G ~= g, H ~= h) 123 | end 124 | tSHA1["bnot"] = function(x) return 255-(x % 256) end 125 | tSHA1["w32_comb"] = function(fn) 126 | return function (a,b) 127 | local aa,ab,ac,ad = tSHA1.w32_to_bytes(a) 128 | local ba,bb,bc,bd = tSHA1.w32_to_bytes(b) 129 | return tSHA1.bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd)) 130 | end 131 | end 132 | tSHA1["w32_xor_n"] = function(a,...) 133 | local aa,ab,ac,ad = tSHA1.w32_to_bytes(a) 134 | for i=1,select('#',...) do 135 | local ba,bb,bc,bd = tSHA1.w32_to_bytes(select(i,...)) 136 | aa,ab,ac,ad = tSHA1.bxor(aa,ba),tSHA1.bxor(ab,bb),tSHA1.bxor(ac,bc),tSHA1.bxor(ad,bd) 137 | end 138 | return tSHA1.bytes_to_w32(aa,ab,ac,ad) 139 | end 140 | tSHA1["w32_or3"] = function(a,b,c) 141 | local aa,ab,ac,ad = tSHA1.w32_to_bytes(a) 142 | local ba,bb,bc,bd = tSHA1.w32_to_bytes(b) 143 | local ca,cb,cc,cd = tSHA1.w32_to_bytes(c) 144 | return tSHA1.bytes_to_w32( 145 | tSHA1.bor(aa,tSHA1.bor(ba,ca)), tSHA1.bor(ab,tSHA1.bor(bb,cb)), tSHA1.bor(ac,tSHA1.bor(bc,cc)), tSHA1.bor(ad,tSHA1.bor(bd,cd)) 146 | ) 147 | end 148 | tSHA1["w32_not"] = function(a) return 4294967295-(a % 4294967296) end 149 | tSHA1["w32_add"] = function(a,b) return (a+b) % 4294967296 end 150 | tSHA1["w32_add_n"] = function(a,...) 151 | for i=1,select('#',...) do 152 | a = (a+select(i,...)) % 4294967296 153 | end 154 | return a 155 | end 156 | tSHA1["w32_to_hexstring"] = function(w) return format("%08x",w) end 157 | tSHA1["w32_and"] = tSHA1.w32_comb(tSHA1.band) 158 | tSHA1["w32_xor"] = tSHA1.w32_comb(tSHA1.bxor) 159 | tSHA1["w32_or"] = tSHA1.w32_comb(tSHA1.bor) 160 | 161 | local CRC = {} 162 | CRC.crc32 = { 163 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 164 | 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 165 | 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 166 | 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 167 | 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 168 | 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 169 | 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 170 | 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 171 | 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 172 | 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 173 | 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 174 | 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 175 | 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 176 | 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 177 | 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 178 | 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 179 | 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 180 | 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 181 | 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 182 | 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 183 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 184 | 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 185 | 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 186 | 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 187 | 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 188 | 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 189 | 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 190 | 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 191 | 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 192 | 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 193 | 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 194 | 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 195 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 196 | 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 197 | 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 198 | 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 199 | 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 200 | 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 201 | 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 202 | 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 203 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 204 | 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 205 | 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D } 206 | 207 | local bit = {} 208 | bit["bnot"] = function(n) 209 | local tbl = bit.tobits(n) 210 | local size = math.max(table.getn(tbl), 32) 211 | for i = 1, size do 212 | if(tbl[i] == 1) then 213 | tbl[i] = 0 214 | else 215 | tbl[i] = 1 216 | end 217 | end 218 | return bit.tonumb(tbl) 219 | end 220 | bit["band"] = function(m, n) 221 | local tbl_m = bit.tobits(m) 222 | local tbl_n = bit.tobits(n) 223 | bit.expand(tbl_m, tbl_n) 224 | local tbl = {} 225 | local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) 226 | for i = 1, rslt do 227 | if(tbl_m[i]== 0 or tbl_n[i] == 0) then 228 | tbl[i] = 0 229 | else 230 | tbl[i] = 1 231 | end 232 | end 233 | return bit.tonumb(tbl) 234 | end 235 | bit["bor"] = function(m, n) 236 | local tbl_m = bit.tobits(m) 237 | local tbl_n = bit.tobits(n) 238 | bit.expand(tbl_m, tbl_n) 239 | local tbl = {} 240 | local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) 241 | for i = 1, rslt do 242 | if(tbl_m[i]== 0 and tbl_n[i] == 0) then 243 | tbl[i] = 0 244 | else 245 | tbl[i] = 1 246 | end 247 | end 248 | return bit.tonumb(tbl) 249 | end 250 | bit["bxor"] = function(m, n) 251 | local tbl_m = bit.tobits(m) 252 | local tbl_n = bit.tobits(n) 253 | bit.expand(tbl_m, tbl_n) 254 | local tbl = {} 255 | local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) 256 | for i = 1, rslt do 257 | if(tbl_m[i] ~= tbl_n[i]) then 258 | tbl[i] = 1 259 | else 260 | tbl[i] = 0 261 | end 262 | end 263 | return bit.tonumb(tbl) 264 | end 265 | bit["brshift"] = function(n, bits) 266 | bit.checkint(n) 267 | local high_bit = 0 268 | if(n < 0) then 269 | n = bit.bnot(math.abs(n)) + 1 270 | high_bit = 2147483648 271 | end 272 | for i=1, bits do 273 | n = n/2 274 | n = bit.bor(math.floor(n), high_bit) 275 | end 276 | return math.floor(n) 277 | end 278 | bit["blshift"] = function(n, bits) 279 | bit.checkint(n) 280 | if(n < 0) then 281 | n = bit.bnot(math.abs(n)) + 1 282 | end 283 | for i=1, bits do 284 | n = n*2 285 | end 286 | return bit.band(n, 4294967295) 287 | end 288 | bit["bxor2"] = function(m, n) 289 | local rhs = bit.bor(bit.bnot(m), bit.bnot(n)) 290 | local lhs = bit.bor(m, n) 291 | local rslt = bit.band(lhs, rhs) 292 | return rslt 293 | end 294 | bit["blogic_rshift"] = function(n, bits) 295 | bit.checkint(n) 296 | if(n < 0) then 297 | n = bit.bnot(math.abs(n)) + 1 298 | end 299 | for i=1, bits do 300 | n = n/2 301 | end 302 | return math.floor(n) 303 | end 304 | bit["tobits"] = function(n) 305 | bit.checkint(n) 306 | if(n < 0) then 307 | return bit.tobits(bit.bnot(math.abs(n)) + 1) 308 | end 309 | local tbl = {} 310 | local cnt = 1 311 | while (n > 0) do 312 | local last = math.fmod(n,2) 313 | if(last == 1) then 314 | tbl[cnt] = 1 315 | else 316 | tbl[cnt] = 0 317 | end 318 | n = (n-last)/2 319 | cnt = cnt + 1 320 | end 321 | return tbl 322 | end 323 | bit["tonumb"] = function(tbl) 324 | local n = table.getn(tbl) 325 | local rslt = 0 326 | local power = 1 327 | for i = 1, n do 328 | rslt = rslt + tbl[i]*power 329 | power = power*2 330 | end 331 | return rslt 332 | end 333 | bit["checkint"] = function(n) 334 | if(n - math.floor(n) > 0) then 335 | error("trying to use bitwise operation on non-integer!") 336 | end 337 | end 338 | bit["expand"] = function(tbl_m, tbl_n) 339 | local big = {} 340 | local small = {} 341 | if(table.getn(tbl_m) > table.getn(tbl_n)) then 342 | big = tbl_m 343 | small = tbl_n 344 | else 345 | big = tbl_n 346 | small = tbl_m 347 | end 348 | for i = table.getn(small) + 1, table.getn(big) do 349 | small[i] = 0 350 | end 351 | end 352 | 353 | local FCS = {} 354 | FCS["16"] = { 355 | [0]=0, 4489, 8978, 12955, 17956, 22445, 25910, 29887, 356 | 35912, 40385, 44890, 48851, 51820, 56293, 59774, 63735, 357 | 4225, 264, 13203, 8730, 22181, 18220, 30135, 25662, 358 | 40137, 36160, 49115, 44626, 56045, 52068, 63999, 59510, 359 | 8450, 12427, 528, 5017, 26406, 30383, 17460, 21949, 360 | 44362, 48323, 36440, 40913, 60270, 64231, 51324, 55797, 361 | 12675, 8202, 4753, 792, 30631, 26158, 21685, 17724, 362 | 48587, 44098, 40665, 36688, 64495, 60006, 55549, 51572, 363 | 16900, 21389, 24854, 28831, 1056, 5545, 10034, 14011, 364 | 52812, 57285, 60766, 64727, 34920, 39393, 43898, 47859, 365 | 21125, 17164, 29079, 24606, 5281, 1320, 14259, 9786, 366 | 57037, 53060, 64991, 60502, 39145, 35168, 48123, 43634, 367 | 25350, 29327, 16404, 20893, 9506, 13483, 1584, 6073, 368 | 61262, 65223, 52316, 56789, 43370, 47331, 35448, 39921, 369 | 29575, 25102, 20629, 16668, 13731, 9258, 5809, 1848, 370 | 65487, 60998, 56541, 52564, 47595, 43106, 39673, 35696, 371 | 33800, 38273, 42778, 46739, 49708, 54181, 57662, 61623, 372 | 2112, 6601, 11090, 15067, 20068, 24557, 28022, 31999, 373 | 38025, 34048, 47003, 42514, 53933, 49956, 61887, 57398, 374 | 6337, 2376, 15315, 10842, 24293, 20332, 32247, 27774, 375 | 42250, 46211, 34328, 38801, 58158, 62119, 49212, 53685, 376 | 10562, 14539, 2640, 7129, 28518, 32495, 19572, 24061, 377 | 46475, 41986, 38553, 34576, 62383, 57894, 53437, 49460, 378 | 14787, 10314, 6865, 2904, 32743, 28270, 23797, 19836, 379 | 50700, 55173, 58654, 62615, 32808, 37281, 41786, 45747, 380 | 19012, 23501, 26966, 30943, 3168, 7657, 12146, 16123, 381 | 54925, 50948, 62879, 58390, 37033, 33056, 46011, 41522, 382 | 23237, 19276, 31191, 26718, 7393, 3432, 16371, 11898, 383 | 59150, 63111, 50204, 54677, 41258, 45219, 33336, 37809, 384 | 27462, 31439, 18516, 23005, 11618, 15595, 3696, 8185, 385 | 63375, 58886, 54429, 50452, 45483, 40994, 37561, 33584, 386 | 31687, 27214, 22741, 18780, 15843, 11370, 7921, 3960 } 387 | FCS["32"] = { 388 | [0]=0, 1996959894, -301047508, -1727442502, 124634137, 1886057615, -379345611, -1637575261, 389 | 249268274, 2044508324, -522852066, -1747789432, 162941995, 2125561021, -407360249, -1866523247, 390 | 498536548, 1789927666, -205950648, -2067906082, 450548861, 1843258603, -187386543, -2083289657, 391 | 325883990, 1684777152, -43845254, -1973040660, 335633487, 1661365465, -99664541, -1928851979, 392 | 997073096, 1281953886, -715111964, -1570279054, 1006888145, 1258607687, -770865667, -1526024853, 393 | 901097722, 1119000684, -608450090, -1396901568, 853044451, 1172266101, -589951537, -1412350631, 394 | 651767980, 1373503546, -925412992, -1076862698, 565507253, 1454621731, -809855591, -1195530993, 395 | 671266974, 1594198024, -972236366, -1324619484, 795835527, 1483230225, -1050600021, -1234817731, 396 | 1994146192, 31158534, -1731059524, -271249366, 1907459465, 112637215, -1614814043, -390540237, 397 | 2013776290, 251722036, -1777751922, -519137256, 2137656763, 141376813, -1855689577, -429695999, 398 | 1802195444, 476864866, -2056965928, -228458418, 1812370925, 453092731, -2113342271, -183516073, 399 | 1706088902, 314042704, -1950435094, -54949764, 1658658271, 366619977, -1932296973, -69972891, 400 | 1303535960, 984961486, -1547960204, -725929758, 1256170817, 1037604311, -1529756563, -740887301, 401 | 1131014506, 879679996, -1385723834, -631195440, 1141124467, 855842277, -1442165665, -586318647, 402 | 1342533948, 654459306, -1106571248, -921952122, 1466479909, 544179635, -1184443383, -832445281, 403 | 1591671054, 702138776, -1328506846, -942167884, 1504918807, 783551873, -1212326853, -1061524307, 404 | -306674912, -1698712650, 62317068, 1957810842, -355121351, -1647151185, 81470997, 1943803523, 405 | -480048366, -1805370492, 225274430, 2053790376, -468791541, -1828061283, 167816743, 2097651377, 406 | -267414716, -2029476910, 503444072, 1762050814, -144550051, -2140837941, 426522225, 1852507879, 407 | -19653770, -1982649376, 282753626, 1742555852, -105259153, -1900089351, 397917763, 1622183637, 408 | -690576408, -1580100738, 953729732, 1340076626, -776247311, -1497606297, 1068828381, 1219638859, 409 | -670225446, -1358292148, 906185462, 1090812512, -547295293, -1469587627, 829329135, 1181335161, 410 | -882789492, -1134132454, 628085408, 1382605366, -871598187, -1156888829, 570562233, 1426400815, 411 | -977650754, -1296233688, 733239954, 1555261956, -1026031705, -1244606671, 752459403, 1541320221, 412 | -1687895376, -328994266, 1969922972, 40735498, -1677130071, -351390145, 1913087877, 83908371, 413 | -1782625662, -491226604, 2075208622, 213261112, -1831694693, -438977011, 2094854071, 198958881, 414 | -2032938284, -237706686, 1759359992, 534414190, -2118248755, -155638181, 1873836001, 414664567, 415 | -2012718362, -15766928, 1711684554, 285281116, -1889165569, -127750551, 1634467795, 376229701, 416 | -1609899400, -686959890, 1308918612, 956543938, -1486412191, -799009033, 1231636301, 1047427035, 417 | -1362007478, -640263460, 1088359270, 936918000, -1447252397, -558129467, 1202900863, 817233897, 418 | -1111625188, -893730166, 1404277552, 615818150, -1160759803, -841546093, 1423857449, 601450431, 419 | -1285129682, -1000256840, 1567103746, 711928724, -1274298825, -1022587231, 1510334235, 755167117 } 420 | 421 | --String Utils : 422 | 423 | local strutils = {} 424 | 425 | function toCharTable(str) --Returns table of @str's chars 426 | if not str then return nil end 427 | str = tostring(str) 428 | local chars = {} 429 | for n=1,#str do 430 | chars[n] = str:sub(n,n) 431 | end 432 | return chars 433 | end 434 | strutils.toCharTable = toCharTable 435 | 436 | function toByteTable(str) --Returns table of @str's bytes 437 | if not str then return nil end 438 | str = tostring(str) 439 | local bytes = {} 440 | for n=1,#str do 441 | bytes[n] = str:byte(n) 442 | end 443 | return bytes 444 | end 445 | strutils.toByteTable = toByteTable 446 | 447 | 448 | function fromCharTable(chars) --Returns string made of chracters in @chars 449 | if not chars or type(chars)~="table" then return nil end 450 | return table.concat(chars) 451 | end 452 | strutils.fromCharTable = fromCharTable 453 | 454 | 455 | function fromByteTable(bytes) --Returns string made of bytes in @bytes 456 | if not bytes or type(bytes)~="table" then return nil end 457 | local str = "" 458 | for n=1,#bytes do 459 | str = str..string.char(bytes[n]) 460 | end 461 | return str 462 | end 463 | strutils.fromByteTable = fromByteTable 464 | 465 | 466 | function contains(str,find) --Returns true if @str contains @find 467 | if not str then return nil end 468 | str = tostring(str) 469 | for n=1, #str-#find+1 do 470 | if str:sub(n,n+#find-1) == find then return true end 471 | end 472 | return false 473 | end 474 | strutils.contains = contains 475 | 476 | function startsWith(str,Start) --Check if @str starts with @Start 477 | if not str then return nil end 478 | str = tostring(str) 479 | return str:sub(1,Start:len())==Start 480 | end 481 | strutils.startsWith = startsWith 482 | 483 | function endsWith(str,End) --Check if @str ends with @End 484 | if not str then return nil end 485 | str = tostring(str) 486 | return End=='' or str:sub(#str-#End+1)==End 487 | end 488 | strutils.endsWith = endsWith 489 | 490 | function trim(str) --Trim @str of initial/trailing whitespace 491 | if not str then return nil end 492 | str = tostring(str) 493 | return (str:gsub("^%s*(.-)%s*$", "%1")) 494 | end 495 | strutils.trim = trim 496 | 497 | function firstLetterUpper(str) --Capitilizes first letter of @str 498 | if not str then return nil end 499 | str = tostring(str) 500 | str = str:gsub("%a", string.upper, 1) 501 | return str 502 | end 503 | strutils.firstLetterUpper = firstLetterUpper 504 | 505 | function titleCase(str) --Changes @str to title case 506 | if not str then return nil end 507 | str = tostring(str) 508 | local function tchelper(first, rest) 509 | return first:upper()..rest:lower() 510 | end 511 | str = str:gsub("(%a)([%w_']*)", tchelper) 512 | return str 513 | end 514 | strutils.titleCase = titleCase 515 | 516 | function isRepetition(str, pat) --Checks if @str is a repetition of @pat 517 | if not str then return nil end 518 | str = tostring(str) 519 | return "" == str:gsub(pat, "") 520 | end 521 | 522 | function isRepetitionWS(str, pat) --Checks if @str is a repetition of @pat seperated by whitespaces 523 | if not str then return nil end 524 | str = tostring(str) 525 | return not str:gsub(pat, ""):find"%S" 526 | end 527 | strutils.isRepetitionWS = isRepetitionWS 528 | 529 | function urlDecode(str) --Url decodes @str 530 | if not str then return nil end 531 | str = tostring(str) 532 | str = string.gsub (str, "+", " ") 533 | str = string.gsub (str, "%%(%x%x)", function(h) return string.char(tonumber(h,16)) end) 534 | str = string.gsub (str, "\r\n", "\n") 535 | return str 536 | end 537 | strutils.urlDecode = urlDecode 538 | 539 | function urlEncode(str) --Url encodes @str 540 | if not str then return nil end 541 | str = tostring(str) 542 | if (str) then 543 | str = string.gsub (str, "\n", "\r\n") 544 | str = string.gsub (str, "([^%w ])", function (c) return string.format ("%%%02X", string.byte(c)) end) 545 | str = string.gsub (str, " ", "+") 546 | end 547 | return str 548 | end 549 | strutils.urlEncode = urlEncode 550 | 551 | function isEmailAddress(str) --Checks if @str is a valid email address 552 | if not str then return nil end 553 | str = tostring(str) 554 | if (str:match("[A-Za-z0-9%.%%%+%-]+@[A-Za-z0-9%.%%%+%-]+%.%w%w%w?%w?")) then 555 | return true 556 | else 557 | return false 558 | end 559 | end 560 | strutils.isEmailAddress = isEmailAddress 561 | 562 | function chunk(str, size) --Splits @str into chunks of length @size 563 | if not size then return nil end 564 | str = tostring(str) 565 | local num2App = size - (#str%size) 566 | str = str..(rep(char(0), num2App) or "") 567 | assert(#str%size==0) 568 | local chunks = {} 569 | local numChunks = #str / size 570 | local chunk = 0 571 | while chunk < numChunks do 572 | local start = chunk * size + 1 573 | chunk = chunk+1 574 | if start+size-1 > #str-num2App then 575 | if str:sub(start, #str-num2App) ~= (nil or "") then 576 | chunks[chunk] = str:sub(start, #str-num2App) 577 | end 578 | else 579 | chunks[chunk] = str:sub(start, start+size-1) 580 | end 581 | end 582 | return chunks 583 | end 584 | strutils.chunk = chunk 585 | 586 | function find(str, match, startIndex) --Finds @match in @str optionally after @startIndex 587 | if not match then return nil end 588 | str = tostring(str) 589 | local _ = startIndex or 1 590 | local _s = nil 591 | local _e = nil 592 | local _len = match:len() 593 | while true do 594 | local _t = str:sub( _ , _len + _ - 1) 595 | if _t == match then 596 | _s = _ 597 | _e = _ + _len - 1 598 | break 599 | end 600 | _ = _ + 1 601 | if _ > str:len() then break end 602 | end 603 | if _s == nil then return nil else return _s, _e end 604 | end 605 | strutils.find = find 606 | 607 | function seperate(str, divider) --Separates @str on @divider 608 | if not divider then return nil end 609 | str = tostring(str) 610 | local start = {} 611 | local endS = {} 612 | local n=1 613 | repeat 614 | if n==1 then 615 | start[n], endS[n] = find(str, divider) 616 | else 617 | start[n], endS[n] = find(str, divider, endS[n-1]+1) 618 | end 619 | n=n+1 620 | until start[n-1]==nil 621 | local subs = {} 622 | for n=1, #start+1 do 623 | if n==1 then 624 | subs[n] = str:sub(1, start[n]-1) 625 | elseif n==#start+1 then 626 | subs[n] = str:sub(endS[n-1]+1) 627 | else 628 | subs[n] = str:sub(endS[n-1]+1, start[n]-1) 629 | end 630 | end 631 | return subs 632 | end 633 | strutils.seperate = seperate 634 | 635 | function replace(str, from, to) --Replaces @from to @to in @str 636 | if not from then return nil end 637 | str = tostring(str) 638 | local pcs = seperate(str, from) 639 | str = pcs[1] 640 | for n=2,#pcs do 641 | str = str..to..pcs[n] 642 | end 643 | return str 644 | end 645 | strutils.replace = replace 646 | 647 | function jumble(str) --Jumbles @str 648 | if not str then return nil end 649 | str = tostring(str) 650 | local chars = {} 651 | for i = 1, #str do 652 | chars[i] = str:sub(i, i) 653 | end 654 | local usedNums = ":" 655 | local res = "" 656 | local rand = 0 657 | for i=1, #chars do 658 | while true do 659 | rand = math.random(#chars) 660 | if find(usedNums, ":"..rand..":") == nil then break end 661 | end 662 | res = res..chars[rand] 663 | usedNums = usedNums..rand..":" 664 | end 665 | return res 666 | end 667 | strutils.jumble = jumble 668 | 669 | function toBase(str, base) --Encodes @str in @base 670 | if not base then return nil end 671 | str = tostring(str) 672 | local res = "" 673 | for i = 1, str:len() do 674 | if i == 1 then 675 | res = basen(str:byte(i), base) 676 | else 677 | res = res..":"..basen(str:byte(i), base) 678 | end 679 | end 680 | return res 681 | end 682 | strutils.toBase = toBase 683 | 684 | function fromBase(str, base) --Decodes @str from @base 685 | if not base then return nil end 686 | str = tostring(str) 687 | local bytes = seperate(str, ":") 688 | local res = "" 689 | for i = 1, #bytes do 690 | res = res..(string.char(basen(tonumber(bytes[i], base), 10))) 691 | end 692 | return res 693 | end 694 | strutils.fromBase = fromBase 695 | 696 | function toBinary(str) --Encodes @str in binary 697 | if not str then return nil end 698 | str = tostring(str) 699 | return toBase(str, 2) 700 | end 701 | strutils.toBinary = toBinary 702 | 703 | function fromBinary(str) --Decodes @str from binary 704 | if not str then return nil end 705 | str = tostring(str) 706 | return fromBase(str, 2) 707 | end 708 | strutils.fromBinary = fromBinary 709 | 710 | function toOctal(str) --Encodes @str in octal 711 | if not str then return nil end 712 | str = tostring(str) 713 | return toBase(str, 8) 714 | end 715 | strutils.toOctal = toOctal 716 | 717 | function fromOctal(str) --Decodes @str from octal 718 | if not str then return nil end 719 | str = tostring(str) 720 | return fromBase(str, 8) 721 | end 722 | strutils.fromOctal = fromOctal 723 | 724 | function toHex(str) --Encodes @str in hex 725 | if not str then return nil end 726 | str = tostring(str) 727 | return toBase(str, 16) 728 | end 729 | strutils.toHex = toHex 730 | 731 | function fromHex(str) --Decodes @str from hex 732 | if not str then return nil end 733 | str = tostring(str) 734 | return fromBase(str, 16) 735 | end 736 | strutils.fromHex = fromHex 737 | 738 | function toBase36(str) --Encodes @str in Base36 739 | if not str then return nil end 740 | str = tostring(str) 741 | return toBase(str, 36) 742 | end 743 | strutils.toBase36 = toBase36 744 | 745 | function fromBase36(str) --Decodes @str from Base36 746 | if not str then return nil end 747 | str = tostring(str) 748 | return fromBase(str, 36) 749 | end 750 | strutils.fromBase36 = fromBase36 751 | 752 | function toBase32(str) --Encodes @str in Base32 753 | if not str then return nil end 754 | str = tostring(str) 755 | local byte=0 756 | local bits=0 757 | local rez="" 758 | local i=0 759 | for i = 1, str:len() do 760 | byte=byte*256+str:byte(i) 761 | bits=bits+8 762 | repeat 763 | bits=bits-5 764 | local mul=(2^(bits)) 765 | local b32n=math.floor(byte/mul) 766 | byte=byte-(b32n*mul) 767 | b32n=b32n+1 768 | rez=rez..string.sub(base32,b32n,b32n) 769 | until bits<5 770 | end 771 | if bits>0 then 772 | local b32n= math.fmod(byte*(2^(5-bits)),32) 773 | b32n=b32n+1 774 | rez=rez..string.sub(base32,b32n,b32n) 775 | end 776 | return rez 777 | end 778 | strutils.toBase32 = toBase32 779 | 780 | function fromBase32(str) --Decodes @str from Base32 781 | if not str then return nil end 782 | str = tostring(str) 783 | local b32n=0 784 | local bits=0 785 | local rez="" 786 | local i=0 787 | string.gsub(str:upper(), "["..base32.."]", function (char) 788 | local num = string.find(base32, char, 1, true) 789 | b32n=b32n*32+(num - 1) 790 | bits=bits+5 791 | while bits>=8 do 792 | bits=bits-8 793 | local mul=(2^(bits)) 794 | local byte = math.floor(b32n/mul) 795 | b32n=b32n-(byte*mul) 796 | rez=rez..string.char(byte) 797 | end 798 | end) 799 | return rez 800 | end 801 | strutils.fromBase32 = fromBase32 802 | 803 | function toBase64(str) --Encodes @str in Base64 804 | if not str then return nil end 805 | str = tostring(str) 806 | local bytes = {} 807 | local result = "" 808 | for spos=0,str:len()-1,3 do 809 | for byte=1,3 do bytes[byte] = str:byte(spos+byte) or 0 end 810 | result = string.format('%s%s%s%s%s',result,Base64.base64chars[Base64.rsh(bytes[1],2)],Base64.base64chars[Base64.lor(Base64.lsh((bytes[1] % 4),4), Base64.rsh(bytes[2],4))] or "=",((str:len()-spos) > 1) and Base64.base64chars[Base64.lor(Base64.lsh(bytes[2] % 16,2), Base64.rsh(bytes[3],6))] or "=",((str:len()-spos) > 2) and Base64.base64chars[(bytes[3] % 64)] or "=") 811 | end 812 | return result 813 | end 814 | strutils.toBase64 = toBase64 815 | 816 | function fromBase64(str) --Decodes @str from Base64 817 | if not str then return nil end 818 | str = tostring(str) 819 | local chars = {} 820 | local result="" 821 | for dpos=0,str:len()-1,4 do 822 | for char=1,4 do chars[char] = Base64.base64bytes[(str:sub((dpos+char),(dpos+char)) or "=")] end 823 | result = string.format('%s%s%s%s',result,string.char(Base64.lor(Base64.lsh(chars[1],2), Base64.rsh(chars[2],4))),(chars[3] ~= nil) and string.char(Base64.lor(Base64.lsh(chars[2],4), Base64.rsh(chars[3],2))) or "",(chars[4] ~= nil) and string.char(Base64.lor(Base64.lsh(chars[3],6) % 192, (chars[4]))) or "") 824 | end 825 | return result 826 | end 827 | strutils.fromBase64 = fromBase64 828 | 829 | function rot13(str) --Rot13s @str 830 | if not str then return nil end 831 | str = tostring(str) 832 | local rot = "" 833 | local len = str:len() 834 | for i = 1, len do 835 | local k = str:byte(i) 836 | if (k >= 65 and k <= 77) or (k >= 97 and k <=109) then 837 | rot = rot..string.char(k+13) 838 | elseif (k >= 78 and k <= 90) or (k >= 110 and k <= 122) then 839 | rot = rot..string.char(k-13) 840 | else 841 | rot = rot..string.char(k) 842 | end 843 | end 844 | return rot 845 | end 846 | strutils.rot13 = rot13 847 | 848 | function rot47(str) --Rot47s @str 849 | if not str then return nil end 850 | str = tostring(str) 851 | local rot = "" 852 | for i = 1, str:len() do 853 | local p = str:byte(i) 854 | if p >= string.byte('!') and p <= string.byte('O') then 855 | p = ((p + 47) % 127) 856 | elseif p >= string.byte('P') and p <= string.byte('~') then 857 | p = ((p - 47) % 127) 858 | end 859 | rot = rot..string.char(p) 860 | end 861 | return rot 862 | end 863 | strutils.rot47 = rot47 864 | 865 | function SHA1(str) --Returns SHA1 Hash of @str 866 | if not str then return nil end 867 | str = tostring(str) 868 | local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0 869 | local msg_len_in_bits = #str * 8 870 | local first_append = char(0x80) 871 | local non_zero_message_bytes = #str +1 +8 872 | local current_mod = non_zero_message_bytes % 64 873 | local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or "" 874 | local B1, R1 = modf(msg_len_in_bits / 0x01000000) 875 | local B2, R2 = modf( 0x01000000 * R1 / 0x00010000) 876 | local B3, R3 = modf( 0x00010000 * R2 / 0x00000100) 877 | local B4 = 0x00000100 * R3 878 | local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) 879 | .. char(B1) .. char(B2) .. char(B3) .. char(B4) 880 | str = str .. first_append .. second_append .. L64 881 | assert(#str % 64 == 0) 882 | local chunks = #str / 64 883 | local W = { } 884 | local start, A, B, C, D, E, f, K, TEMP 885 | local chunk = 0 886 | while chunk < chunks do 887 | start,chunk = chunk * 64 + 1,chunk + 1 888 | for t = 0, 15 do 889 | W[t] = tSHA1.bytes_to_w32(str:byte(start, start + 3)) 890 | start = start + 4 891 | end 892 | for t = 16, 79 do 893 | W[t] = tSHA1.w32_rot(1, tSHA1.w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16])) 894 | end 895 | A,B,C,D,E = H0,H1,H2,H3,H4 896 | for t = 0, 79 do 897 | if t <= 19 then 898 | f = tSHA1.w32_or(tSHA1.w32_and(B, C), tSHA1.w32_and(tSHA1.w32_not(B), D)) 899 | K = 0x5A827999 900 | elseif t <= 39 then 901 | f = tSHA1.w32_xor_n(B, C, D) 902 | K = 0x6ED9EBA1 903 | elseif t <= 59 then 904 | f = tSHA1.w32_or3(tSHA1.w32_and(B, C), tSHA1.w32_and(B, D), tSHA1.w32_and(C, D)) 905 | K = 0x8F1BBCDC 906 | else 907 | f = tSHA1.w32_xor_n(B, C, D) 908 | K = 0xCA62C1D6 909 | end 910 | A,B,C,D,E = tSHA1.w32_add_n(tSHA1.w32_rot(5, A), f, E, W[t], K), 911 | A, tSHA1.w32_rot(30, B), C, D 912 | end 913 | H0,H1,H2,H3,H4 = tSHA1.w32_add(H0, A),tSHA1.w32_add(H1, B),tSHA1.w32_add(H2, C),tSHA1.w32_add(H3, D),tSHA1.w32_add(H4, E) 914 | end 915 | local f = tSHA1.w32_to_hexstring 916 | return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4) 917 | end 918 | strutils.SHA1 = SHA1 919 | 920 | function CRC32(str) --Returns CRC32 Hash of @str 921 | local crc, l, i = 0xFFFFFFFF, string.len(str) 922 | for i = 1, l, 1 do 923 | crc = bit.bxor(bit.brshift(crc, 8), CRC.crc32[bit.band(bit.bxor(crc, string.byte(str, i)), 0xFF) + 1]) 924 | end 925 | return bit.bxor(crc, -1) 926 | end 927 | strutils.CRC32 = CRC32 928 | 929 | function FCS16(str) --Returns FCS16 Hash of @str 930 | local i 931 | local l=string.len(str) 932 | local uFcs16 = 65535 933 | for i = 1,l do 934 | uFcs16 = bit.bxor(bit.brshift(uFcs16,8), FCS["16"][bit.band(bit.bxor(uFcs16, string.byte(str,i)), 255)]) 935 | end 936 | return bit.bxor(uFcs16, 65535) 937 | end 938 | strutils.FCS16 = FCS16 939 | 940 | function FCS32(str) --Returns FCS32 Hash of @str 941 | local i 942 | local l = string.len(str) 943 | local uFcs32 = -1 944 | for i=1,l do 945 | uFcs32 = bit.bxor(bit.brshift(uFcs32,8), FCS["32"][bit.band(bit.bxor(uFcs32, string.byte(str,i)), 255)]) 946 | end 947 | return bit.bnot(uFcs32) 948 | end 949 | strutils.FCS32 = FCS32 950 | 951 | function encrypt(str, key) --Encrypts @str with @key 952 | if not key then return nil end 953 | str = tostring(str) 954 | local alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~" 955 | local _rand = math.random(#alphabet-10) 956 | local iv = string.sub(jumble(alphabet), _rand, _rand + 9) 957 | iv = jumble(iv) 958 | str = iv..str 959 | local key = SHA1(key) 960 | local strLen = str:len() 961 | local keyLen = key:len() 962 | local j=1 963 | local result = "" 964 | for i=1, strLen do 965 | local ordStr = string.byte(str:sub(i,i)) 966 | if j == keyLen then j=1 end 967 | local ordKey = string.byte(key:sub(j,j)) 968 | result = result..string.reverse(basen(ordStr+ordKey, 36)) 969 | j = j+1 970 | end 971 | return result 972 | end 973 | strutils.encrypt = encrypt 974 | 975 | function decrypt(str, key) --Decrypts @str with @key 976 | if not key then return nil end 977 | str = tostring(str) 978 | local key = SHA1(key) 979 | local strLen = str:len() 980 | local keyLen = key:len() 981 | local j=1 982 | local result = "" 983 | for i=1, strLen, 2 do 984 | local ordStr = basen(tonumber(string.reverse(str:sub(i, i+1)),36),10) 985 | if j==keyLen then j=1 end 986 | local ordKey = string.byte(key:sub(j,j)) 987 | result = result..string.char(ordStr-ordKey) 988 | j = j+1 989 | end 990 | return result:sub(11) 991 | end 992 | strutils.decrypt = decrypt 993 | 994 | function setRandSeed(seed) --Sets random seed to @seed 995 | math.randomseed(seed) 996 | end 997 | strutils.setRandSeed = setRandSeed 998 | 999 | --function Test() 1000 | -- local br = "\n----------\n" 1001 | -- setRandSeed(os.time()) 1002 | -- local logFile = io.open("StrUtilTest.log", "w") 1003 | -- logFile:write(br) 1004 | -- logFile:write("toCharTable(\"Hello\")".."\n") 1005 | -- local ct = toCharTable("Hello") 1006 | -- for k,v in pairs(ct) do logFile:write(k.."-"..v.."\n") end 1007 | -- logFile:write(br) 1008 | -- logFile:write("fromCharTable()".."\n") 1009 | -- logFile:write(fromCharTable(ct).."\n") 1010 | -- logFile:write(br) 1011 | -- logFile:write("toByteTable(\"Hello\")".."\n") 1012 | -- local bt = toByteTable("Hello") 1013 | -- for k,v in pairs(bt) do logFile:write(k.."-"..v.."\n") end 1014 | -- logFile:write(br) 1015 | -- logFile:write("fromByteTable()".."\n") 1016 | -- logFile:write(fromByteTable(bt).."\n") 1017 | -- logFile:write(br) 1018 | -- logFile:write("contains(\"Hello\", \"Hell\")".."\n") 1019 | -- logFile:write(tostring(contains("Hello", "Hell")).."\n") 1020 | -- logFile:write(br) 1021 | -- logFile:write("startsWith(\"Hello\", \"H\")".."\n") 1022 | -- logFile:write(tostring(startsWith("Hello", "H")).."\n") 1023 | -- logFile:write(br) 1024 | -- logFile:write("endsWith(\"Hello\", \"o\")".."\n") 1025 | -- logFile:write(tostring(endsWith("Hello", "o")).."\n") 1026 | -- logFile:write(br) 1027 | -- logFile:write("trim(\" Hello \")".."\n") 1028 | -- logFile:write(trim(" Hello ").."\n") 1029 | -- logFile:write(br) 1030 | -- logFile:write("firstLetterUpper(\"hello\")".."\n") 1031 | -- logFile:write(firstLetterUpper("hello").."\n") 1032 | -- logFile:write(br) 1033 | -- logFile:write("titleCase(\"hello world, how are you\")".."\n") 1034 | -- logFile:write(titleCase("hello world, how are you").."\n") 1035 | -- logFile:write(br) 1036 | -- logFile:write("isRepetition(\"HelloHelloHello\", \"Hello\")".."\n") 1037 | -- logFile:write(tostring(isRepetition("HelloHelloHello", "Hello")).."\n") 1038 | -- logFile:write(br) 1039 | -- logFile:write("isRepetitionWS(\"Hello Hello Hello\", \"Hello\")".."\n") 1040 | -- logFile:write(tostring(isRepetitionWS("Hello Hello Hello", "Hello")).."\n") 1041 | -- logFile:write(br) 1042 | -- logFile:write("urlEncode(\"Hello There World\")".."\n") 1043 | -- local US = urlEncode("Hello There World") 1044 | -- logFile:write(US.."\n") 1045 | -- logFile:write(br) 1046 | -- logFile:write("urlDecode()".."\n") 1047 | -- logFile:write(urlDecode(US).."\n") 1048 | -- logFile:write(br) 1049 | -- logFile:write("isEmailAddress(\"cooldude@emailhost.com\")".."\n") 1050 | -- logFile:write(tostring(isEmailAddress("cooldude@emailhost.com")).."\n") 1051 | -- logFile:write(br) 1052 | -- logFile:write("chunk(\"123456789\", 3)".."\n") 1053 | -- local chnk = chunk("123456789", 3) 1054 | -- for k,v in pairs(chnk) do logFile:write(k.."-"..v.."\n") end 1055 | -- logFile:write(br) 1056 | -- logFile:write("find(\"Hello World HeHe\", \"World\")".."\n") 1057 | -- logFile:write(find("Hello World HeHe", "World").."\n") 1058 | -- logFile:write(br) 1059 | -- logFile:write("seperate(\"1.2.3.4.5\", \".\")".."\n") 1060 | -- local pcs = seperate("1.2.3.4.5", ".") 1061 | -- for k,v in pairs(pcs) do logFile:write(k.."-"..v.."\n") end 1062 | -- logFile:write(br) 1063 | -- logFile:write("replace(\"# # # #\",\" \",\"*\")".."\n") 1064 | -- logFile:write(replace("# # # #", " ", "*").."\n") 1065 | -- logFile:write(br) 1066 | -- logFile:write("jumble(\"Hello World\")".."\n") 1067 | -- logFile:write(jumble("Hello World").."\n") 1068 | -- logFile:write(br) 1069 | -- logFile:write("toBase(\"Hello\", 13)".."\n") 1070 | -- local tb = toBase("Hello", 13) 1071 | -- logFile:write(tb.."\n") 1072 | -- logFile:write(br) 1073 | -- logFile:write("fromBase()".."\n") 1074 | -- logFile:write(fromBase(tb,13).."\n") 1075 | -- logFile:write(br) 1076 | -- logFile:write("toBinary(\"Hello\")".."\n") 1077 | -- local tb = toBinary("Hello") 1078 | -- logFile:write(tb.."\n") 1079 | -- logFile:write(br) 1080 | -- logFile:write("fromBinary()".."\n") 1081 | -- logFile:write(fromBinary(tb).."\n") 1082 | -- logFile:write(br) 1083 | -- logFile:write("toOctal(\"Hello\")".."\n") 1084 | -- local tb = toOctal("Hello") 1085 | -- logFile:write(tb.."\n") 1086 | -- logFile:write(br) 1087 | -- logFile:write("fromOctal()".."\n") 1088 | -- logFile:write(fromOctal(tb).."\n") 1089 | -- logFile:write(br) 1090 | -- logFile:write("toHex(\"Hello\")".."\n") 1091 | -- local tb = toHex("Hello") 1092 | -- logFile:write(tb.."\n") 1093 | -- logFile:write(br) 1094 | -- logFile:write("fromHex()".."\n") 1095 | -- logFile:write(fromHex(tb).."\n") 1096 | -- logFile:write(br) 1097 | -- logFile:write("toBase36(\"Hello\")".."\n") 1098 | -- local tb = toBase36("Hello") 1099 | -- logFile:write(tb.."\n") 1100 | -- logFile:write(br) 1101 | -- logFile:write("fromBase36()".."\n") 1102 | -- logFile:write(fromBase36(tb).."\n") 1103 | -- logFile:write(br) 1104 | -- logFile:write("toBase32(\"Hello\")".."\n") 1105 | -- local tb = toBase32("Hello") 1106 | -- logFile:write(tb.."\n") 1107 | -- logFile:write(br) 1108 | -- logFile:write("fromBase32()".."\n") 1109 | -- logFile:write(fromBase32(tb).."\n") 1110 | -- logFile:write(br) 1111 | -- logFile:write("toBase64(\"Hello\")".."\n") 1112 | -- local tb = toBase64("Hello") 1113 | -- logFile:write(tb.."\n") 1114 | -- logFile:write(br) 1115 | -- logFile:write("fromBase64()".."\n") 1116 | -- logFile:write(fromBase64(tb).."\n") 1117 | -- logFile:write(br) 1118 | -- logFile:write("rot13(\"Hello\")".."\n") 1119 | -- logFile:write(rot13("Hello").."\n") 1120 | -- logFile:write(br) 1121 | -- logFile:write("rot47(\"Hello\")".."\n") 1122 | -- logFile:write(rot47("Hello").."\n") 1123 | -- logFile:write(br) 1124 | -- logFile:write("SHA1(\"Hello\")".."\n") 1125 | -- logFile:write(SHA1("Hello").."\n") 1126 | -- logFile:write(br) 1127 | -- logFile:write("CRC32(\"Hello\")".."\n") 1128 | -- logFile:write(CRC32("Hello").."\n") 1129 | -- logFile:write(br) 1130 | -- logFile:write("FCS16(\"Hello\")".."\n") 1131 | -- logFile:write(FCS16("Hello").."\n") 1132 | -- logFile:write(br) 1133 | -- logFile:write("FCS32(\"Hello\")".."\n") 1134 | -- logFile:write(FCS32("Hello").."\n") 1135 | -- logFile:write(br) 1136 | -- logFile:write("encrypt(\"Hello\", \"Key\")".."\n") 1137 | -- local enc = encrypt("Hello", "Key") 1138 | -- logFile:write(enc.."\n") 1139 | -- logFile:write(br) 1140 | -- logFile:write("decrypt()".."\n") 1141 | -- logFile:write(decrypt(enc, "Key").."\n") 1142 | -- logFile:write(br) 1143 | -- logFile:close() 1144 | --end 1145 | --Test() 1146 | 1147 | setRandSeed(os.time()) 1148 | 1149 | return strutils --------------------------------------------------------------------------------