Last active
June 5, 2016 04:14
Bicycle for Mongoose.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
_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("&", "&"):gsub("<", "<"):gsub(">", ">") | |
:gsub("[^A-Za-z0-9$_.!*'(), -]", function(c) | |
return string.format("%%%02x", string.byte(c)) | |
end):gsub(" ", "+") | |
end, | |
unescape = function (s) | |
return s:gsub("&", "&"):gsub("<", "<"):gsub(">", ">") | |
: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, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MONGOOSE_PATH=./mongoose | |
$MONGOOSE_PATH -w **.lp=/dev/null/,**.lua=/dev/null/,/route/=/dev/null/,/static/=static/,**=router.lp -d no |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<? | |
require "bicycle" | |
bicycle.ride() | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment