summaryrefslogtreecommitdiffstats
path: root/nselib/redis.lua
blob: fab9190596e5a9c2fe735cb8e9ce78be66b4d886 (plain)
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;