├── README.md ├── Rakefile ├── fakengx.lua ├── sha1.lua └── spec ├── fakengx_spec.lua ├── helper.lua └── inspect.lua /README.md: -------------------------------------------------------------------------------- 1 | # FakeNGX # 2 | 3 | FakeNGX is an initial attempt to create a library for testing Lua scripts 4 | embedded into Nginx, via [HttpLuaModule](http://wiki.nginx.org/HttpLuaModule) 5 | 6 | ## Features ## 7 | 8 | * Support for basic constants: ngx.HTTP_OK, etc 9 | * Support for core functions: ngx.time(...), ngx.encode_args(...), etc 10 | * Sub-request stubbing, e.g. ngx.location.capture() 11 | * Fully tested with [Telescope](http://norman.github.com/telescope/) 12 | 13 | ## Supported functions (currently) ## 14 | 15 | * ngx.print() 16 | * ngx.say() 17 | * ngx.log() 18 | * ngx.time() 19 | * ngx.now() 20 | * ngx.exit() 21 | * ngx.escape_uri() 22 | * ngx.unescape_uri() 23 | * ngx.encode_args() 24 | * ngx.crc32_short() 25 | * ngx.location.capture() 26 | 27 | ## Usage Example ## 28 | 29 | This is an example for how to test your scripts with FakeNGX. Imagine this is 30 | your Nginx configuration: 31 | 32 | -- nginx.conf 33 | location /internal { 34 | internal; 35 | content_by_lua 'ngx.print("OK")'; 36 | } 37 | 38 | location /main { 39 | content_by_lua_file 'main.lua'; 40 | } 41 | 42 | It's good practice to keep file loaded by 43 | [content_by_lua_file](http://wiki.nginx.org/HttpLuaModule#content_by_lua_file) 44 | at a minimum and place all processing logic into external modules. This allows 45 | [lua_code_cache](http://wiki.nginx.org/HttpLuaModule#lua_code_cache) 46 | to work its magic and simplifies your testing. For example: 47 | 48 | -- main.lua 49 | local my_module = require 'my_module' 50 | my_module.run() 51 | 52 | Then, place all the controller code into separate module(s): 53 | 54 | -- my_module.lua 55 | module("my_module", package.seeall) 56 | 57 | local function respond() 58 | local res = ngx.location.capture("/internal", { method = ngx.HTTP_POST }) 59 | 60 | if res.status == ngx.HTTP_OK then 61 | ngx.print(res.body) 62 | else 63 | ngx.print("ERR") 64 | ngx.exit(500) 65 | end 66 | end 67 | 68 | function run() 69 | if ngx.var.http_authentication == "TOKEN" 70 | respond() 71 | else 72 | ngx.exit(403) 73 | end 74 | end 75 | 76 | return my_module 77 | 78 | In your tests e.g. with [Telescope](http://norman.github.com/telescope/): 79 | 80 | fakengx = require 'fakengx' 81 | my_module = require 'my_module' 82 | 83 | context('my_module', function() 84 | 85 | before(function() 86 | ngx = fakengx.new() -- create a fresh ngx on each request context 87 | end) 88 | 89 | test('run unauthenticated', function() 90 | my_module.run() 91 | assert_equal(ngx._body, '') -- No output 92 | assert_equal(ngx._exit, 403) -- Exited with 403 93 | assert_equal(ngx.status, 403) -- Response status 94 | end) 95 | 96 | test('run success', function() 97 | ngx.location.stub('/internal', {}, { body = "OK" }) 98 | ngx.var['http_authentication' = "TOKEN" 99 | my_module.run() 100 | assert_equal(ngx._body, 'OK') -- Written "OK" 101 | assert_nil(ngx._exit) -- No explicit exit 102 | assert_equal(ngx.status, 200) -- Response status 103 | end) 104 | 105 | test('run failure', function() 106 | ngx.location.stub('/internal', {}, { status = 302 }) 107 | my_module.run() 108 | assert_equal(ngx._body, 'ERR') 109 | assert_equal(ngx._exit, 500) 110 | assert_equal(ngx.status, 500) 111 | end) 112 | 113 | end) 114 | 115 | ## Prerequsites ## 116 | 117 | FakeNGX relies on the [luasocket](http://w3.impa.br/~diego/software/luasocket/) 118 | and the [bitop](http://bitop.luajit.org/) libraries. 119 | 120 | To install (on Debian, Ubuntu, etc): 121 | 122 | sudo apt-get install lua5.1 luarocks liblua5.1-socket2 liblua5.1-bitop0 liblua5.1-md5-0 123 | 124 | ## Contributing ## 125 | 126 | You need to install telescope, via LuaRocks: 127 | 128 | sudo luarocks install telescope 129 | 130 | And run: 131 | 132 | tsc -f spec/*_spec.lua 133 | 134 | 135 | ## License ## 136 | 137 | The MIT License 138 | 139 | Copyright (c) 2012 Dimitrij Denissenko 140 | 141 | Permission is hereby granted, free of charge, to any person obtaining a copy of 142 | this software and associated documentation files (the "Software"), to deal in 143 | the Software without restriction, including without limitation the rights to 144 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 145 | of the Software, and to permit persons to whom the Software is furnished to do 146 | so, subject to the following conditions: 147 | 148 | The above copyright notice and this permission notice shall be included in all 149 | copies or substantial portions of the Software. 150 | 151 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 152 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 153 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 154 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 155 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 156 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 157 | SOFTWARE. 158 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :default => [:test] 2 | 3 | task :test do 4 | sh "tsc -f spec/*_spec.lua" 5 | end -------------------------------------------------------------------------------- /fakengx.lua: -------------------------------------------------------------------------------- 1 | local bit = require 'bit' 2 | local socket = require 'socket' 3 | local sha1 = require 'sha1' 4 | local md5 = require 'md5' 5 | local mime = require 'mime' 6 | local CRC32 = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D } 7 | 8 | -- Helpers 9 | local encode_param = function(str) 10 | return tostring(str):gsub("\n", "\r\n"):gsub("([^%w_])", function (c) 11 | return string.format("%%%02X", string.byte(c)) 12 | end) 13 | end 14 | 15 | local encode_params = function(tab) 16 | local list = {} 17 | for k, v in pairs(tab) do 18 | table.insert(list, encode_param(k) .. "=" .. encode_param(v)) 19 | end 20 | return table.concat(list, "&") 21 | end 22 | 23 | local function reverse_merge(src, defs) 24 | local opts = {} 25 | for k,v in pairs(src) do opts[k] = v end 26 | for k,v in pairs(defs) do 27 | if src[k] then v = src[k] end 28 | opts[k] = v 29 | end 30 | return opts 31 | end 32 | 33 | local function stub_options(opts, method) 34 | opts = opts or {} 35 | if method and opts["method"] == nil then opts["method"] = method end 36 | if type(opts.args) == "table" then opts["args"] = encode_params(opts.args) end 37 | return opts 38 | end 39 | 40 | local function stub_response(res) 41 | return reverse_merge(res or {}, { status = 200, headers = {}, body = "" }) 42 | end 43 | 44 | local control_chars = { 45 | ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", 46 | ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\\"] = "\\\\" 47 | } 48 | 49 | local function replace_control_char(c) 50 | return control_chars[c] 51 | end 52 | 53 | local function stub_format(uri, opts) 54 | local pad = 0 55 | for k,_ in pairs(opts) do 56 | if k ~= "method" and #k > pad then pad = #k end 57 | end 58 | 59 | local msg = " " .. (opts.method or "GET") .. " " .. uri .. "\n" 60 | for k,v in pairs(opts) do 61 | if k ~= "method" then 62 | k = k .. ":" .. string.rep(" ", pad + 1 - #k) 63 | msg = msg .. " " .. k .. v:gsub("(%c)", replace_control_char) .. "\n" 64 | end 65 | end 66 | return msg 67 | end 68 | 69 | -- Capture Registry 70 | local Captures = {} 71 | 72 | function Captures:new() 73 | local this = { stubs = {} } 74 | setmetatable(this, { __index = self }) 75 | return this 76 | end 77 | 78 | function Captures:length() 79 | return #self.stubs 80 | end 81 | 82 | function Captures:each(fun) 83 | for i=self:length(),1,-1 do 84 | local stub = self.stubs[i] 85 | fun(stub) 86 | end 87 | end 88 | 89 | function Captures:find(uri, opts) 90 | opts = stub_options(opts, "GET") 91 | 92 | for i=self:length(),1,-1 do 93 | local stub = self.stubs[i] 94 | if uri == stub.uri then 95 | local is_match = true 96 | 97 | for k,v in pairs(stub.opts) do 98 | if type(v) == 'function' then 99 | is_match = v(opts[k]) 100 | elseif type(v) == 'string' and v:sub(1, 2) == "~>" then 101 | is_match = tostring(opts[k]):match(v:sub(3)) and true 102 | elseif opts[k] ~= v then 103 | is_match = false 104 | end 105 | if not is_match then break end 106 | end 107 | if is_match then return stub end 108 | end 109 | end 110 | 111 | return nil 112 | end 113 | 114 | function Captures:stub(uri, opts, res) 115 | local stub = { uri = uri, opts = stub_options(opts), res = stub_response(res), calls = {} } 116 | table.insert(self.stubs, stub) 117 | return stub 118 | end 119 | 120 | -- TCP Proxy 121 | local TCP = {} 122 | 123 | function TCP:new() 124 | return setmetatable({ host = nil, port = 0, timeout = 0, keepalive = {-1, 0}, data = {} }, { __index = self }) 125 | end 126 | 127 | function TCP:connect(host, port) 128 | self.host = host 129 | self.port = port 130 | return true, nil 131 | end 132 | 133 | function TCP:settimeout(value) 134 | self.timeout = value 135 | end 136 | 137 | function TCP:setkeepalive(...) 138 | self.keepalive = {...} 139 | end 140 | 141 | function TCP:send(msg) 142 | table.insert(self.data, msg) 143 | end 144 | 145 | -- UDP Proxy 146 | local UDP = {} 147 | 148 | function UDP:new() 149 | return setmetatable({ host = nil, port = 0, timeout = 0, data = {}, closed = false }, { __index = self }) 150 | end 151 | 152 | function UDP:setpeername(host, port) 153 | self.host = host 154 | self.port = port 155 | return true, nil 156 | end 157 | 158 | function UDP:settimeout(value) 159 | self.timeout = value 160 | end 161 | 162 | function UDP:send(msg) 163 | table.insert(self.data, msg) 164 | return true, nil 165 | end 166 | 167 | function UDP:close() 168 | self.closed = true 169 | return true, nil 170 | end 171 | 172 | -- DICT Proxy 173 | local SharedDict = {} 174 | 175 | function SharedDict:new() 176 | return setmetatable({ data = {} }, { __index = self }) 177 | end 178 | 179 | function SharedDict:get(key) 180 | return self.data[key], 0 181 | end 182 | 183 | function SharedDict:set(key, value) 184 | self.data[key] = value 185 | return true, nil, false 186 | end 187 | 188 | function SharedDict:add(key, value) 189 | if self.data[key] ~= nil then 190 | return false, "exists", false 191 | end 192 | 193 | self.data[key] = value 194 | return true, nil, false 195 | end 196 | 197 | function SharedDict:replace(key, value) 198 | if self.data[key] == nil then 199 | return false, "not found", false 200 | end 201 | 202 | self.data[key] = value 203 | return true, nil, false 204 | end 205 | 206 | function SharedDict:delete(key) 207 | self.data[key] = nil 208 | end 209 | 210 | function SharedDict:incr(key, value) 211 | if not self.data[key] then 212 | return nil, "not found" 213 | elseif type(self.data[key]) ~= "number" then 214 | return nil, "not a number" 215 | end 216 | 217 | self.data[key] = self.data[key] + value 218 | return self.data[key], nil 219 | end 220 | 221 | -- NGX Prototype 222 | local protoype = { 223 | 224 | -- Log constants 225 | STDERR = 0, 226 | EMERG = 1, 227 | ALERT = 2, 228 | CRIT = 3, 229 | ERR = 4, 230 | WARN = 5, 231 | NOTICE = 6, 232 | INFO = 7, 233 | DEBUG = 8, 234 | 235 | -- HTTP Method Constants 236 | HTTP_GET = "GET", 237 | HTTP_HEAD = "HEAD", 238 | HTTP_POST = "POST", 239 | HTTP_PUT = "PUT", 240 | HTTP_DELETE = "DELETE", 241 | 242 | -- HTTP Status Constants 243 | HTTP_OK = 200, 244 | HTTP_CREATED = 201, 245 | HTTP_ACCEPTED = 202, 246 | HTTP_NO_CONTENT = 204, 247 | HTTP_PARTIAL_CONTENT = 206, 248 | HTTP_SPECIAL_RESPONSE = 300, 249 | HTTP_MOVED_PERMANENTLY = 301, 250 | HTTP_MOVED_TEMPORARILY = 302, 251 | HTTP_SEE_OTHER = 303, 252 | HTTP_NOT_MODIFIED = 304, 253 | HTTP_BAD_REQUEST = 400, 254 | HTTP_UNAUTHORIZED = 401, 255 | HTTP_FORBIDDEN = 403, 256 | HTTP_NOT_FOUND = 404, 257 | HTTP_NOT_ALLOWED = 405, 258 | HTTP_REQUEST_TIME_OUT = 408, 259 | HTTP_CONFLICT = 409, 260 | HTTP_LENGTH_REQUIRED = 411, 261 | HTTP_PRECONDITION_FAILED = 412, 262 | HTTP_REQUEST_ENTITY_TOO_LARGE = 413, 263 | HTTP_REQUEST_URI_TOO_LARGE = 414, 264 | HTTP_UNSUPPORTED_MEDIA_TYPE = 415, 265 | HTTP_RANGE_NOT_SATISFIABLE = 416, 266 | HTTP_CLOSE = 444, 267 | HTTP_NGINX_CODES = 494, 268 | HTTP_REQUEST_HEADER_TOO_LARGE = 494, 269 | HTTP_INTERNAL_SERVER_ERROR = 500, 270 | HTTP_NOT_IMPLEMENTED = 501, 271 | HTTP_BAD_GATEWAY = 502, 272 | HTTP_SERVICE_UNAVAILABLE = 503, 273 | HTTP_GATEWAY_TIME_OUT = 504, 274 | HTTP_INSUFFICIENT_STORAGE = 507, 275 | 276 | } 277 | 278 | -- NGX Builder 279 | local fakengx = {} 280 | 281 | -- Constructor 282 | function fakengx.new() 283 | local ngx = {} 284 | for k, v in pairs(protoype) do 285 | ngx[k] = v 286 | end 287 | setmetatable(ngx, getmetatable(protoype)) 288 | 289 | -- Create namespaces 290 | ngx.req = {} 291 | ngx.re = {} 292 | ngx.socket = {} 293 | ngx.thread = {} 294 | ngx.location = {} 295 | ngx.shared = {} 296 | 297 | -- Create shared dict API 298 | setmetatable(ngx.shared, { 299 | __index = function(t, k) 300 | t[k] = SharedDict:new() 301 | return t[k] 302 | end 303 | }) 304 | 305 | function ngx._reset() 306 | ngx.status = 200 307 | ngx.var = {} 308 | ngx.ctx = {} 309 | ngx.header = {} 310 | ngx.arg = {} 311 | 312 | -- Internal Registries 313 | ngx._captures = Captures:new() 314 | ngx._sockets = {} 315 | ngx._body = "" 316 | ngx._log = "" 317 | ngx._exit = nil 318 | 319 | for k,_ in pairs(ngx.shared) do 320 | ngx.shared[k] = nil 321 | end 322 | end 323 | 324 | -- Reset once 325 | ngx._reset() 326 | 327 | -- http://wiki.nginx.org/HttpLuaModule#ngx.print 328 | function ngx.print(s) 329 | ngx._body = ngx._body .. s 330 | end 331 | 332 | -- http://wiki.nginx.org/HttpLuaModule#ngx.say 333 | function ngx.say(s) 334 | ngx.print(s .. "\n") 335 | end 336 | 337 | -- http://wiki.nginx.org/HttpLuaModule#ngx.log 338 | function ngx.log(level, ...) 339 | local args = {...} 340 | for i=1,#args do args[i] = tostring(args[i]) or "nil" end 341 | ngx._log = ngx._log .. "LOG(" .. tostring(level) .. "): " .. table.concat(args) .. "\n" 342 | end 343 | 344 | -- http://wiki.nginx.org/HttpLuaModule#ngx.time 345 | function ngx.time() 346 | if not ngx._time then 347 | ngx._time = os.time() 348 | end 349 | return ngx._time 350 | end 351 | 352 | -- http://wiki.nginx.org/HttpLuaModule#ngx.update_time 353 | function ngx.update_time() 354 | ngx._time = nil 355 | ngx._now = nil 356 | end 357 | 358 | -- http://wiki.nginx.org/HttpLuaModule#ngx.now 359 | function ngx.now() 360 | if not ngx._now then 361 | ngx._now = socket.gettime() 362 | end 363 | return ngx._now 364 | end 365 | 366 | -- http://wiki.nginx.org/HttpLuaModule#ngx.cookie_time 367 | function ngx.cookie_time(t) 368 | return os.date('!%a, %d-%b-%Y %H:%M:%S GMT', t) 369 | end 370 | 371 | -- http://wiki.nginx.org/HttpLuaModule#ngx.exit 372 | function ngx.exit(status) 373 | if status > ngx.status then ngx.status = status end 374 | ngx._exit = status 375 | end 376 | 377 | -- http://wiki.nginx.org/HttpLuaModule#ngx.crc32_short 378 | function ngx.crc32_short(s) 379 | local crc, l, i = 0xFFFFFFFF, string.len(s) 380 | for i = 1, l, 1 do 381 | crc = bit.bxor(bit.rshift(crc, 8), CRC32[bit.band(bit.bxor(crc, string.byte(s, i)), 0xFF) + 1]) 382 | end 383 | return bit.bxor(crc, -1) % 2^32 384 | end 385 | 386 | -- http://wiki.nginx.org/HttpLuaModule#ngx.hmac_sha1 387 | function ngx.hmac_sha1(secret_key, str) 388 | return sha1.hmac_sha1_binary(secret_key, str) 389 | end 390 | 391 | -- http://wiki.nginx.org/HttpLuaModule#ngx.sha1_bin 392 | function ngx.sha1_bin(str) 393 | return sha1.sha1_binary(str) 394 | end 395 | 396 | -- http://wiki.nginx.org/HttpLuaModule#ngx.md5 397 | function ngx.md5(str) 398 | return md5.sumhexa(str) 399 | end 400 | 401 | -- http://wiki.nginx.org/HttpLuaModule#ngx.md5_bin 402 | function ngx.md5_bin(str) 403 | return md5.sum(str) 404 | end 405 | 406 | -- http://wiki.nginx.org/HttpLuaModule#ngx.escape_uri 407 | function ngx.escape_uri(str) 408 | return tostring(str):gsub("\n", "\r\n"):gsub("([^%w_ ])", function (c) 409 | return string.format("%%%02X", string.byte(c)) 410 | end):gsub(" ", "+") 411 | end 412 | 413 | -- http://wiki.nginx.org/HttpLuaModule#ngx.unescape_uri 414 | function ngx.unescape_uri(str) 415 | return tostring(str):gsub("+", " "):gsub("\r\n", "\n"):gsub("%%(%x%x)", function(h) 416 | return string.char(tonumber(h,16)) 417 | end) 418 | end 419 | 420 | -- http://wiki.nginx.org/HttpLuaModule#ngx.encode_args 421 | function ngx.encode_args(tab) 422 | return encode_params(tab) 423 | end 424 | 425 | -- http://wiki.nginx.org/HttpLuaModule#ngx.location.capture 426 | function ngx.location.capture(uri, opts) 427 | local stub = ngx._captures:find(uri, opts) 428 | if not stub then 429 | local msg = "\n\nUnstubbed request:\n\n" .. stub_format(uri, opts or {}) .. "\nStubbed were:\n" 430 | ngx._captures:each(function(stub) 431 | msg = msg .. "\n" .. stub_format(stub.uri, stub.opts or {}) 432 | end) 433 | error(msg) 434 | end 435 | 436 | table.insert(stub.calls, { uri = uri, opts = opts }) 437 | return stub.res 438 | end 439 | 440 | -- http://wiki.nginx.org/HttpLuaModule#ngx.location.capture_multi 441 | function ngx.location.capture_multi(...) 442 | local requests = ... 443 | local responses = {} 444 | for i, request in ipairs(requests) do 445 | table.insert(responses, ngx.location.capture(request[1], request[2])) 446 | end 447 | return unpack(responses) 448 | end 449 | 450 | -- Stub a capture 451 | function ngx.location.stub(...) 452 | return ngx._captures:stub(...) 453 | end 454 | 455 | -- http://wiki.nginx.org/HttpLuaModule#ngx.req.read_body 456 | function ngx.req.read_body() 457 | end 458 | 459 | -- http://wiki.nginx.org/HttpLuaModule#ngx.socket.tcp 460 | function ngx.socket.tcp() 461 | local sock = TCP:new() 462 | table.insert(ngx._sockets, sock) 463 | return sock 464 | end 465 | 466 | -- http://wiki.nginx.org/HttpLuaModule#ngx.socket.udp 467 | function ngx.socket.udp() 468 | local sock = UDP:new() 469 | table.insert(ngx._sockets, sock) 470 | return sock 471 | end 472 | 473 | -- http://wiki.nginx.org/HttpLuaModule#ngx.encode_base64 474 | function ngx.encode_base64(s) 475 | return mime.b64(s) 476 | end 477 | 478 | -- http://wiki.nginx.org/HttpLuaModule#ngx.decode_base64 479 | function ngx.decode_base64(s) 480 | return mime.unb64(s) 481 | end 482 | 483 | -- http://wiki.nginx.org/HttpLuaModule#ngx.thread.spawn 484 | function ngx.thread.spawn(fun, ...) 485 | return { fun = fun, args = {...} } 486 | end 487 | 488 | -- http://wiki.nginx.org/HttpLuaModule#ngx.thread.wait 489 | function ngx.thread.wait(thread) 490 | return true, thread.fun(unpack(thread.args)) 491 | end 492 | 493 | -- http://wiki.nginx.org/HttpLuaModule#ngx.re.gmatch 494 | function ngx.re.gmatch(s, pattern) 495 | return string.gmatch(s, pattern) 496 | end 497 | 498 | -- http://wiki.nginx.org/HttpLuaModule#ngx.re.match 499 | function ngx.re.match(s, pattern) 500 | return string.match(s, pattern) 501 | end 502 | 503 | return ngx 504 | end 505 | 506 | return fakengx 507 | -------------------------------------------------------------------------------- /sha1.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- SHA-1 secure hash computation, and HMAC-SHA1 signature computation, 3 | -- in pure Lua (tested on Lua 5.1) 4 | -- 5 | -- Latest version always at: http://regex.info/blog/lua/sha1 6 | -- 7 | -- Copyright 2009 Jeffrey Friedl 8 | -- jfriedl@yahoo.com 9 | -- http://regex.info/blog/ 10 | -- 11 | -- 12 | -- Version 1 [May 28, 2009] 13 | -- 14 | -- 15 | -- Lua is a pathetic, horrid, turd of a language. Not only doesn't it have 16 | -- bitwise integer operators like OR and AND, it doesn't even have integers 17 | -- (and those, relatively speaking, are its good points). Yet, this 18 | -- implements the SHA-1 digest hash in pure Lua. While coding it, I felt as 19 | -- if I were chiseling NAND gates out of rough blocks of silicon. Those not 20 | -- already familiar with this woeful language may, upon seeing this code, 21 | -- throw up in their own mouth. 22 | -- 23 | -- It's not super fast.... a 10k-byte message takes about 2 seconds on a 24 | -- circa-2008 mid-level server, but it should be plenty adequate for short 25 | -- messages, such as is often needed during authentication handshaking. 26 | -- 27 | -- Algorithm: http://www.itl.nist.gov/fipspubs/fip180-1.htm 28 | -- 29 | -- This file creates four entries in the global namespace: 30 | -- 31 | -- local hash_as_hex = sha1(message) -- returns a hex string 32 | -- local hash_as_data = sha1_binary(message) -- returns raw bytes 33 | -- 34 | -- local hmac_as_hex = hmac_sha1(key, message) -- hex string 35 | -- local hmac_as_data = hmac_sha1_binary(key, message) -- raw bytes 36 | -- 37 | -- Pass sha1() a string, and it returns a hash as a 40-character hex string. 38 | -- For example, the call 39 | -- 40 | -- local hash = sha1 "http://regex.info/blog/" 41 | -- 42 | -- puts the 40-character string 43 | -- 44 | -- "7f103bf600de51dfe91062300c14738b32725db5" 45 | -- 46 | -- into the variable 'hash' 47 | -- 48 | -- Pass sha1_hmac() a key and a message, and it returns the signature as a 49 | -- 40-byte hex string. 50 | -- 51 | -- 52 | -- The two "_binary" versions do the same, but return the 20-byte string of raw data 53 | -- that the 40-byte hex strings represent. 54 | -- 55 | 56 | ------------------------------------------------------------------------------------------ 57 | ------------------------------------------------------------------------------------------ 58 | 59 | 60 | -- 61 | -- Return a W32 object for the number zero 62 | -- 63 | local function ZERO() 64 | return { 65 | false, false, false, false, false, false, false, false, 66 | false, false, false, false, false, false, false, false, 67 | false, false, false, false, false, false, false, false, 68 | false, false, false, false, false, false, false, false, 69 | } 70 | end 71 | 72 | local hex_to_bits = { 73 | ["0"] = { false, false, false, false }, 74 | ["1"] = { false, false, false, true }, 75 | ["2"] = { false, false, true, false }, 76 | ["3"] = { false, false, true, true }, 77 | 78 | ["4"] = { false, true, false, false }, 79 | ["5"] = { false, true, false, true }, 80 | ["6"] = { false, true, true, false }, 81 | ["7"] = { false, true, true, true }, 82 | 83 | ["8"] = { true, false, false, false }, 84 | ["9"] = { true, false, false, true }, 85 | ["A"] = { true, false, true, false }, 86 | ["B"] = { true, false, true, true }, 87 | 88 | ["C"] = { true, true, false, false }, 89 | ["D"] = { true, true, false, true }, 90 | ["E"] = { true, true, true, false }, 91 | ["F"] = { true, true, true, true }, 92 | 93 | ["a"] = { true, false, true, false }, 94 | ["b"] = { true, false, true, true }, 95 | ["c"] = { true, true, false, false }, 96 | ["d"] = { true, true, false, true }, 97 | ["e"] = { true, true, true, false }, 98 | ["f"] = { true, true, true, true }, 99 | } 100 | 101 | -- 102 | -- Given a string of 8 hex digits, return a W32 object representing that number 103 | -- 104 | local function from_hex(hex) 105 | 106 | assert(type(hex) == 'string') 107 | assert(hex:match('^[0123456789abcdefABCDEF]+$')) 108 | assert(#hex == 8) 109 | 110 | local W32 = { } 111 | 112 | for letter in hex:gmatch('.') do 113 | local b = hex_to_bits[letter] 114 | assert(b) 115 | table.insert(W32, 1, b[1]) 116 | table.insert(W32, 1, b[2]) 117 | table.insert(W32, 1, b[3]) 118 | table.insert(W32, 1, b[4]) 119 | end 120 | 121 | return W32 122 | end 123 | 124 | local function COPY(old) 125 | local W32 = { } 126 | for k,v in pairs(old) do 127 | W32[k] = v 128 | end 129 | 130 | return W32 131 | end 132 | 133 | local function ADD(first, ...) 134 | 135 | local a = COPY(first) 136 | 137 | local C, b, sum 138 | 139 | for v = 1, select('#', ...) do 140 | b = select(v, ...) 141 | C = 0 142 | 143 | for i = 1, #a do 144 | sum = (a[i] and 1 or 0) 145 | + (b[i] and 1 or 0) 146 | + C 147 | 148 | if sum == 0 then 149 | a[i] = false 150 | C = 0 151 | elseif sum == 1 then 152 | a[i] = true 153 | C = 0 154 | elseif sum == 2 then 155 | a[i] = false 156 | C = 1 157 | else 158 | a[i] = true 159 | C = 1 160 | end 161 | end 162 | -- we drop any ending carry 163 | 164 | end 165 | 166 | return a 167 | end 168 | 169 | local function XOR(first, ...) 170 | 171 | local a = COPY(first) 172 | local b 173 | for v = 1, select('#', ...) do 174 | b = select(v, ...) 175 | for i = 1, #a do 176 | a[i] = a[i] ~= b[i] 177 | end 178 | end 179 | 180 | return a 181 | 182 | end 183 | 184 | local function AND(a, b) 185 | 186 | local c = ZERO() 187 | 188 | for i = 1, #a do 189 | -- only need to set true bits; other bits remain false 190 | if a[i] and b[i] then 191 | c[i] = true 192 | end 193 | end 194 | 195 | return c 196 | end 197 | 198 | local function OR(a, b) 199 | 200 | local c = ZERO() 201 | 202 | for i = 1, #a do 203 | -- only need to set true bits; other bits remain false 204 | if a[i] or b[i] then 205 | c[i] = true 206 | end 207 | end 208 | 209 | return c 210 | end 211 | 212 | local function OR3(a, b, c) 213 | 214 | local d = ZERO() 215 | 216 | for i = 1, #a do 217 | -- only need to set true bits; other bits remain false 218 | if a[i] or b[i] or c[i] then 219 | d[i] = true 220 | end 221 | end 222 | 223 | return d 224 | end 225 | 226 | local function NOT(a) 227 | 228 | local b = ZERO() 229 | 230 | for i = 1, #a do 231 | -- only need to set true bits; other bits remain false 232 | if not a[i] then 233 | b[i] = true 234 | end 235 | end 236 | 237 | return b 238 | end 239 | 240 | local function ROTATE(bits, a) 241 | 242 | local b = COPY(a) 243 | 244 | while bits > 0 do 245 | bits = bits - 1 246 | table.insert(b, 1, table.remove(b)) 247 | end 248 | 249 | return b 250 | 251 | end 252 | 253 | 254 | local binary_to_hex = { 255 | ["0000"] = "0", 256 | ["0001"] = "1", 257 | ["0010"] = "2", 258 | ["0011"] = "3", 259 | ["0100"] = "4", 260 | ["0101"] = "5", 261 | ["0110"] = "6", 262 | ["0111"] = "7", 263 | ["1000"] = "8", 264 | ["1001"] = "9", 265 | ["1010"] = "a", 266 | ["1011"] = "b", 267 | ["1100"] = "c", 268 | ["1101"] = "d", 269 | ["1110"] = "e", 270 | ["1111"] = "f", 271 | } 272 | 273 | local sha1 = {} 274 | 275 | function sha1.asHEX(a) 276 | 277 | local hex = "" 278 | local i = 1 279 | while i < #a do 280 | local binary = (a[i + 3] and '1' or '0') 281 | .. 282 | (a[i + 2] and '1' or '0') 283 | .. 284 | (a[i + 1] and '1' or '0') 285 | .. 286 | (a[i + 0] and '1' or '0') 287 | 288 | hex = binary_to_hex[binary] .. hex 289 | 290 | i = i + 4 291 | end 292 | 293 | return hex 294 | 295 | end 296 | 297 | local x67452301 = from_hex("67452301") 298 | local xEFCDAB89 = from_hex("EFCDAB89") 299 | local x98BADCFE = from_hex("98BADCFE") 300 | local x10325476 = from_hex("10325476") 301 | local xC3D2E1F0 = from_hex("C3D2E1F0") 302 | 303 | local x5A827999 = from_hex("5A827999") 304 | local x6ED9EBA1 = from_hex("6ED9EBA1") 305 | local x8F1BBCDC = from_hex("8F1BBCDC") 306 | local xCA62C1D6 = from_hex("CA62C1D6") 307 | 308 | 309 | function sha1.sha1(msg) 310 | 311 | assert(type(msg) == 'string') 312 | assert(#msg < 0x7FFFFFFF) -- have no idea what would happen if it were large 313 | 314 | local H0 = x67452301 315 | local H1 = xEFCDAB89 316 | local H2 = x98BADCFE 317 | local H3 = x10325476 318 | local H4 = xC3D2E1F0 319 | 320 | local msg_len_in_bits = #msg * 8 321 | 322 | local first_append = string.char(0x80) -- append a '1' bit plus seven '0' bits 323 | 324 | local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length 325 | local current_mod = non_zero_message_bytes % 64 326 | local second_append = "" 327 | if current_mod ~= 0 then 328 | second_append = string.rep(string.char(0), 64 - current_mod) 329 | end 330 | 331 | -- now to append the length as a 64-bit number. 332 | local B1, R1 = math.modf(msg_len_in_bits / 0x01000000) 333 | local B2, R2 = math.modf( 0x01000000 * R1 / 0x00010000) 334 | local B3, R3 = math.modf( 0x00010000 * R2 / 0x00000100) 335 | local B4 = 0x00000100 * R3 336 | 337 | local L64 = string.char( 0) .. string.char( 0) .. string.char( 0) .. string.char( 0) -- high 32 bits 338 | .. string.char(B1) .. string.char(B2) .. string.char(B3) .. string.char(B4) -- low 32 bits 339 | 340 | 341 | 342 | msg = msg .. first_append .. second_append .. L64 343 | 344 | assert(#msg % 64 == 0) 345 | 346 | --local fd = io.open("/tmp/msg", "wb") 347 | --fd:write(msg) 348 | --fd:close() 349 | 350 | local chunks = #msg / 64 351 | 352 | local W = { } 353 | local start, A, B, C, D, E, f, K, TEMP 354 | local chunk = 0 355 | 356 | while chunk < chunks do 357 | -- 358 | -- break chunk up into W[0] through W[15] 359 | -- 360 | start = chunk * 64 + 1 361 | chunk = chunk + 1 362 | 363 | for t = 0, 15 do 364 | W[t] = from_hex(string.format("%02x%02x%02x%02x", msg:byte(start, start + 3))) 365 | start = start + 4 366 | end 367 | 368 | -- 369 | -- build W[16] through W[79] 370 | -- 371 | for t = 16, 79 do 372 | -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16). 373 | W[t] = ROTATE(1, XOR(W[t-3], W[t-8], W[t-14], W[t-16])) 374 | end 375 | 376 | A = H0 377 | B = H1 378 | C = H2 379 | D = H3 380 | E = H4 381 | 382 | for t = 0, 79 do 383 | if t <= 19 then 384 | -- (B AND C) OR ((NOT B) AND D) 385 | f = OR(AND(B, C), AND(NOT(B), D)) 386 | K = x5A827999 387 | elseif t <= 39 then 388 | -- B XOR C XOR D 389 | f = XOR(B, C, D) 390 | K = x6ED9EBA1 391 | elseif t <= 59 then 392 | -- (B AND C) OR (B AND D) OR (C AND D 393 | f = OR3(AND(B, C), AND(B, D), AND(C, D)) 394 | K = x8F1BBCDC 395 | else 396 | -- B XOR C XOR D 397 | f = XOR(B, C, D) 398 | K = xCA62C1D6 399 | end 400 | 401 | -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; 402 | TEMP = ADD(ROTATE(5, A), f, E, W[t], K) 403 | 404 | --E = D; D = C; C = S30(B); B = A; A = TEMP; 405 | E = D 406 | D = C 407 | C = ROTATE(30, B) 408 | B = A 409 | A = TEMP 410 | 411 | --printf("t = %2d: %s %s %s %s %s", t, A:HEX(), B:HEX(), C:HEX(), D:HEX(), E:HEX()) 412 | end 413 | 414 | -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. 415 | H0 = ADD(H0, A) 416 | H1 = ADD(H1, B) 417 | H2 = ADD(H2, C) 418 | H3 = ADD(H3, D) 419 | H4 = ADD(H4, E) 420 | end 421 | 422 | return sha1.asHEX(H0) .. sha1.asHEX(H1) .. sha1.asHEX(H2) .. sha1.asHEX(H3) .. sha1.asHEX(H4) 423 | end 424 | 425 | local function hex_to_binary(hex) 426 | return hex:gsub('..', function(hexval) 427 | return string.char(tonumber(hexval, 16)) 428 | end) 429 | end 430 | 431 | function sha1.sha1_binary(msg) 432 | return hex_to_binary(sha1.sha1(msg)) 433 | end 434 | 435 | local xor_with_0x5c = { 436 | [string.char( 0)] = string.char( 92), [string.char( 1)] = string.char( 93), 437 | [string.char( 2)] = string.char( 94), [string.char( 3)] = string.char( 95), 438 | [string.char( 4)] = string.char( 88), [string.char( 5)] = string.char( 89), 439 | [string.char( 6)] = string.char( 90), [string.char( 7)] = string.char( 91), 440 | [string.char( 8)] = string.char( 84), [string.char( 9)] = string.char( 85), 441 | [string.char( 10)] = string.char( 86), [string.char( 11)] = string.char( 87), 442 | [string.char( 12)] = string.char( 80), [string.char( 13)] = string.char( 81), 443 | [string.char( 14)] = string.char( 82), [string.char( 15)] = string.char( 83), 444 | [string.char( 16)] = string.char( 76), [string.char( 17)] = string.char( 77), 445 | [string.char( 18)] = string.char( 78), [string.char( 19)] = string.char( 79), 446 | [string.char( 20)] = string.char( 72), [string.char( 21)] = string.char( 73), 447 | [string.char( 22)] = string.char( 74), [string.char( 23)] = string.char( 75), 448 | [string.char( 24)] = string.char( 68), [string.char( 25)] = string.char( 69), 449 | [string.char( 26)] = string.char( 70), [string.char( 27)] = string.char( 71), 450 | [string.char( 28)] = string.char( 64), [string.char( 29)] = string.char( 65), 451 | [string.char( 30)] = string.char( 66), [string.char( 31)] = string.char( 67), 452 | [string.char( 32)] = string.char(124), [string.char( 33)] = string.char(125), 453 | [string.char( 34)] = string.char(126), [string.char( 35)] = string.char(127), 454 | [string.char( 36)] = string.char(120), [string.char( 37)] = string.char(121), 455 | [string.char( 38)] = string.char(122), [string.char( 39)] = string.char(123), 456 | [string.char( 40)] = string.char(116), [string.char( 41)] = string.char(117), 457 | [string.char( 42)] = string.char(118), [string.char( 43)] = string.char(119), 458 | [string.char( 44)] = string.char(112), [string.char( 45)] = string.char(113), 459 | [string.char( 46)] = string.char(114), [string.char( 47)] = string.char(115), 460 | [string.char( 48)] = string.char(108), [string.char( 49)] = string.char(109), 461 | [string.char( 50)] = string.char(110), [string.char( 51)] = string.char(111), 462 | [string.char( 52)] = string.char(104), [string.char( 53)] = string.char(105), 463 | [string.char( 54)] = string.char(106), [string.char( 55)] = string.char(107), 464 | [string.char( 56)] = string.char(100), [string.char( 57)] = string.char(101), 465 | [string.char( 58)] = string.char(102), [string.char( 59)] = string.char(103), 466 | [string.char( 60)] = string.char( 96), [string.char( 61)] = string.char( 97), 467 | [string.char( 62)] = string.char( 98), [string.char( 63)] = string.char( 99), 468 | [string.char( 64)] = string.char( 28), [string.char( 65)] = string.char( 29), 469 | [string.char( 66)] = string.char( 30), [string.char( 67)] = string.char( 31), 470 | [string.char( 68)] = string.char( 24), [string.char( 69)] = string.char( 25), 471 | [string.char( 70)] = string.char( 26), [string.char( 71)] = string.char( 27), 472 | [string.char( 72)] = string.char( 20), [string.char( 73)] = string.char( 21), 473 | [string.char( 74)] = string.char( 22), [string.char( 75)] = string.char( 23), 474 | [string.char( 76)] = string.char( 16), [string.char( 77)] = string.char( 17), 475 | [string.char( 78)] = string.char( 18), [string.char( 79)] = string.char( 19), 476 | [string.char( 80)] = string.char( 12), [string.char( 81)] = string.char( 13), 477 | [string.char( 82)] = string.char( 14), [string.char( 83)] = string.char( 15), 478 | [string.char( 84)] = string.char( 8), [string.char( 85)] = string.char( 9), 479 | [string.char( 86)] = string.char( 10), [string.char( 87)] = string.char( 11), 480 | [string.char( 88)] = string.char( 4), [string.char( 89)] = string.char( 5), 481 | [string.char( 90)] = string.char( 6), [string.char( 91)] = string.char( 7), 482 | [string.char( 92)] = string.char( 0), [string.char( 93)] = string.char( 1), 483 | [string.char( 94)] = string.char( 2), [string.char( 95)] = string.char( 3), 484 | [string.char( 96)] = string.char( 60), [string.char( 97)] = string.char( 61), 485 | [string.char( 98)] = string.char( 62), [string.char( 99)] = string.char( 63), 486 | [string.char(100)] = string.char( 56), [string.char(101)] = string.char( 57), 487 | [string.char(102)] = string.char( 58), [string.char(103)] = string.char( 59), 488 | [string.char(104)] = string.char( 52), [string.char(105)] = string.char( 53), 489 | [string.char(106)] = string.char( 54), [string.char(107)] = string.char( 55), 490 | [string.char(108)] = string.char( 48), [string.char(109)] = string.char( 49), 491 | [string.char(110)] = string.char( 50), [string.char(111)] = string.char( 51), 492 | [string.char(112)] = string.char( 44), [string.char(113)] = string.char( 45), 493 | [string.char(114)] = string.char( 46), [string.char(115)] = string.char( 47), 494 | [string.char(116)] = string.char( 40), [string.char(117)] = string.char( 41), 495 | [string.char(118)] = string.char( 42), [string.char(119)] = string.char( 43), 496 | [string.char(120)] = string.char( 36), [string.char(121)] = string.char( 37), 497 | [string.char(122)] = string.char( 38), [string.char(123)] = string.char( 39), 498 | [string.char(124)] = string.char( 32), [string.char(125)] = string.char( 33), 499 | [string.char(126)] = string.char( 34), [string.char(127)] = string.char( 35), 500 | [string.char(128)] = string.char(220), [string.char(129)] = string.char(221), 501 | [string.char(130)] = string.char(222), [string.char(131)] = string.char(223), 502 | [string.char(132)] = string.char(216), [string.char(133)] = string.char(217), 503 | [string.char(134)] = string.char(218), [string.char(135)] = string.char(219), 504 | [string.char(136)] = string.char(212), [string.char(137)] = string.char(213), 505 | [string.char(138)] = string.char(214), [string.char(139)] = string.char(215), 506 | [string.char(140)] = string.char(208), [string.char(141)] = string.char(209), 507 | [string.char(142)] = string.char(210), [string.char(143)] = string.char(211), 508 | [string.char(144)] = string.char(204), [string.char(145)] = string.char(205), 509 | [string.char(146)] = string.char(206), [string.char(147)] = string.char(207), 510 | [string.char(148)] = string.char(200), [string.char(149)] = string.char(201), 511 | [string.char(150)] = string.char(202), [string.char(151)] = string.char(203), 512 | [string.char(152)] = string.char(196), [string.char(153)] = string.char(197), 513 | [string.char(154)] = string.char(198), [string.char(155)] = string.char(199), 514 | [string.char(156)] = string.char(192), [string.char(157)] = string.char(193), 515 | [string.char(158)] = string.char(194), [string.char(159)] = string.char(195), 516 | [string.char(160)] = string.char(252), [string.char(161)] = string.char(253), 517 | [string.char(162)] = string.char(254), [string.char(163)] = string.char(255), 518 | [string.char(164)] = string.char(248), [string.char(165)] = string.char(249), 519 | [string.char(166)] = string.char(250), [string.char(167)] = string.char(251), 520 | [string.char(168)] = string.char(244), [string.char(169)] = string.char(245), 521 | [string.char(170)] = string.char(246), [string.char(171)] = string.char(247), 522 | [string.char(172)] = string.char(240), [string.char(173)] = string.char(241), 523 | [string.char(174)] = string.char(242), [string.char(175)] = string.char(243), 524 | [string.char(176)] = string.char(236), [string.char(177)] = string.char(237), 525 | [string.char(178)] = string.char(238), [string.char(179)] = string.char(239), 526 | [string.char(180)] = string.char(232), [string.char(181)] = string.char(233), 527 | [string.char(182)] = string.char(234), [string.char(183)] = string.char(235), 528 | [string.char(184)] = string.char(228), [string.char(185)] = string.char(229), 529 | [string.char(186)] = string.char(230), [string.char(187)] = string.char(231), 530 | [string.char(188)] = string.char(224), [string.char(189)] = string.char(225), 531 | [string.char(190)] = string.char(226), [string.char(191)] = string.char(227), 532 | [string.char(192)] = string.char(156), [string.char(193)] = string.char(157), 533 | [string.char(194)] = string.char(158), [string.char(195)] = string.char(159), 534 | [string.char(196)] = string.char(152), [string.char(197)] = string.char(153), 535 | [string.char(198)] = string.char(154), [string.char(199)] = string.char(155), 536 | [string.char(200)] = string.char(148), [string.char(201)] = string.char(149), 537 | [string.char(202)] = string.char(150), [string.char(203)] = string.char(151), 538 | [string.char(204)] = string.char(144), [string.char(205)] = string.char(145), 539 | [string.char(206)] = string.char(146), [string.char(207)] = string.char(147), 540 | [string.char(208)] = string.char(140), [string.char(209)] = string.char(141), 541 | [string.char(210)] = string.char(142), [string.char(211)] = string.char(143), 542 | [string.char(212)] = string.char(136), [string.char(213)] = string.char(137), 543 | [string.char(214)] = string.char(138), [string.char(215)] = string.char(139), 544 | [string.char(216)] = string.char(132), [string.char(217)] = string.char(133), 545 | [string.char(218)] = string.char(134), [string.char(219)] = string.char(135), 546 | [string.char(220)] = string.char(128), [string.char(221)] = string.char(129), 547 | [string.char(222)] = string.char(130), [string.char(223)] = string.char(131), 548 | [string.char(224)] = string.char(188), [string.char(225)] = string.char(189), 549 | [string.char(226)] = string.char(190), [string.char(227)] = string.char(191), 550 | [string.char(228)] = string.char(184), [string.char(229)] = string.char(185), 551 | [string.char(230)] = string.char(186), [string.char(231)] = string.char(187), 552 | [string.char(232)] = string.char(180), [string.char(233)] = string.char(181), 553 | [string.char(234)] = string.char(182), [string.char(235)] = string.char(183), 554 | [string.char(236)] = string.char(176), [string.char(237)] = string.char(177), 555 | [string.char(238)] = string.char(178), [string.char(239)] = string.char(179), 556 | [string.char(240)] = string.char(172), [string.char(241)] = string.char(173), 557 | [string.char(242)] = string.char(174), [string.char(243)] = string.char(175), 558 | [string.char(244)] = string.char(168), [string.char(245)] = string.char(169), 559 | [string.char(246)] = string.char(170), [string.char(247)] = string.char(171), 560 | [string.char(248)] = string.char(164), [string.char(249)] = string.char(165), 561 | [string.char(250)] = string.char(166), [string.char(251)] = string.char(167), 562 | [string.char(252)] = string.char(160), [string.char(253)] = string.char(161), 563 | [string.char(254)] = string.char(162), [string.char(255)] = string.char(163), 564 | } 565 | 566 | local xor_with_0x36 = { 567 | [string.char( 0)] = string.char( 54), [string.char( 1)] = string.char( 55), 568 | [string.char( 2)] = string.char( 52), [string.char( 3)] = string.char( 53), 569 | [string.char( 4)] = string.char( 50), [string.char( 5)] = string.char( 51), 570 | [string.char( 6)] = string.char( 48), [string.char( 7)] = string.char( 49), 571 | [string.char( 8)] = string.char( 62), [string.char( 9)] = string.char( 63), 572 | [string.char( 10)] = string.char( 60), [string.char( 11)] = string.char( 61), 573 | [string.char( 12)] = string.char( 58), [string.char( 13)] = string.char( 59), 574 | [string.char( 14)] = string.char( 56), [string.char( 15)] = string.char( 57), 575 | [string.char( 16)] = string.char( 38), [string.char( 17)] = string.char( 39), 576 | [string.char( 18)] = string.char( 36), [string.char( 19)] = string.char( 37), 577 | [string.char( 20)] = string.char( 34), [string.char( 21)] = string.char( 35), 578 | [string.char( 22)] = string.char( 32), [string.char( 23)] = string.char( 33), 579 | [string.char( 24)] = string.char( 46), [string.char( 25)] = string.char( 47), 580 | [string.char( 26)] = string.char( 44), [string.char( 27)] = string.char( 45), 581 | [string.char( 28)] = string.char( 42), [string.char( 29)] = string.char( 43), 582 | [string.char( 30)] = string.char( 40), [string.char( 31)] = string.char( 41), 583 | [string.char( 32)] = string.char( 22), [string.char( 33)] = string.char( 23), 584 | [string.char( 34)] = string.char( 20), [string.char( 35)] = string.char( 21), 585 | [string.char( 36)] = string.char( 18), [string.char( 37)] = string.char( 19), 586 | [string.char( 38)] = string.char( 16), [string.char( 39)] = string.char( 17), 587 | [string.char( 40)] = string.char( 30), [string.char( 41)] = string.char( 31), 588 | [string.char( 42)] = string.char( 28), [string.char( 43)] = string.char( 29), 589 | [string.char( 44)] = string.char( 26), [string.char( 45)] = string.char( 27), 590 | [string.char( 46)] = string.char( 24), [string.char( 47)] = string.char( 25), 591 | [string.char( 48)] = string.char( 6), [string.char( 49)] = string.char( 7), 592 | [string.char( 50)] = string.char( 4), [string.char( 51)] = string.char( 5), 593 | [string.char( 52)] = string.char( 2), [string.char( 53)] = string.char( 3), 594 | [string.char( 54)] = string.char( 0), [string.char( 55)] = string.char( 1), 595 | [string.char( 56)] = string.char( 14), [string.char( 57)] = string.char( 15), 596 | [string.char( 58)] = string.char( 12), [string.char( 59)] = string.char( 13), 597 | [string.char( 60)] = string.char( 10), [string.char( 61)] = string.char( 11), 598 | [string.char( 62)] = string.char( 8), [string.char( 63)] = string.char( 9), 599 | [string.char( 64)] = string.char(118), [string.char( 65)] = string.char(119), 600 | [string.char( 66)] = string.char(116), [string.char( 67)] = string.char(117), 601 | [string.char( 68)] = string.char(114), [string.char( 69)] = string.char(115), 602 | [string.char( 70)] = string.char(112), [string.char( 71)] = string.char(113), 603 | [string.char( 72)] = string.char(126), [string.char( 73)] = string.char(127), 604 | [string.char( 74)] = string.char(124), [string.char( 75)] = string.char(125), 605 | [string.char( 76)] = string.char(122), [string.char( 77)] = string.char(123), 606 | [string.char( 78)] = string.char(120), [string.char( 79)] = string.char(121), 607 | [string.char( 80)] = string.char(102), [string.char( 81)] = string.char(103), 608 | [string.char( 82)] = string.char(100), [string.char( 83)] = string.char(101), 609 | [string.char( 84)] = string.char( 98), [string.char( 85)] = string.char( 99), 610 | [string.char( 86)] = string.char( 96), [string.char( 87)] = string.char( 97), 611 | [string.char( 88)] = string.char(110), [string.char( 89)] = string.char(111), 612 | [string.char( 90)] = string.char(108), [string.char( 91)] = string.char(109), 613 | [string.char( 92)] = string.char(106), [string.char( 93)] = string.char(107), 614 | [string.char( 94)] = string.char(104), [string.char( 95)] = string.char(105), 615 | [string.char( 96)] = string.char( 86), [string.char( 97)] = string.char( 87), 616 | [string.char( 98)] = string.char( 84), [string.char( 99)] = string.char( 85), 617 | [string.char(100)] = string.char( 82), [string.char(101)] = string.char( 83), 618 | [string.char(102)] = string.char( 80), [string.char(103)] = string.char( 81), 619 | [string.char(104)] = string.char( 94), [string.char(105)] = string.char( 95), 620 | [string.char(106)] = string.char( 92), [string.char(107)] = string.char( 93), 621 | [string.char(108)] = string.char( 90), [string.char(109)] = string.char( 91), 622 | [string.char(110)] = string.char( 88), [string.char(111)] = string.char( 89), 623 | [string.char(112)] = string.char( 70), [string.char(113)] = string.char( 71), 624 | [string.char(114)] = string.char( 68), [string.char(115)] = string.char( 69), 625 | [string.char(116)] = string.char( 66), [string.char(117)] = string.char( 67), 626 | [string.char(118)] = string.char( 64), [string.char(119)] = string.char( 65), 627 | [string.char(120)] = string.char( 78), [string.char(121)] = string.char( 79), 628 | [string.char(122)] = string.char( 76), [string.char(123)] = string.char( 77), 629 | [string.char(124)] = string.char( 74), [string.char(125)] = string.char( 75), 630 | [string.char(126)] = string.char( 72), [string.char(127)] = string.char( 73), 631 | [string.char(128)] = string.char(182), [string.char(129)] = string.char(183), 632 | [string.char(130)] = string.char(180), [string.char(131)] = string.char(181), 633 | [string.char(132)] = string.char(178), [string.char(133)] = string.char(179), 634 | [string.char(134)] = string.char(176), [string.char(135)] = string.char(177), 635 | [string.char(136)] = string.char(190), [string.char(137)] = string.char(191), 636 | [string.char(138)] = string.char(188), [string.char(139)] = string.char(189), 637 | [string.char(140)] = string.char(186), [string.char(141)] = string.char(187), 638 | [string.char(142)] = string.char(184), [string.char(143)] = string.char(185), 639 | [string.char(144)] = string.char(166), [string.char(145)] = string.char(167), 640 | [string.char(146)] = string.char(164), [string.char(147)] = string.char(165), 641 | [string.char(148)] = string.char(162), [string.char(149)] = string.char(163), 642 | [string.char(150)] = string.char(160), [string.char(151)] = string.char(161), 643 | [string.char(152)] = string.char(174), [string.char(153)] = string.char(175), 644 | [string.char(154)] = string.char(172), [string.char(155)] = string.char(173), 645 | [string.char(156)] = string.char(170), [string.char(157)] = string.char(171), 646 | [string.char(158)] = string.char(168), [string.char(159)] = string.char(169), 647 | [string.char(160)] = string.char(150), [string.char(161)] = string.char(151), 648 | [string.char(162)] = string.char(148), [string.char(163)] = string.char(149), 649 | [string.char(164)] = string.char(146), [string.char(165)] = string.char(147), 650 | [string.char(166)] = string.char(144), [string.char(167)] = string.char(145), 651 | [string.char(168)] = string.char(158), [string.char(169)] = string.char(159), 652 | [string.char(170)] = string.char(156), [string.char(171)] = string.char(157), 653 | [string.char(172)] = string.char(154), [string.char(173)] = string.char(155), 654 | [string.char(174)] = string.char(152), [string.char(175)] = string.char(153), 655 | [string.char(176)] = string.char(134), [string.char(177)] = string.char(135), 656 | [string.char(178)] = string.char(132), [string.char(179)] = string.char(133), 657 | [string.char(180)] = string.char(130), [string.char(181)] = string.char(131), 658 | [string.char(182)] = string.char(128), [string.char(183)] = string.char(129), 659 | [string.char(184)] = string.char(142), [string.char(185)] = string.char(143), 660 | [string.char(186)] = string.char(140), [string.char(187)] = string.char(141), 661 | [string.char(188)] = string.char(138), [string.char(189)] = string.char(139), 662 | [string.char(190)] = string.char(136), [string.char(191)] = string.char(137), 663 | [string.char(192)] = string.char(246), [string.char(193)] = string.char(247), 664 | [string.char(194)] = string.char(244), [string.char(195)] = string.char(245), 665 | [string.char(196)] = string.char(242), [string.char(197)] = string.char(243), 666 | [string.char(198)] = string.char(240), [string.char(199)] = string.char(241), 667 | [string.char(200)] = string.char(254), [string.char(201)] = string.char(255), 668 | [string.char(202)] = string.char(252), [string.char(203)] = string.char(253), 669 | [string.char(204)] = string.char(250), [string.char(205)] = string.char(251), 670 | [string.char(206)] = string.char(248), [string.char(207)] = string.char(249), 671 | [string.char(208)] = string.char(230), [string.char(209)] = string.char(231), 672 | [string.char(210)] = string.char(228), [string.char(211)] = string.char(229), 673 | [string.char(212)] = string.char(226), [string.char(213)] = string.char(227), 674 | [string.char(214)] = string.char(224), [string.char(215)] = string.char(225), 675 | [string.char(216)] = string.char(238), [string.char(217)] = string.char(239), 676 | [string.char(218)] = string.char(236), [string.char(219)] = string.char(237), 677 | [string.char(220)] = string.char(234), [string.char(221)] = string.char(235), 678 | [string.char(222)] = string.char(232), [string.char(223)] = string.char(233), 679 | [string.char(224)] = string.char(214), [string.char(225)] = string.char(215), 680 | [string.char(226)] = string.char(212), [string.char(227)] = string.char(213), 681 | [string.char(228)] = string.char(210), [string.char(229)] = string.char(211), 682 | [string.char(230)] = string.char(208), [string.char(231)] = string.char(209), 683 | [string.char(232)] = string.char(222), [string.char(233)] = string.char(223), 684 | [string.char(234)] = string.char(220), [string.char(235)] = string.char(221), 685 | [string.char(236)] = string.char(218), [string.char(237)] = string.char(219), 686 | [string.char(238)] = string.char(216), [string.char(239)] = string.char(217), 687 | [string.char(240)] = string.char(198), [string.char(241)] = string.char(199), 688 | [string.char(242)] = string.char(196), [string.char(243)] = string.char(197), 689 | [string.char(244)] = string.char(194), [string.char(245)] = string.char(195), 690 | [string.char(246)] = string.char(192), [string.char(247)] = string.char(193), 691 | [string.char(248)] = string.char(206), [string.char(249)] = string.char(207), 692 | [string.char(250)] = string.char(204), [string.char(251)] = string.char(205), 693 | [string.char(252)] = string.char(202), [string.char(253)] = string.char(203), 694 | [string.char(254)] = string.char(200), [string.char(255)] = string.char(201), 695 | } 696 | 697 | 698 | local blocksize = 64 -- 512 bits 699 | 700 | function sha1.hmac_sha1(key, text) 701 | assert(type(key) == 'string', "key passed to hmac_sha1 should be a string") 702 | assert(type(text) == 'string', "text passed to hmac_sha1 should be a string") 703 | 704 | if #key > blocksize then 705 | key = sha1.sha1_binary(key) 706 | end 707 | 708 | local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), blocksize - #key) 709 | local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), blocksize - #key) 710 | 711 | return sha1.sha1(key_xord_with_0x5c .. sha1.sha1_binary(key_xord_with_0x36 .. text)) 712 | end 713 | 714 | function sha1.hmac_sha1_binary(key, text) 715 | return hex_to_binary(sha1.hmac_sha1(key, text)) 716 | end 717 | 718 | return sha1 719 | -------------------------------------------------------------------------------- /spec/fakengx_spec.lua: -------------------------------------------------------------------------------- 1 | require 'spec.helper' 2 | 3 | context('fakengx', function() 4 | 5 | before(function() 6 | ngx = fakengx.new() 7 | end) 8 | 9 | test('instance type', function() 10 | assert_type(ngx, 'table') 11 | end) 12 | 13 | test('fresh instances', function() 14 | ngx.var.something = 1 15 | local a = fakengx.new() 16 | assert_tables(a.var, {}) 17 | end) 18 | 19 | test('constants', function() 20 | assert_equal(ngx.DEBUG, 8) 21 | assert_equal(ngx.HTTP_GET, 'GET') 22 | assert_equal(ngx.HTTP_OK, 200) 23 | assert_equal(ngx.HTTP_BAD_REQUEST, 400) 24 | end) 25 | 26 | test('static', function() 27 | assert_equal(ngx.status, 200) 28 | assert_tables(ngx.var, {}) 29 | assert_tables(ngx.arg, {}) 30 | assert_tables(ngx.header, {}) 31 | end) 32 | 33 | test('internal registries', function() 34 | assert_equal(ngx._body, "") 35 | assert_equal(ngx._log, "") 36 | assert_tables(ngx._captures, { stubs = {} }) 37 | end) 38 | 39 | test('_captures.length()', function() 40 | assert_equal(ngx._captures:length(), 0) 41 | end) 42 | 43 | test('_captures.stub()', function() 44 | local s1 = ngx.location.stub("/subrequest") 45 | local s2 = ngx.location.stub("/subrequest", { body = "ABC", method = "POST" }, { status = 201 }) 46 | local s3 = ngx.location.stub("/subrequest", { args = { b = 1, a = 2 } }, { body = "OK" }) 47 | assert_equal(ngx._captures:length(), 3) 48 | 49 | local stub 50 | stub = ngx._captures.stubs[1] 51 | assert_equal(stub, s1) 52 | assert_equal(stub.uri, "/subrequest") 53 | assert_tables(stub.opts, { }) 54 | assert_tables(stub.res, { status = 200, headers = {}, body = "" }) 55 | 56 | stub = ngx._captures.stubs[2] 57 | assert_equal(stub, s2) 58 | assert_equal(stub.uri, "/subrequest") 59 | assert_tables(stub.opts, { body = "ABC", method = "POST" }) 60 | assert_tables(stub.res, { status = 201, headers = {}, body = "" }) 61 | 62 | stub = ngx._captures.stubs[3] 63 | assert_equal(stub, s3) 64 | assert_equal(stub.uri, "/subrequest") 65 | assert_tables(stub.opts, { args = "a=2&b=1" }) 66 | assert_tables(stub.res, { status = 200, headers = {}, body = "OK" }) 67 | end) 68 | 69 | test('_captures.find()', function() 70 | local s0 = ngx.location.stub("/subrequest", { }, { status = 200 }) 71 | local s1 = ngx.location.stub("/subrequest", { method = "GET" }, { status = 200 }) 72 | local s2 = ngx.location.stub("/subrequest", { body = "~>A%a+C", method = "POST" }, { status = 201, headers = { Location = "http://host/resource/1" } }) 73 | local s3 = ngx.location.stub("/subrequest", { args = { b = 1, a = 2 } }, { body = "OK" }) 74 | local s4 = ngx.location.stub("/subrequest", { body = (function(v) return v == "HI" end) }, { body = "OK" }) 75 | 76 | assert_nil(ngx._captures:find("/not-registered", {})) 77 | assert_nil(ngx._captures:find("/not-registered")) 78 | 79 | assert_tables(ngx._captures:find("/subrequest"), s1) 80 | assert_tables(ngx._captures:find("/subrequest", { method = "GET" }), s1) 81 | assert_tables(ngx._captures:find("/subrequest", { method = "POST", body = "ABC" }), s2) 82 | assert_tables(ngx._captures:find("/subrequest", { body = "ABC" }), s1) 83 | assert_tables(ngx._captures:find("/subrequest", { args = "a=2&b=1" }), s3) 84 | assert_tables(ngx._captures:find("/subrequest", { args = { a = 2, b = 1 } }), s3) 85 | assert_tables(ngx._captures:find("/subrequest", { args = "b=1&a=1" }), s1) 86 | assert_tables(ngx._captures:find("/subrequest", { method = "POST" }), s0) 87 | assert_tables(ngx._captures:find("/subrequest", { body = "HI" }), s4) 88 | end) 89 | 90 | test('shared.DICT.set()', function() 91 | local ok, err = ngx.shared.store:set("key", "value") 92 | assert_equal(ok, true) 93 | assert_equal(err, nil) 94 | end) 95 | 96 | test('shared.DICT.get()', function() 97 | local val 98 | 99 | val = ngx.shared.store:get("key") 100 | assert_equal(val, nil) 101 | 102 | ngx.shared.store:set("key", "value") 103 | val = ngx.shared.store:get("key") 104 | assert_equal(val, "value") 105 | end) 106 | 107 | test('shared.DICT.add()', function() 108 | local ok, err 109 | ok, err = ngx.shared.store:add("key", "v1") 110 | assert_equal(ok, true) 111 | assert_equal(err, nil) 112 | 113 | ok, err = ngx.shared.store:add("key", "v2") 114 | assert_equal(ok, false) 115 | assert_equal(err, "exists") 116 | 117 | assert_equal(ngx.shared.store:get("key"), "v1") 118 | end) 119 | 120 | test('shared.DICT.replace()', function() 121 | local ok, err 122 | ok, err = ngx.shared.store:replace("key", "v1") 123 | assert_equal(ok, false) 124 | assert_equal(err, "not found") 125 | 126 | ok, err = ngx.shared.store:add("key", "v1") 127 | ok, err = ngx.shared.store:replace("key", "v2") 128 | assert_equal(ok, true) 129 | assert_equal(err, nil) 130 | 131 | assert_equal(ngx.shared.store:get("key"), "v2") 132 | end) 133 | 134 | test('shared.DICT.delete()', function() 135 | assert_equal(ngx.shared.store:get("key"), nil) 136 | ngx.shared.store:set("key", "v") 137 | assert_equal(ngx.shared.store:get("key"), "v") 138 | ngx.shared.store:delete("key", "v") 139 | assert_equal(ngx.shared.store:get("key"), nil) 140 | end) 141 | 142 | test('shared.DICT.incr()', function() 143 | local ok, err 144 | ok, err = ngx.shared.store:incr("key", 1) 145 | assert_equal(ok, nil) 146 | assert_equal(err, "not found") 147 | 148 | ngx.shared.store:set("key", "v") 149 | ok, err = ngx.shared.store:incr("key", 1) 150 | assert_equal(ok, nil) 151 | assert_equal(err, "not a number") 152 | 153 | ngx.shared.store:set("key", 123) 154 | ok, err = ngx.shared.store:incr("key", 1) 155 | assert_equal(ok, 124) 156 | assert_equal(err, nil) 157 | end) 158 | 159 | 160 | test('print()', function() 161 | ngx.print("string") 162 | assert_equal(ngx._body, "string") 163 | end) 164 | 165 | test('say()', function() 166 | ngx.say("string") 167 | assert_equal(ngx._body, "string\n") 168 | end) 169 | 170 | test('log()', function() 171 | ngx.log(ngx.NOTICE, "string") 172 | assert_equal(ngx._log, "LOG(6): string\n") 173 | end) 174 | 175 | test('time()', function() 176 | assert_type(ngx.time(), 'number') 177 | local t = os.time() 178 | assert_equal(ngx.time(), t) 179 | while os.time() - t == 0 do end -- wait for next second 180 | assert_equal(ngx.time(), t) -- ensure cached value is used 181 | end) 182 | 183 | test('update_time()', function() 184 | local t = os.time() 185 | local t1 = ngx.time() 186 | local n1 = ngx.now() 187 | while os.time() - t == 0 do end -- wait for next second 188 | assert_equal(ngx.time(), t1) -- until ngx.update_time() is called uses cached value 189 | assert_equal(ngx.now(), n1) 190 | ngx.update_time() 191 | assert_greater_than(ngx.time(), t1) 192 | assert_greater_than(ngx.now(), n1) 193 | end) 194 | 195 | test('now()', function() 196 | assert_type(ngx.now(), 'number') 197 | assert(ngx.now() >= os.time()) 198 | assert(ngx.now() <= (os.time() + 1)) 199 | end) 200 | 201 | test('cookie_time()', function() 202 | assert_type(ngx.cookie_time(1212121212), 'string') 203 | assert_equal(ngx.cookie_time(1212121212), 'Fri, 30-May-2008 04:20:12 GMT') 204 | end) 205 | 206 | test('exit()', function() 207 | assert_equal(ngx.status, 200) 208 | assert_nil(ngx._exit) 209 | 210 | ngx.exit(ngx.HTTP_BAD_REQUEST) 211 | assert_equal(ngx.status, 400) 212 | assert_equal(ngx._exit, 400) 213 | 214 | ngx.exit(ngx.HTTP_OK) 215 | assert_equal(ngx.status, 400) 216 | assert_equal(ngx._exit, 200) 217 | end) 218 | 219 | test('escape_uri()', function() 220 | assert_equal(ngx.escape_uri("here [ & ] now_"), "here+%5B+%26+%5D+now_") 221 | end) 222 | 223 | test('unescape_uri()', function() 224 | assert_equal(ngx.unescape_uri("here+%5B+%26+%5D+now"), "here [ & ] now") 225 | end) 226 | 227 | test('encode_args()', function() 228 | assert_equal(ngx.encode_args({foo = 3, ["b r"] = "hello world"}), "b%20r=hello%20world&foo=3") 229 | assert_equal(ngx.encode_args({["b r"] = "hello world", foo = 3}), "b%20r=hello%20world&foo=3") 230 | end) 231 | 232 | test('crc32_short()', function() 233 | assert_type(ngx.crc32_short("abc"), 'number') 234 | assert_equal(ngx.crc32_short("abc"), 891568578) 235 | assert_equal(ngx.crc32_short("def"), 214229345) 236 | assert_equal(ngx.crc32_short("another"), 2636723256) 237 | end) 238 | 239 | test('hmac_sha1()', function() 240 | local secret = string.char(220,58,17,206,77,234,240,187,69,25,179,182,186,38,57,83,120,107,198,148,234,246,46,96,83,28,231,89,3,169,42,62,125,235,137) 241 | local str = string.char(1,225,98,83,100,71,237,241,239,170,244,215,3,254,14,24,216,66,69,30,124,126,96,177,241,20,44,3,92,111,243,169,100,119,198,167,146,242,30,124,7,22,251,52,235,95,211,145,56,204,236,37,107,139,17,184,65,207,245,101,241,12,50,149,19,118,208,133,198,33,80,94,87,133,146,202,27,89,201,218,171,206,21,191,43,77,127,30,187,194,166,39,191,208,42,167,77,202,186,225,4,86,218,237,157,117,175,106,63,166,132,136,153,243,187) 242 | 243 | assert_type(ngx.hmac_sha1(secret, str), 'string') 244 | assert_equal(ngx.hmac_sha1(secret, str), string.char(236,200,181,211,171,181,24,45,61,87,10,111,6,15,239,46,230,193,26,68)) 245 | end) 246 | 247 | test('sha1_bin()', function() 248 | assert_type(ngx.sha1_bin("abc"), 'string') 249 | assert_equal(ngx.sha1_bin("abc"), string.char(169,153,62,54,71,6,129,106,186,62,37,113,120,80,194,108,156,208,216,157)) 250 | end) 251 | 252 | test('md5()', function() 253 | assert_type(ngx.md5("abc"), 'string') 254 | assert_equal(ngx.md5("abc"), '900150983cd24fb0d6963f7d28e17f72') 255 | end) 256 | 257 | test('md5_bin()', function() 258 | assert_type(ngx.md5_bin("abc"), 'string') 259 | assert_equal(ngx.md5_bin("abc"), string.char(144,1,80,152,60,210,79,176,214,150,63,125,40,225,127,114)) 260 | end) 261 | 262 | test('encode_base64()', function() 263 | assert_type(ngx.encode_base64("abc"), 'string') 264 | assert_equal(ngx.encode_base64("abc"), 'YWJj') 265 | end) 266 | 267 | test('decode_base64()', function() 268 | assert_type(ngx.decode_base64("YWJj"), 'string') 269 | assert_equal(ngx.decode_base64("YWJj"), 'abc') 270 | end) 271 | 272 | test('location.capture()', function() 273 | local s1 = ngx.location.stub("/stubbed", {}, { body = "OK" }) 274 | 275 | assert_error(function() ngx.location.capture("/not-stubbed") end) 276 | assert_not_error(function() ngx.location.capture("/stubbed") end) 277 | assert_equal(#s1.calls, 1) 278 | 279 | assert_tables(ngx.location.capture("/stubbed"), { status = 200, headers = {}, body = "OK" }) 280 | assert_equal(#s1.calls, 2) 281 | end) 282 | 283 | test('location.capture_multi()', function() 284 | local s1 = ngx.location.stub("/stubbed", {}, { body = "OK" }) 285 | local s2 = ngx.location.stub("/stubbed2", {}, { body = "OK" }) 286 | 287 | assert_not_error(function() ngx.location.capture_multi({ { "/stubbed" }, { "/stubbed2"} }) end) 288 | assert_equal(#s1.calls, 1) 289 | assert_equal(#s2.calls, 1) 290 | 291 | local r1, r2 292 | r1, r2 = ngx.location.capture_multi({ { "/stubbed" }, { "/stubbed2"} }) 293 | assert_equal(r1.body, 'OK') 294 | assert_equal(r2.body, 'OK') 295 | end) 296 | 297 | test('req.read_body()', function() 298 | assert_nil(ngx.req.read_body()) 299 | end) 300 | 301 | 302 | test('thread.spawn()', function() 303 | local concat = function(a, b) return "Hello " .. a .. b end 304 | assert_tables(ngx.thread.spawn(concat, "Wor", "ld"), { 305 | fun = concat, args = { "Wor", "ld" } 306 | }) 307 | end) 308 | 309 | test('thread.wait()', function() 310 | local concat = function(a, b) return "Hello " .. a .. b end 311 | local thread = ngx.thread.spawn(concat, "Wor", "ld") 312 | local ok, res = ngx.thread.wait(thread) 313 | assert_equal(ok, true) 314 | assert_equal(res, "Hello World") 315 | end) 316 | 317 | end) 318 | -------------------------------------------------------------------------------- /spec/helper.lua: -------------------------------------------------------------------------------- 1 | fakengx = require 'fakengx' 2 | inspect = require 'spec.inspect' 3 | 4 | local function compare_tables(t1, t2) 5 | local ty1 = type(t1) 6 | local ty2 = type(t2) 7 | if ty1 ~= ty2 then return false end 8 | -- non-table types can be directly compared 9 | if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end 10 | -- as well as tables which have the metamethod __eq 11 | local mt = getmetatable(t1) 12 | for k1,v1 in pairs(t1) do 13 | local v2 = t2[k1] 14 | if v2 == nil or not compare_tables(v1,v2) then return false end 15 | end 16 | for k2,v2 in pairs(t2) do 17 | local v1 = t1[k2] 18 | if v1 == nil or not compare_tables(v1,v2) then return false end 19 | end 20 | return true 21 | end 22 | 23 | telescope.make_assertion("tables", function(_, a, b) 24 | return "Expected table to be " .. inspect(b) .. ", but was " .. inspect(a) 25 | end, function(a, b) 26 | return compare_tables(a, b) 27 | end) 28 | -------------------------------------------------------------------------------- /spec/inspect.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------------------------------------------------- 2 | -- inspect.lua - v1.1.1 (2011-01) 3 | -- Enrique García Cota - enrique.garcia.cota [AT] gmail [DOT] com 4 | -- human-readable representations of tables. 5 | -- inspired by http://lua-users.org/wiki/TableSerialization 6 | ----------------------------------------------------------------------------------------------------------------------- 7 | 8 | -- Apostrophizes the string if it has quotes, but not aphostrophes 9 | -- Otherwise, it returns a regular quoted string 10 | local function smartQuote(str) 11 | if string.match( string.gsub(str,"[^'\"]",""), '^"+$' ) then 12 | return "'" .. str .. "'" 13 | end 14 | return string.format("%q", str ) 15 | end 16 | 17 | local controlCharsTranslation = { 18 | ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", 19 | ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\\"] = "\\\\" 20 | } 21 | 22 | local function unescapeChar(c) return controlCharsTranslation[c] end 23 | 24 | local function unescape(str) 25 | local result, _ = string.gsub( str, "(%c)", unescapeChar ) 26 | return result 27 | end 28 | 29 | local function isIdentifier(str) 30 | return string.match( str, "^[_%a][_%a%d]*$" ) 31 | end 32 | 33 | local function isArrayKey(k, length) 34 | return type(k)=='number' and 1 <= k and k <= length 35 | end 36 | 37 | local function isDictionaryKey(k, length) 38 | return not isArrayKey(k, length) 39 | end 40 | 41 | local sortOrdersByType = { 42 | ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, 43 | ['function'] = 5, ['userdata'] = 6, ['thread'] = 7 44 | } 45 | 46 | local function sortKeys(a,b) 47 | local ta, tb = type(a), type(b) 48 | if ta ~= tb then return sortOrdersByType[ta] < sortOrdersByType[tb] end 49 | if ta == 'string' or ta == 'number' then return a < b end 50 | return false 51 | end 52 | 53 | local function getDictionaryKeys(t) 54 | local length = #t 55 | local keys = {} 56 | for k,_ in pairs(t) do 57 | if isDictionaryKey(k, length) then table.insert(keys,k) end 58 | end 59 | table.sort(keys, sortKeys) 60 | return keys 61 | end 62 | 63 | local function getToStringResultSafely(t, mt) 64 | local __tostring = type(mt) == 'table' and mt.__tostring 65 | local string, status 66 | if type(__tostring) == 'function' then 67 | status, string = pcall(__tostring, t) 68 | string = status and string or 'error: ' .. tostring(string) 69 | end 70 | return string 71 | end 72 | 73 | local Inspector = {} 74 | 75 | function Inspector:new(v, depth) 76 | local inspector = { 77 | buffer = {}, 78 | depth = depth, 79 | level = 0, 80 | counters = { 81 | ['function'] = 0, 82 | ['userdata'] = 0, 83 | ['thread'] = 0, 84 | ['table'] = 0 85 | }, 86 | pools = { 87 | ['function'] = setmetatable({}, {__mode = "kv"}), 88 | ['userdata'] = setmetatable({}, {__mode = "kv"}), 89 | ['thread'] = setmetatable({}, {__mode = "kv"}), 90 | ['table'] = setmetatable({}, {__mode = "kv"}) 91 | } 92 | } 93 | 94 | setmetatable( inspector, { 95 | __index = Inspector, 96 | __tostring = function(instance) return table.concat(instance.buffer) end 97 | } ) 98 | return inspector:putValue(v) 99 | end 100 | 101 | function Inspector:puts(...) 102 | local args = {...} 103 | for i=1, #args do 104 | table.insert(self.buffer, tostring(args[i])) 105 | end 106 | return self 107 | end 108 | 109 | function Inspector:tabify() 110 | self:puts("\n", string.rep(" ", self.level)) 111 | return self 112 | end 113 | 114 | function Inspector:up() 115 | self.level = self.level - 1 116 | end 117 | 118 | function Inspector:down() 119 | self.level = self.level + 1 120 | end 121 | 122 | function Inspector:putComma(comma) 123 | if comma then self:puts(',') end 124 | return true 125 | end 126 | 127 | function Inspector:putTable(t) 128 | if self:alreadySeen(t) then 129 | self:puts('