This file is indexed.

/usr/share/lua/5.1/nginx/redis/connector.lua is in lua-nginx-redis-connector 0.03-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
local redis = require "nginx.redis"
redis.add_commands("sentinel")
local sentinel = require "nginx.redis.sentinel"


local ipairs, setmetatable, pcall = ipairs, setmetatable, pcall
local ngx_null = ngx.null
local ngx_log = ngx.log
local ngx_DEBUG = ngx.DEBUG
local ngx_ERR = ngx.ERR
local ngx_re_match = ngx.re.match
local tbl_insert = table.insert
local tbl_remove = table.remove
local tbl_sort = table.sort

local ok, tbl_new = pcall(require, "table.new")
if not ok then
    tbl_new = function (narr, nrec) return {} end
end


local _M = {
    _VERSION = '0.03',
}

local mt = { __index = _M }


local DEFAULTS = {
    host = "127.0.0.1",
    port = 6379,
    path = nil, -- /tmp/redis.sock
    password = nil,
    db = 0,
    master_name = "mymaster",
    role = "master", -- master | slave | any (tries master first, failover to a slave)
    sentinels = nil,
    cluster_startup_nodes = {},
}


function _M.new()
    return setmetatable({
        connect_timeout = 100,
        read_timeout = 1000,
        connection_options = nil, -- pool, etc
    }, mt)
end


function _M.set_connect_timeout(self, timeout)
    self.connect_timeout = timeout
end


function _M.set_read_timeout(self, timeout)
    self.read_timeout = timeout
end


function _M.set_connection_options(self, options)
    self.connection_options = options
end


local function parse_dsn(params)
    local url = params.url
    if url then
        local url_pattern = [[^(?:(redis|sentinel)://)(?:([^@]*)@)?([^:/]+)(?::(\d+|[msa]+))/?(.*)$]]
        local m, err = ngx_re_match(url, url_pattern, "")
        if not m then
            ngx_log(ngx_ERR, "could not parse DSN: ", err)
        else
            local fields
            if m[1] == "redis" then
                fields = { "password", "host", "port", "db" }
            elseif m[1] == "sentinel" then
                fields = { "password", "master_name", "role", "db" }
            end
            
            -- password may not be present
            if #m < 5 then tbl_remove(fields, 1) end

            local roles = { m = "master", s = "slave", a = "any" }
            
            for i,v in ipairs(fields) do
                params[v] = m[i + 1]
                if v == "role" then
                    params[v] = roles[params[v]]
                end
            end
        end
    end
end


function _M.connect(self, params)
    -- If we have nothing, assume default host connection options apply
    if not params or type(params) ~= "table" then
        params = {}
    end

    if params.url then 
        parse_dsn(params) 
    end

    if params.sentinels then
        setmetatable(params, { __index = DEFAULTS } )
        return self:connect_via_sentinel(params)
    elseif params.startup_cluster_nodes then
        setmetatable(params, { __index = DEFAULTS } )
        -- TODO: Implement cluster
        return nil, "Redis Cluster not yet implemented"
    else
        setmetatable(params, { __index = DEFAULTS } )
        return self:connect_to_host(params)
    end
end


local function sort_by_localhost(a, b)
    if a.host == "127.0.0.1" then
        return true
    else
        return false
    end
end


function _M.connect_via_sentinel(self, params)
    local sentinels = params.sentinels
    local master_name = params.master_name
    local role = params.role
    local db = params.db
    local password = params.password

    local sentnl, err, previous_errors = self:try_hosts(sentinels)
    if not sentnl then
        return nil, err, previous_errors
    end

    if role == "master" or role == "any" then
        local master, err = sentinel.get_master(sentnl, master_name)
        if master then
            master.db = db
            master.password = password

            local redis, err = self:connect_to_host(master)
            if redis then
                sentnl:set_keepalive()
                return redis, err
            else
                if role == "master" then
                    return nil, err
                end
            end
        end
    end

    -- We either wanted a slave, or are failing over to a slave "any"
    local slaves, err = sentinel.get_slaves(sentnl, master_name)
    sentnl:set_keepalive()

    if not slaves then
        return nil, err
    end

    -- Put any slaves on 127.0.0.1 at the front
    tbl_sort(slaves, sort_by_localhost)

    if db or password then
        for i,slave in ipairs(slaves) do
            slave.db = db
            slave.password = password
        end
    end

    local slave, err, previous_errors = self:try_hosts(slaves)
    if not slave then
        return nil, err, previous_errors
    else
        return slave
    end
end


-- In case of errors, returns "nil, err, previous_errors" where err is
-- the last error received, and previous_errors is a table of the previous errors.
function _M.try_hosts(self, hosts)
    local errors = tbl_new(#hosts, 0)
    
    for i, host in ipairs(hosts) do
        local r
        r, errors[i] = self:connect_to_host(host)
        if r then
            return r, tbl_remove(errors), errors
        end
    end
    return nil, tbl_remove(errors), errors
end


function _M.connect_to_host(self, host)
    local r = redis.new()
    r:set_timeout(self.connect_timeout)

    local ok, err
    local socket = host.socket
    if socket then
        if self.connection_options then
            ok, err = r:connect(socket, self.connection_options)
        else
            ok, err = r:connect(socket)
        end
    else
        if self.connection_options then
            ok, err = r:connect(host.host, host.port, self.connection_options)
        else
            ok, err = r:connect(host.host, host.port)
        end
    end

    if not ok then
        ngx_log(ngx_ERR, err, " for ", host.host, ":", host.port)
        return nil, err
    else
        r:set_timeout(self, self.read_timeout)

        local password = host.password
        if password then
            local res, err = r:auth(password)
            if err then
                ngx_log(ngx_ERR, err)
                return res, err
            end
        end

        r:select(host.db)
        return r, nil
    end
end


return _M