├── Changes.md ├── LICENSE ├── README.md ├── dist.ini ├── lib └── resty │ └── reqargs.lua └── lua-resty-reqargs-dev-1.rockspec /Changes.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `lua-resty-reqargs` will be documented in this file. 4 | 5 | ## [1.4] - 2017-01-07 6 | ### Fixed 7 | - Fixed issue with no options passed as reported here: 8 | https://groups.google.com/forum/#!topic/openresty-en/uXRXC0NbfbI 9 | 10 | ## [1.3] - 2016-09-29 11 | ### Added 12 | - Support for the official OpenResty package manager (opm). 13 | - Added changelog (this file). 14 | - A lots of new documentation. 15 | 16 | ##[1.2] - 2016-08-23 17 | ### Added 18 | - Added max_fsize option that can be used to control how large can one uploaded file be. 19 | - Added max_files option that can be used to control how many files can be uploaded. 20 | 21 | ### Fixed 22 | - LuaRocks etc. was using wrong directory name (renamed regargs dir to resty). 23 | 24 | ##[1.1] - 2016-08-19 25 | ### Fixed 26 | - Files are always opened in binary mode (this affects mainly Windows users). 27 | 28 | ##[1.0] - 2016-07-06 29 | ### Added 30 | - Initial Release. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 - 2017, Aapo Talvensaari 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-resty-reqargs 2 | 3 | Helper to Retrieve `application/x-www-form-urlencoded`, `multipart/form-data`, and `application/json` Request Arguments. 4 | 5 | ## Synopsis 6 | 7 | ```lua 8 | local get, post, files = require "resty.reqargs"() 9 | if not get then 10 | error(post) 11 | end 12 | -- Use get, post, and files... 13 | ``` 14 | 15 | ## Installation 16 | 17 | Just place [`reqargs.lua`](https://github.com/bungle/lua-resty-reqargs/blob/master/lib/resty/reqargs.lua) 18 | somewhere in your `package.path`, under `resty` directory. If you are using OpenResty, the default location 19 | would be `/usr/local/openresty/lualib/resty`. 20 | 21 | ### Using OpenResty Package Manager (opm) 22 | 23 | ```Shell 24 | $ opm get bungle/lua-resty-reqargs 25 | ``` 26 | 27 | ### Using LuaRocks 28 | 29 | ```Shell 30 | $ luarocks install lua-resty-reqargs 31 | ``` 32 | 33 | LuaRocks repository for `lua-resty-reqargs` is located at https://luarocks.org/modules/bungle/lua-resty-reqargs. 34 | 35 | ## API 36 | 37 | This module has only one function, and that function is loaded with require: 38 | 39 | ```lua 40 | local reqargs = require "resty.reqargs" 41 | ``` 42 | 43 | ### get, post, files reqargs(options) 44 | 45 | When you call the function (`reqargs`) you can pass it `options`. These 46 | options override whatever you may have defined in your Nginx configuration 47 | (or the defaults). You may use the following options: 48 | 49 | ```lua 50 | { 51 | tmp_dir = "/tmp", 52 | timeout = 1000, 53 | chunk_size = 4096, 54 | max_get_args = 100, 55 | mas_post_args = 100, 56 | max_line_size = 512, 57 | max_file_size = 10240, 58 | max_file_uploads = 10 59 | } 60 | ``` 61 | 62 | This function will return three (3) return values, and they are called 63 | `get`, `post`, and `files`. These are Lua tables containing the data 64 | that was (HTTP) requested. `get` contains HTTP request GET arguments 65 | retrieved with [ngx.req.get_uri_args](https://github.com/openresty/lua-nginx-module#ngxreqget_uri_args). 66 | `post` contains either HTTP request POST arguments retrieved with 67 | [ngx.req.get_post_args](https://github.com/openresty/lua-nginx-module#ngxreqget_post_args), 68 | or in case of `application/json` (as a content type header for the request), 69 | it will read the request body and decode the JSON, and the `post` will 70 | then contain the decoded JSON structure presented as Lua tables. The 71 | last return value `files` contains all the files uploaded. The `files` 72 | return value will only contain data when there are actually files uploaded 73 | and that the request content type is set to `multipart/form-data`. `files` 74 | has the same structure as `get` and `post` for the keys, but the values 75 | are presented as a Lua tables, that look like this (think about PHP's `$_FILES`): 76 | 77 | ```lua 78 | { 79 | -- The name of the file upload form field (same as the key) 80 | name = "photo", 81 | -- The name of the file that the user selected for the upload 82 | file = "cat.jpg", 83 | -- The mimetype of the uploaded file 84 | type = "image/jpeg" 85 | -- The file size of the uploaded file (in bytes) 86 | size = 123465 87 | -- The location where the uploaded file was streamed 88 | temp = "/tmp/????" 89 | } 90 | ``` 91 | 92 | In case of error, this function will return `nil`, `error message`. 93 | 94 | ## Nginx Configuration Variables 95 | 96 | You can configure several aspects of `lua-resty-reqargs` directly from 97 | the Nginx configuration, here are the configuration values that you may 98 | use, and their default values: 99 | 100 | ```nginx 101 | # the default is the system temp dir 102 | set $reqargs_tmp_dir /tmp; 103 | # see https://github.com/openresty/lua-resty-upload 104 | set $reqargs_timeout 1000; 105 | # see https://github.com/openresty/lua-resty-upload 106 | set $reqargs_chunk_size 4096; 107 | # see https://github.com/openresty/lua-nginx-module#ngxreqget_uri_args 108 | set $reqargs_max_get_args 100; 109 | # see https://github.com/openresty/lua-nginx-module#ngxreqget_post_args 110 | set $reqargs_max_post_args 100; 111 | # see https://github.com/openresty/lua-resty-upload 112 | set $reqargs_max_line_size 512; 113 | # the default is unlimited 114 | set $reqargs_max_file_size 10240; 115 | # the default is unlimited 116 | set $reqargs_max_file_uploads 10; 117 | ``` 118 | 119 | ## Changes 120 | 121 | The changes of every release of this module is recorded in [Changes.md](https://github.com/bungle/lua-resty-reqargs/blob/master/Changes.md) file. 122 | 123 | ## Roadmap 124 | 125 | * Add safeguards and streaming parsing to `application/json` bodies 126 | * Add support for pluggable stream writer (e.g. non-blocking network / db writer) 127 | 128 | ## See Also 129 | 130 | * [lua-resty-route](https://github.com/bungle/lua-resty-route) — Routing library 131 | * [lua-resty-session](https://github.com/bungle/lua-resty-session) — Session library 132 | * [lua-resty-template](https://github.com/bungle/lua-resty-template) — Templating engine 133 | * [lua-resty-validation](https://github.com/bungle/lua-resty-validation) — Validation and filtering library 134 | 135 | ## License 136 | 137 | `lua-resty-reqargs` uses two clause BSD license. 138 | 139 | ``` 140 | Copyright (c) 2015 - 2017, Aapo Talvensaari 141 | All rights reserved. 142 | 143 | Redistribution and use in source and binary forms, with or without modification, 144 | are permitted provided that the following conditions are met: 145 | 146 | * Redistributions of source code must retain the above copyright notice, this 147 | list of conditions and the following disclaimer. 148 | 149 | * Redistributions in binary form must reproduce the above copyright notice, this 150 | list of conditions and the following disclaimer in the documentation and/or 151 | other materials provided with the distribution. 152 | 153 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 154 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 155 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 156 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 157 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES` 158 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-reqargs 2 | abstract = HTTP Request Arguments and File Uploads Helper 3 | author = Aapo Talvensaari (@bungle) 4 | is_original = yes 5 | license = 2bsd 6 | repo_link = https://github.com/bungle/lua-resty-reqargs 7 | version = 1.4 8 | requires = openresty, openresty/lua-resty-upload 9 | -------------------------------------------------------------------------------- /lib/resty/reqargs.lua: -------------------------------------------------------------------------------- 1 | local upload = require "resty.upload" 2 | local decode = require "cjson.safe".decode 3 | local tonumber = tonumber 4 | local tmpname = os.tmpname 5 | local concat = table.concat 6 | local type = type 7 | local find = string.find 8 | local open = io.open 9 | local sub = string.sub 10 | local sep = sub(package.config, 1, 1) or "/" 11 | local ngx = ngx 12 | local req = ngx.req 13 | local var = ngx.var 14 | local body = req.read_body 15 | local file = ngx.req.get_body_file 16 | local data = req.get_body_data 17 | local pargs = req.get_post_args 18 | local uargs = req.get_uri_args 19 | 20 | local defaults = { 21 | tmp_dir = var.reqargs_tmp_dir, 22 | timeout = tonumber(var.reqargs_timeout) or 1000, 23 | chunk_size = tonumber(var.reqargs_chunk_size) or 4096, 24 | max_get_args = tonumber(var.reqargs_max_get_args) or 100, 25 | max_post_args = tonumber(var.reqargs_max_post_args) or 100, 26 | max_line_size = tonumber(var.reqargs_max_line_size), 27 | max_file_size = tonumber(var.reqargs_max_file_size), 28 | max_file_uploads = tonumber(var.reqargs_max_file_uploads) 29 | } 30 | 31 | local function read(f) 32 | local f, e = open(f, "rb") 33 | if not f then 34 | return nil, e 35 | end 36 | local c = f:read "*a" 37 | f:close() 38 | return c 39 | end 40 | 41 | local function basename(s) 42 | local p = 1 43 | local i = find(s, sep, 1, true) 44 | while i do 45 | p = i + 1 46 | i = find(s, sep, p, true) 47 | end 48 | if p > 1 then 49 | s = sub(s, p) 50 | end 51 | return s 52 | end 53 | 54 | local function kv(r, s) 55 | if s == "formdata" then return end 56 | local e = find(s, "=", 1, true) 57 | if e then 58 | r[sub(s, 2, e - 1)] = sub(s, e + 2, #s - 1) 59 | else 60 | r[#r+1] = s 61 | end 62 | end 63 | 64 | local function parse(s) 65 | if not s then return nil end 66 | local r = {} 67 | local i = 1 68 | local b = find(s, ";", 1, true) 69 | while b do 70 | local p = sub(s, i, b - 1) 71 | kv(r, p) 72 | i = b + 1 73 | b = find(s, ";", i, true) 74 | end 75 | local p = sub(s, i) 76 | if p ~= "" then kv(r, p) end 77 | return r 78 | end 79 | 80 | return function(options) 81 | options = options or defaults 82 | local get = uargs(options.max_get_args or defaults.max_get_args) 83 | local ct = var.content_type or "" 84 | local post = {} 85 | local files = {} 86 | if sub(ct, 1, 33) == "application/x-www-form-urlencoded" then 87 | body() 88 | post = pargs(options.max_post_args or defaults.max_post_args) 89 | elseif sub(ct, 1, 19) == "multipart/form-data" then 90 | local tmpdr = options.tmp_dir or defaults.tmp_dir 91 | if tmpdr and sub(tmpdr, -1) ~= sep then 92 | tmpdr = tmpdr .. sep 93 | end 94 | local maxfz = options.max_file_size or defaults.max_file_size 95 | local maxfs = options.max_file_uploads or defaults.max_file_uploads 96 | local chunk = options.chunk_size or defaults.chunk_size 97 | local form, e = upload:new(chunk, options.max_line_size or defaults.max_line_size) 98 | if not form then return nil, e end 99 | local h, p, f, o, s 100 | local u = 0 101 | form:set_timeout(options.timeout or defaults.timeout) 102 | while true do 103 | local t, r, e = form:read() 104 | if not t then return nil, e end 105 | if t == "header" then 106 | if not h then h = {} end 107 | if type(r) == "table" then 108 | local k, v = r[1], parse(r[2]) 109 | if v then h[k] = v end 110 | end 111 | elseif t == "body" then 112 | if h then 113 | local d = h["Content-Disposition"] 114 | if d then 115 | if d.filename then 116 | if maxfz then 117 | s = 0 118 | end 119 | f = { 120 | name = d.name, 121 | type = h["Content-Type"] and h["Content-Type"][1], 122 | file = basename(d.filename), 123 | temp = tmpdr and (tmpdr .. basename(tmpname())) or tmpname() 124 | } 125 | o, e = open(f.temp, "w+b") 126 | if not o then return nil, e end 127 | o:setvbuf("full", chunk) 128 | else 129 | p = { name = d.name, data = { n = 1 } } 130 | end 131 | end 132 | h = nil 133 | end 134 | if o then 135 | if maxfz then 136 | s = s + #r 137 | if maxfz < s then 138 | o:close() 139 | return nil, "The maximum size of an uploaded file exceeded." 140 | end 141 | end 142 | if maxfs and maxfs < u + 1 then 143 | o:close() 144 | return nil, "The maximum number of files allowed to be uploaded simultaneously exceeded." 145 | end 146 | local ok, e = o:write(r) 147 | if not ok then 148 | o:close() 149 | return nil, e 150 | end 151 | elseif p then 152 | local n = p.data.n 153 | p.data[n] = r 154 | p.data.n = n + 1 155 | end 156 | elseif t == "part_end" then 157 | if o then 158 | f.size = o:seek() 159 | o:close() 160 | o = nil 161 | if maxfs and f.size > 0 then 162 | u = u + 1 163 | end 164 | end 165 | local c, d 166 | if f then 167 | c, d, f = files, f, nil 168 | elseif p then 169 | c, d, p = post, p, nil 170 | end 171 | if c then 172 | local n = d.name 173 | local s = d.data and concat(d.data) or d 174 | if n then 175 | local z = c[n] 176 | if z then 177 | if z.n then 178 | z.n = z.n + 1 179 | z[z.n] = s 180 | else 181 | z = { z, s } 182 | z.n = 2 183 | end 184 | c[n] = z 185 | else 186 | c[n] = s 187 | end 188 | else 189 | c.n = c.n + 1 190 | c[c.n] = s 191 | end 192 | end 193 | elseif t == "eof" then 194 | break 195 | end 196 | end 197 | local t, _, e = form:read() 198 | if not t then return nil, e end 199 | elseif sub(ct, 1, 16) == "application/json" then 200 | body() 201 | local j = data() 202 | if j == nil then 203 | local f = file() 204 | if f ~= nil then 205 | j = read(f) 206 | if j then 207 | post = decode(j) or {} 208 | end 209 | end 210 | else 211 | post = decode(j) or {} 212 | end 213 | else 214 | body() 215 | local b = data() 216 | if b == nil then 217 | local f = file() 218 | if f ~= nil then 219 | b = read(f) 220 | end 221 | end 222 | if b then 223 | post = { b } 224 | end 225 | end 226 | return get, post, files 227 | end 228 | -------------------------------------------------------------------------------- /lua-resty-reqargs-dev-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-reqargs" 2 | version = "dev-1" 3 | source = { 4 | url = "git://github.com/bungle/lua-resty-reqargs.git" 5 | } 6 | description = { 7 | summary = "HTTP Request Arguments and File Uploads Helper", 8 | detailed = "lua-resty-reqargs is a helper to retrieve application/x-www-form-urlencoded, multipart/form-data, and application/json HTTP request arguments.", 9 | homepage = "https://github.com/bungle/lua-resty-reqargs", 10 | maintainer = "Aapo Talvensaari ", 11 | license = "BSD" 12 | } 13 | dependencies = { 14 | "lua >= 5.1" 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | ["resty.reqargs"] = "lib/resty/reqargs.lua" 20 | } 21 | } 22 | --------------------------------------------------------------------------------