1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
--- A minimalistic Redis (in-memory key-value data store) library.
--
-- @author Patrik Karlsson <patrik@cqure.net>
local match = require "match"
local nmap = require "nmap"
local stdnse = require "stdnse"
local table = require "table"
_ENV = stdnse.module("redis", stdnse.seeall)
Request = {
new = function(self, cmd, ...)
local o = { cmd = cmd, args = {...} }
setmetatable (o,self)
self.__index = self
return o
end,
__tostring = function(self)
local output = ("*%s\r\n$%d\r\n%s\r\n"):format(#self.args + 1, #self.cmd, self.cmd)
for _, arg in ipairs(self.args) do
arg = tostring(arg)
output = output .. ("$%s\r\n%s\r\n"):format(#arg, arg)
end
return output
end
}
Response = {
Type = {
STATUS = 0,
ERROR = 1,
INTEGER = 2,
BULK = 3,
MULTIBULK = 4,
},
new = function(self, socket)
local o = { socket = socket }
setmetatable (o,self)
self.__index = self
return o
end,
receive = function(self)
local status, data = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false)
if ( not(status) ) then
return false, "Failed to receive data from server"
end
-- if we have a status, integer or error message
if ( data:match("^[%-%+%:]") ) then
local response = { data = data }
local t = data:match("^([-+:])")
if ( t == "-" ) then
response.type = Response.Type.ERROR
elseif ( t == "+" ) then
response.type = Response.Type.STATUS
elseif ( t == ":" ) then
response.type = Response.Type.INTEGER
end
return true, response
end
-- process bulk reply
if ( data:match("^%$") ) then
-- non existing key
if ( data == "$-1" ) then
return true, nil
end
local len = tonumber(data:match("^%$(%d*)"))
-- we should only have a single line, so we can just peel of the length
status, data = self.socket:receive_buf(match.numbytes(len), true)
if( not(status) ) then
return false, "Failed to receive data from server"
end
-- move past the terminal CRLF
local status, crlf = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false)
return true, { data = data, type = Response.Type.BULK }
end
-- process multi-bulk reply
if ( data:match("^%*%d*") ) then
local count = data:match("^%*(%d*)")
local results = {}
for i=1, count do
-- peel of the length
local status = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false)
if( not(status) ) then
return false, "Failed to receive data from server"
end
status, data = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false)
if( not(status) ) then
return false, "Failed to receive data from server"
end
table.insert(results, data)
end
return true, { data = results, type = Response.Type.MULTIBULK }
end
return false, "Unsupported response"
end,
}
Helper = {
new = function(self, host, port)
local o = { host = host, port = port }
setmetatable (o,self)
self.__index = self
return o
end,
connect = function(self, socket)
self.socket = socket or nmap.new_socket()
return self.socket:connect(self.host, self.port)
end,
reqCmd = function(self, cmd, ...)
local req = Request:new(cmd, ...)
local status, err = self.socket:send(tostring(req))
if (not(status)) then
return false, "Failed to send command to server"
end
return Response:new(self.socket):receive()
end,
close = function(self)
return self.socket:close()
end
}
return _ENV;
|