Skip to content

Instantly share code, notes, and snippets.

@abadc0de
Last active June 5, 2016 04:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save abadc0de/5050864 to your computer and use it in GitHub Desktop.
Save abadc0de/5050864 to your computer and use it in GitHub Desktop.
Bicycle for Mongoose.
_G.bicycle = {
-- Add your form callbacks here.
action = { },
-- This will contain data parsed from the query string.
basket = { },
-- Configuration options. Set these before calling "ride" if needed.
config = {
-- Your new web root, inside your real web root. :)
route = "route",
-- Where should uploaded files go?
upload = "upload",
},
-- Check if a file is readable.
access = function (path)
local file = io.open(path, "r")
if not file then return false end
io.close(file)
return true
end,
-- Redirect to a URI.
bounce = function (uri)
if not uri or uri == "" then uri = "/" end
if uri == request_info.uri then
print(
'HTTP/1.0 404 Not Found\r\nContent-Type: text/html\r\n\r\n' ..
'Not found: ' .. uri)
else
print(
("HTTP/1.0 302 Found\r\nLocation: %s://%s%s\r\n\r\n"):format(
request_info.is_ssl == 1 and 'https' or 'http',
request_info.http_headers.Host,
uri == "." and request_info.uri or uri))
end
end,
-- Parse query string data into a table.
bundle = function (query)
local parsed = {}
local pos = 0
local unescape = bicycle.uri.unescape;
if not query then return parsed end
local function insert(s)
local first, last = s:find("=")
if first then
parsed[unescape(s:sub(0, first - 1))] = unescape(s:sub(first + 1))
end
end
while true do
local first, last = query:find("&", pos)
if first then
insert(query:sub(pos, first - 1))
pos = last + 1
else
insert(query:sub(pos));
break;
end
end
return parsed
end,
-- Like mg.include, but also includes the .lua file. Omit the file extension.
include = function (path)
local has_code
if bicycle.access(path .. ".lua") then
has_code = true
local setup = bicycle.setup
local teardown = bicycle.teardown
bicycle.setup = nil
bicycle.teardown = nil
require(path)
if bicycle.setup then
bicycle.setup()
bicycle.setup = setup
end
end
mg.include(path .. ".lp")
if has_code and bicycle.on_teardown then
bicycle.teardown()
bicycle.teardown = teardown
end
end,
-- Possibly the only function here you'll need to call.
ride = function ()
local bounce = bicycle.bounce
local destination = request_info.uri:gsub("/+$", ""):gsub("^$", "/index")
local route = bicycle.config.route
local path = route .. destination
local method = request_info.request_method
local content_type = request_info.http_headers["Content-Type"]
local uri = request_info.uri
local query_string = request_info.query_string
local fields, files
local s, e = uri:find("/index/?")
if s == 1 and (e == 7 or e == # uri) then return bounce() end
if method == "POST" then
local realpath = path:gsub("/[^/]*$", "")
local action = path:gsub("^.*/", "")
if not bicycle.access(realpath .. ".lua") then return bounce() end
if content_type == "application/x-www-form-urlencoded" then
fields = bicycle._handle_urlencoded_form()
elseif content_type:find("multipart/form-data", nil, true) then
fields, files = bicycle._handle_multipart_form()
else
error("Content type " .. content_type .. " not supported.")
end
bicycle._do_form_callback(realpath, action, fields, files)
return bounce(uri:gsub("/[^/]*$", "/"))
end
if method ~= "GET" then return bounce() end
if not bicycle.access(path .. ".lp") then return bounce() end
if destination ~= "/index" and destination .. "/" ~= uri then
return bounce(destination .. '/')
end
bicycle.basket = bicycle.bundle(query_string)
bicycle.include(path)
end,
-- Encode and decode URIs
-- http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4
-- http://www.ietf.org/rfc/rfc1738
uri = {
escape = function (s)
return s:gsub("&", "&amp;"):gsub("<", "&lt;"):gsub(">", "&gt;")
:gsub("[^A-Za-z0-9$_.!*'(), -]", function(c)
return string.format("%%%02x", string.byte(c))
end):gsub(" ", "+")
end,
unescape = function (s)
return s:gsub("&amp;", "&"):gsub("&lt;", "<"):gsub("&gt;", ">")
:gsub("+", " "):gsub("%%(%x%x)", function(hex)
return string.char(tonumber(hex, 16))
end)
end
},
--
-- Internal functions below
--
_do_form_callback = function (path, action, fields, files)
bicycle.basket = bicycle.bundle(request_info.query_string)
require(path)
bicycle.action[action](fields, files)
end,
_handle_urlencoded_form = function ()
local post = ""
for buf in read do
if buf == "" then break end
post = post .. buf
end
return bicycle.bundle(post)
end,
_handle_multipart_form = function ()
local unescape = bicycle.uri.unescape
local content_type = request_info.http_headers["Content-Type"];
local boundary = content_type:gsub(".*boundary=", "")
local boundary_start, boundary_end = 0, 0
local fields, files = {}, {}
local first = true
local head, name, mimetype, filename, file, is_file
-- Store data in a file or in the `fields` table, as appropriate
local function store(buf, from, to)
local data = buf:sub(from + 1, to and to - 1)
if is_file then
file:write(data)
else
local val = fields[name]
if val then
fields[name] = val .. data
else
fields[name] = data
end
end
end
-- Start reading the buffer
for buf in read do
-- TODO: Remove this line when #130 / #131 is implemented
-- https://github.com/valenok/mongoose/issues/130
if buf == "" then break end
-- Look for a boundary
boundary_start, boundary_end = buf:find(
(first and '' or '\r\n--') .. boundary, nil, true
);
-- If this isn't the first read, flush the buffer
if not first then
store(buf, 0, boundary_start)
end
-- If we found a boundary...
while boundary_start do
local _, head_end = buf:find('\r\n\r\n', boundary_end + 1, true)
-- If we found a boundary, but didn't find headers...
if not head_end then
-- Usually when we get here, it's the end of the request,
-- but try to read() one more time in case the buffer ended
-- between an inner boundary and the following '\r\n\r\n'
local buf2 = read()
if not buf2 or buf2 == "" then break end
buf = buf .. buf2
_, head_end = buf:find('\r\n\r\n', boundary_end + 1, true)
if not head_end then break end
end
-- Found headers
head = buf:sub(boundary_end + 1, head_end)
name = unescape(head:match('name="([^"]*)"') or "")
filename = head:match('filename="([^"]*)"')
mimetype = unescape(head:match('Content%-Type: ([^\r\n]*)') or "")
is_file = not not filename
filename = unescape(filename or "")
-- If it has a filename, open a file
if is_file and filename ~= "" then
if file then io.close(file) end
local path = bicycle._sanitize_upload_filename(filename)
local info = {
name = name,
filename = filename,
path = path,
content = mimetype
}
table.insert(files, info)
fields[name] = info
file = io.open(path, "wb")
end
-- Find the next boundary
boundary_start, boundary_end = buf:find(
'\r\n--' .. boundary, head_end, true
);
-- Store the data
if not is_file or filename ~= "" then
store(buf, head_end, boundary_start)
end
end
boundary_start = 0
boundary_end = 0
first = false
end
-- Clean up
if file then io.close(file) end
return fields, files
end,
_sanitize_upload_filename = function (filename)
local p = bicycle.config.upload .. '/' .. filename:gsub("[^%w._-]", "")
local op = p
local counter = 1
while bicycle.access(p) do
counter = counter + 1
p = op:gsub("[.][^.]*", "-" .. counter .. "%1")
if p == op then p = op .. "-" .. counter end
end
return p
end,
}
MONGOOSE_PATH=./mongoose
$MONGOOSE_PATH -w **.lp=/dev/null/,**.lua=/dev/null/,/route/=/dev/null/,/static/=static/,**=router.lp -d no
<?
require "bicycle"
bicycle.ride()
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment