├── .drone.yml ├── .gitignore ├── .rancher-pipeline.yml ├── README.md ├── index.lua ├── init.lua ├── libs ├── Response.lua ├── ansicolors.lua ├── cookie.lua ├── etlua.lua ├── fse.lua ├── helpers.lua ├── mime.lua ├── resty-template.lua └── template │ ├── 403.html │ ├── 404.html │ ├── 500.html │ └── directory.html ├── main.lua ├── package.json ├── package.lua └── test ├── init.lua └── views ├── footer.html ├── header.html ├── index.elua └── index.html /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | name: default 3 | 4 | steps: 5 | - name: Install Luvit 6 | image: ubuntu 7 | commands: 8 | - apt install curl 9 | - echo "Hello world!" 10 | - curl -L https://github.com/luvit/lit/raw/master/get-lit.sh | sh 11 | - mv luvit /usr/local/bin && mv lit /usr/local/bin 12 | - luvit test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /deps/ 2 | mooncake 3 | test/cache.html 4 | -------------------------------------------------------------------------------- /.rancher-pipeline.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - name: compile 3 | steps: 4 | - runScriptConfig: 5 | image: ubuntu 6 | shellScript: |- 7 | apt-get update 8 | apt-get install curl -y 9 | curl -L https://github.com/luvit/lit/raw/master/get-lit.sh | sh 10 | ./luvit test 11 | timeout: 60 12 | notification: {} 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mooncake 2 | 3 | [![Join the chat at https://gitter.im/cyrilis/luvit-mooncake](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cyrilis/luvit-mooncake?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | **A web framework powered by luvit.** 6 | 7 | Mooncake is a web framework powered by [luvit](https://luvit.io/). inspired by [expressjs](http://expressjs.com/) for nodejs. 8 | 9 | ## Install 10 | 11 | Install with [lit](https://luvit.io/lit.html) 12 | 13 | ```bash 14 | lit install cyrilis/mooncake 15 | ``` 16 | 17 | ## Usage 18 | 19 | ### Getting start 20 | 21 | ```lua 22 | local MoonCake = require("mooncake") 23 | local server = MoonCake:new() 24 | 25 | -- route your application 26 | server:get("/", function(req, res) 27 | local content = "

Hello world from MoonCake

" 28 | res:send(content, 200) 29 | end) 30 | 31 | server:start(8080) 32 | ``` 33 | 34 | ### Server 35 | 36 | - #### create server 37 | 38 | - create a http server: 39 | 40 | ```lua 41 | Mooncake = require("mooncake") 42 | local sever = Mooncake:new() 43 | ``` 44 | 45 | - or create a https server: 46 | 47 | ```lua 48 | Mooncake = require("mooncake") 49 | local server = Mooncake:new({ 50 | isHttps = true 51 | keyPath = "/path/to/key" 52 | }) 53 | -- will read ssl key and cert file at "/path/to/key/key.pem" and "path/to/key/cert.pem" 54 | ``` 55 | 56 | - #### server:use(func) 57 | 58 | Use the given middleware function, 59 | 60 | Example: 61 | 62 | ```lua 63 | server:use(function(req, res, next) 64 | res:locals({first = true}) 65 | next() 66 | end) 67 | ``` 68 | 69 | - #### server:static(fileDir, options) 70 | 71 | - fileDir: string, directory path, **required**, eg: "public/files" 72 | 73 | - options: 74 | 75 | - root: string, mount path, eg: `"/static"` 76 | 77 | - maxAge: number, cache option for maxAge, default is `15552000` (half a year). 78 | 79 | eg: 80 | 81 | ```lua 82 | server:static(path.resolve(module.dir, "../public/"), { 83 | root = "/static/", 84 | maxAge = 31536000 -- one year 85 | }) 86 | ``` 87 | 88 | - #### server:start(port [, address]) 89 | 90 | - port: number, optional, default to 8080 91 | - address: string, optional, default to "127.0.0.1" 92 | 93 | Start your server: 94 | 95 | ```lua 96 | server:start(8080) 97 | ``` 98 | 99 | ### Route 100 | 101 | #### You can route your app easily with below methods: 102 | 103 | `:get` `:post` `:put` `:delete` `:all` 104 | 105 | Example: 106 | 107 | ```lua 108 | -- :all 109 | server:all("/hello", function(req, res, next) 110 | res:send("HELLO!") 111 | end) 112 | 113 | -- :get 114 | server:get("/admin/:page", function(req, res,next) 115 | --- ... 116 | -- login check func 117 | if isLogin then 118 | next() 119 | else 120 | res:status(403):redirect("/login") 121 | end 122 | end) 123 | 124 | server:get("/admin/dashboard", function(req, res, next) 125 | --- ... 126 | res:render("../views/dashbaord", data) 127 | end) 128 | 129 | -- :post 130 | server:post("/posts", function(req, res) 131 | p(req.body) -- print post data; 132 | ... -- create a new post 133 | end) 134 | 135 | -- :put 136 | server:put("/posts/:id", function(req, res) 137 | p(req.params.id) -- print id params in request 138 | ... -- update post with id = `req.params.id` 139 | end) 140 | 141 | -- :delete 142 | server:delete("/posts/:id", function(req, res) 143 | ... -- delete post with id = "req.params.id" 144 | end) 145 | ``` 146 | 147 | Or you can use `server:route()` to match route if your use custom method: 148 | 149 | ```lua 150 | server:route("custom-method", "/posts/:id", function(req, res) 151 | ... -- delete post with id = "req.params.id" 152 | end) 153 | ``` 154 | 155 | ### Request 156 | 157 | - #### req.params 158 | 159 | This property is an array containing properties mapped to the named route “parameters”. For example if you have the route /user/:name, then the “name” property is available to you as req.params.name. This object defaults to {}. 160 | 161 | eg: 162 | 163 | ```lua 164 | -- GET "/user/cyrilis" 165 | server:put("/user/:name", function(req, res) 166 | p(req.params.name) -- output user name `cyrilis` 167 | end) 168 | ``` 169 | 170 | - #### req.query 171 | 172 | This property is an object containing the parsed query-string, defaulting to {}. 173 | 174 | ```lua 175 | -- GET /search?q=tobi+ferret 176 | req.query.q 177 | -- => "tobi ferret" 178 | 179 | -- GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse 180 | req.query.order 181 | -- => "desc" 182 | 183 | req.query.shoe.color 184 | -- => "blue" 185 | 186 | req.query.shoe.type 187 | -- => "converse" 188 | ``` 189 | 190 | - #### req.body 191 | 192 | This property is an object containing the parsed request body. This property defaults to {}. 193 | 194 | ```lua 195 | -- POST user[name]=tobi&user[email]=tobi@learnboost.com 196 | req.body.user.name 197 | -- => "tobi" 198 | 199 | req.body.user.email 200 | -- => "tobi@learnboost.com" 201 | 202 | // POST { "name": "tobi" } 203 | req.body.name 204 | -- => "tobi" 205 | ``` 206 | 207 | You can get post data via req.body, then save to db: 208 | 209 | ```lua 210 | server:post("/posts/new", function(req,res) 211 | if req.body.title and req.body.content then 212 | print("new post") 213 | -- Save to DB: 214 | -- DB.save("post", {title = req.body.title, content = req.body.content}) 215 | res.redirect("/posts") 216 | end 217 | end) 218 | ``` 219 | 220 | - #### req.method 221 | 222 | return request method, eg: `"GET"` 223 | 224 | - #### req.cookie 225 | 226 | This object return parsed cookies sent by the user-agent. If no cookies are sent, it defaults to {}. 227 | 228 | ```lua 229 | -- Cookie: name=cyrilis 230 | req.cookies.name 231 | -- => "cyrilis" 232 | ``` 233 | 234 | - #### req.headers 235 | 236 | return headers of request. 237 | 238 | - #### req.url 239 | 240 | return the url of request. 241 | 242 | ### Response 243 | 244 | `ServerResponse` has several extra methods: 245 | 246 | - #### res:send (data, code, header) 247 | 248 | This method performs a myriad of useful tasks for simple non-streaming responses such as automatically assigning the Content-Length unless previously defined and providing automatic `HEAD` and `HTTP` cache freshness support. 249 | 250 | - `data`: string, content string to render, **required**, eg: "Hello World!" 251 | - `code`: number, status code, eg: 200 252 | - `header`: table, Custom header, eg: `{['Content-Type'] = 'text/plain', ["Content-Length"] = 1000}` 253 | 254 | Example: 255 | 256 | ```lua 257 | server:get("/abc", function(req, res) 258 | res:send("") 259 | end) 260 | ``` 261 | 262 | - #### res:render(tpl, data) 263 | 264 | There are two render engines build in, default is [lua-resty-template](https://github.com/bungle/lua-resty-template/), and the other one is [etlua](https://github.com/leafo/etlua), if you prefer `etlua` as your default render engine, you can add below code in your project: 265 | 266 | ```lua 267 | local env = require("env") 268 | env.set("viewEngine", "etlua") 269 | ``` 270 | 271 | — Or just use .elua as template file extension name 272 | 273 | ```lua 274 | res:render("./views/index.elua", {title = "Hello world!"}) 275 | ``` 276 | 277 | Render engine accept the first argument as template file path or template content, the second arguments as render data. you can visit [lua-resty-template ](https://github.com/bungle/lua-resty-template/) and [etlua](https://github.com/leafo/etlua) project home page for template syntax. 278 | 279 | - `tpl`: string, path to template file, **required**,eg: `"views/post.html"` 280 | - `data`: table, render data, **required**, eg: `{["status" = "success"]}` 281 | 282 | Example of [lua-resty-template ](https://github.com/bungle/lua-resty-template/): 283 | 284 | ```lua 285 | server:get("/posts", function(q,s) 286 | -- get post list, render template. 287 | s:render("./view/post-list.html", {posts = DB.find("posts")}) 288 | end) 289 | ``` 290 | 291 | `./view/post-list.html` 292 | 293 | ```html 294 | 299 | ``` 300 | 301 | Example of [etlua](https://github.com/leafo/etlua) 302 | 303 | ```lua 304 | server:get("/posts", function(q,s) 305 | -- get post list, render template. 306 | s:render("./view/post-list.elua", {posts = DB.find("posts")}) 307 | end) 308 | ``` 309 | 310 | `./view/post-list.elua` 311 | 312 | ```ejs 313 | 318 | ``` 319 | 320 | #### Render Result: 321 | 322 | ```html 323 | 328 | ``` 329 | 330 | 331 | - #### res:redirect(url, code) 332 | 333 | Redirect to the given url with optional status code defaulting to 302 “Found”. 334 | 335 | - `url`: string, url for redirect, **required**, eg: "/404" 336 | - `code`: number, status code, eg: 200 337 | 338 | Example: 339 | 340 | ```lua 341 | server:get("/post-not-exist", function(req, res) 342 | -- if user not login then 343 | res:redirect("/page-not-found", 302) 344 | end) 345 | server:get("/page-not-found", function(req, res) 346 | res:render("404.html", 404) 347 | end) 348 | ``` 349 | 350 | - #### res:status(statusCode) 351 | 352 | Chainable alias of luvit `res.statusCode=`. 353 | 354 | - `statusCode`: number, required, status code, eg: 403 355 | 356 | Example: 357 | 358 | ```lua 359 | server:get("/page-not-exist", function(req, res) 360 | res:status(404):render("404.html") 361 | end) 362 | ``` 363 | 364 | - #### res:sendFile(filePath, headers) 365 | 366 | - `filePath`: string, **required**, send file directly. eg: `res:sendFile("files/preview.pdf")` 367 | - `headers`: table, Custom header, eg: `{['Content-Type'] = 'text/plain', ["Content-Length"] = 1000}` 368 | 369 | Example: 370 | 371 | ```lua 372 | server:get("/files/:file", function(req,res) 373 | res:sendFile("/public/files/".. req.params.file) 374 | end) 375 | ``` 376 | 377 | - #### res:json(obj, code, headers) 378 | 379 | Send a JSON response. When an Table is given mooncake will respond with the JSON representation: 380 | 381 | - `obj`: table, **required**, data for render. eg: `{["status"]="OK"}` 382 | - `code`: number, status code, eg: 200 383 | - `headers`: table, Custom header, eg: `{['Content-Type'] = 'text/plain', ["Content-Length"] = 1000}` 384 | 385 | Example: 386 | 387 | ```lua 388 | server:get("/api.json", function(req, res) 389 | posts = DB:find("*") 390 | res:json(posts, 200) 391 | end) 392 | ``` 393 | 394 | - #### res:flash(type, flash) 395 | 396 | `flash` method like in rails. 397 | 398 | - `type`: string, required, flash type: eg: "success" 399 | - `flash`: string, required, flash content: eg: "You've successfully login, welcome back." 400 | 401 | - #### res:locals(data) 402 | 403 | Store data as local data for render. 404 | 405 | Example: 406 | 407 | ```lua 408 | server:use(function(req, res, next) 409 | if req.query.page ~= 1 then 410 | res:locals({isFirstPage = false}) 411 | end 412 | next() 413 | end) 414 | server:get("/post", function() 415 | render("views/post-list.html") 416 | end) 417 | ``` 418 | 419 | ## Contribute 420 | 421 | Feel free to open issues and submit pull request :) 422 | 423 | ## Licence 424 | 425 | (The MIT License) 426 | 427 | Copyright (c) 2015 Cyril Hou 428 | 429 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 430 | 431 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 432 | 433 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 434 | -------------------------------------------------------------------------------- /index.lua: -------------------------------------------------------------------------------- 1 | require("./libs/Response") 2 | local http = require("http") 3 | local https = require("https") 4 | local path = require("path") 5 | local fs = require('fs') 6 | local mime = require('./libs/mime') 7 | local helpers = require('./libs/helpers') 8 | local querystring = require('querystring') 9 | local JSON = require("json") 10 | local Cookie = require("./libs/cookie") 11 | require("./libs/ansicolors") 12 | 13 | d((" Hello "):bluebg(), (" World "):redbg(), (" from MoonCake "):yellowbg(), (" ! "):greenbg()) 14 | 15 | local Emitter = require("core").Emitter 16 | 17 | local _routes = {} 18 | 19 | local getQueryFromUrl = function(url) 20 | local params = string.match(url, "?(.*)") or "" 21 | return querystring.parse(params) 22 | end 23 | 24 | local MoonCake = Emitter:extend() 25 | 26 | function MoonCake:initialize(options) 27 | options = options or {} 28 | self.options = options 29 | self.notAuthorizedRequest = function(req, res, next) 30 | res:status(403):render("./libs/template/403.html") 31 | end 32 | self.indexDirectory = function(filePath, routePath, req, res) 33 | local fileList = fs.readdirSync(filePath) 34 | local parentPathList = helpers.split2(routePath, "/") 35 | if routePath:sub(#routePath) == "/" then 36 | table.remove(parentPathList) 37 | end 38 | table.remove(parentPathList) 39 | local parentPath = table.concat(parentPathList, "/") 40 | if parentPath == "" then parentPath = "/" end 41 | local data = {parentPath = parentPath, currentPath = routePath, files = {}} 42 | for idx, v in pairs(fileList) do 43 | local fstat = fs.statSync(path.join(filePath, v)) 44 | local vfilePath = path.join(routePath, v) 45 | if fstat.type ~= "file" then 46 | vfilePath = vfilePath .. "/" 47 | end 48 | local lastModifiedTime = os.date("%c", fstat.mtime.sec) 49 | data.files[idx] = { 50 | name = v, 51 | path = vfilePath, 52 | lastModified = lastModifiedTime, 53 | type = fstat.type, 54 | size = fstat.size 55 | } 56 | end 57 | res:render(path.resolve(module.dir, "./libs/template/directory.html"), data) 58 | end 59 | self.isHttps = false 60 | if self.options.isHttps == true then 61 | self.isHttps = true 62 | self.keyPath = self.options.keyPath 63 | end 64 | return self 65 | end 66 | 67 | function MoonCake:listen (port) 68 | return self:start(port) 69 | end 70 | 71 | function MoonCake:start (port, host) 72 | host = host or "127.0.0.1" 73 | if port == nil then 74 | port = 8080 75 | end 76 | local fn = function(req, res) 77 | self:handleRequest(req, res) 78 | end 79 | if self.isHttps == true then 80 | local keyConfig = { 81 | key = fs:readFileSync(path.join(self.keyPath, "key.pem")), 82 | cert = fs:readFileSync(path.join(self.keyPath, "cert.pem")) 83 | } 84 | self.server = https.createServer(keyConfig, fn):listen(port, host) 85 | else 86 | --- Export server instance 87 | self.server = http.createServer(fn):listen(port, host) 88 | end 89 | d(("Moon"):redbg(),("Cake"):yellowbg()," Server Listening at http".. (self.isHttps and "s" or "") .."://".. host .. ":" .. tostring(port) .. "/") 90 | end 91 | 92 | function MoonCake:handleRequest(req, res) 93 | local url = req.url 94 | local method = string.lower(req.method) 95 | res.req = req 96 | local querys = getQueryFromUrl(url) 97 | req.query = querys 98 | req.start_time = helpers.getTime() 99 | res:on("finish", function() 100 | helpers.log(req, res) 101 | end) 102 | if req.headers.cookie then 103 | local cookie = Cookie:parse(req.headers.cookie) 104 | req.cookie = cookie or {} 105 | else 106 | req.cookie = {} 107 | end 108 | if method ~= "get" then 109 | local body = "" 110 | local fileData = "" 111 | req:on("data", function(chunk) 112 | if req.headers['Content-Type'] then 113 | if string.find(req.headers['Content-Type'], "multipart/form-data", 1, true) then 114 | fileData = fileData..chunk 115 | else 116 | body = body..chunk 117 | end 118 | else 119 | body = body..chunk 120 | if #body > 0 then 121 | res:status(400):json({ 122 | status = "failed", 123 | success = false, 124 | code = 400, 125 | message = "Request is not valid, 'Content-Type' should be specified in header if request body exist." 126 | }) 127 | end 128 | end 129 | end) 130 | req:on("end", function() 131 | 132 | local contentType = req.headers['Content-Type'] 133 | if contentType and string.find(contentType, "multipart/form-data", 1, true) then 134 | local boundary = string.match(fileData, "^([^\r?\n?]+)\n?\r?") 135 | local fileArray = helpers.split2(fileData,boundary) 136 | table.remove(fileArray) 137 | table.remove(fileArray, 1) 138 | req.files = {} 139 | req.body = {} 140 | for _, fileString in pairs(fileArray) do 141 | local header, headers = string.match(fileString, "^\r?\n(.-\r?\n\r?\n)"), {} 142 | local content = "" 143 | string.gsub(fileString, "^\r?\n(.-\r?\n\r?\n)(.*)", function(_,b) 144 | if b:sub(#b-1):find("\r?\n") then 145 | local _, n = b:sub(#b-1):find("\r?\n") 146 | content = b:sub(0,#b-n) 147 | end 148 | end) 149 | string.gsub(header, '%s?([^%:?%=?]+)%:?%s?%=?%"?([^%"?%;?%c?]+)%"?%;?%c?', function(k,v) 150 | headers[k] = v 151 | end) 152 | if headers["filename"] then 153 | local tempname = os.tmpname() 154 | fs.writeFileSync(tempname, content) 155 | req.files[headers["name"]] = {path = tempname, name = headers["filename"], ["Content-Type"] = headers["Content-Type"] } 156 | else 157 | req.body[headers["name"]] = content 158 | end 159 | end 160 | else 161 | local bodyObj 162 | if contentType then 163 | if req.headers["Content-Type"]:sub(1,16) == 'application/json' then 164 | -- is this request JSON? 165 | bodyObj = JSON.parse(body) 166 | elseif req.headers["Content-Type"]:sub(1, 33) == "application/x-www-form-urlencoded" then 167 | -- normal form 168 | bodyObj = querystring.parse(body) 169 | else 170 | -- content-type: text/xml 171 | bodyObj = body 172 | end 173 | else 174 | if #body > 0 then 175 | res:status(400):json({ 176 | status = "failed", 177 | success = false, 178 | code = 400, 179 | message = "Bad Request, 'Content-Type' in request headers should be specified if request body exist." 180 | }) 181 | end 182 | end 183 | req.body = bodyObj or {} 184 | if req.body._method then 185 | req._method = req.body._method:lower() 186 | end 187 | end 188 | 189 | req.body = req.body or {} 190 | req.files = req.files or {} 191 | 192 | self:execute(req, res) 193 | end) 194 | else 195 | req.body = {} 196 | self:execute(req, res) 197 | end 198 | end 199 | 200 | function MoonCake.notFound(req, res, err) 201 | if(err) then 202 | MoonCake.serverError(req, res, err) 203 | else 204 | p("404 - Not Found!") 205 | res:status(404):render("./libs/template/404.html") 206 | end 207 | end 208 | 209 | function MoonCake.serverError (req, res, err) 210 | d(("MoonCake: Server Error"):redbg():white()) 211 | p(err) 212 | res:status(500):render("./libs/template/500.html") 213 | end 214 | 215 | function MoonCake:execute(req, res) 216 | function go (i, error, req, res) 217 | local success, err = pcall(function () 218 | i = i or 1 219 | local next = function(error) 220 | if(error)then 221 | MoonCake.serverError(req, res, error) 222 | else 223 | if i < #_routes then 224 | return go(i + 1, error, req, res) 225 | else 226 | MoonCake.notFound(req, res) 227 | end 228 | end 229 | end 230 | 231 | return _routes[i](req, res, next) 232 | end) 233 | if not success then 234 | p(err) 235 | MoonCake.serverError(req, res, err) 236 | end 237 | end 238 | go(1, nil, req, res); 239 | end 240 | 241 | function MoonCake:use(fn) 242 | table.insert(_routes, fn) 243 | return self 244 | end 245 | 246 | function MoonCake:clear() 247 | _routes = {} 248 | end 249 | 250 | local quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])' 251 | 252 | local function escape(str) 253 | return str:gsub(quotepattern, "%%%1") 254 | end 255 | 256 | local function compileRoute(route) 257 | local parts = {"^"} 258 | local names = {} 259 | for a, b, c, d in route:gmatch("([^:]*):([_%a][_%w]*)(:?)([^:]*)") do 260 | if #a > 0 then 261 | parts[#parts + 1] = escape(a) 262 | end 263 | if #c > 0 then 264 | parts[#parts + 1] = "(.*)" 265 | else 266 | parts[#parts + 1] = "([^/]*)" 267 | end 268 | names[#names + 1] = b 269 | if #d > 0 then 270 | parts[#parts + 1] = escape(d) 271 | end 272 | end 273 | if #parts == 1 then 274 | return function (string) 275 | if string == route then 276 | return {} 277 | else 278 | if route == string:gsub("%/$", "") then 279 | return {} 280 | else 281 | if route:gsub("%/$", "") == string then 282 | return {} 283 | end 284 | end 285 | end 286 | end 287 | end 288 | 289 | if #parts > 1 and not(parts[#parts]:match("%*%)")) then 290 | local lastComp = parts[#parts] 291 | if lastComp:sub(#lastComp) == "/" then 292 | lastComp = lastComp:sub(1, #lastComp - 1) 293 | end 294 | parts[#parts] = lastComp .. "%/?" 295 | end 296 | 297 | parts[#parts + 1] = "$" 298 | local pattern = table.concat(parts) 299 | return function (string) 300 | local matches = {string:match(pattern)} 301 | if #matches > 0 then 302 | local results = {} 303 | for i = 1, #matches do 304 | results[i] = matches[i] 305 | results[names[i]] = matches[i] 306 | end 307 | return results 308 | end 309 | end 310 | end 311 | 312 | function MoonCake:route(method, path, fn) 313 | local _path = path and compileRoute(path) 314 | self:use(function (req, res, next) 315 | if method:lower() ~= (req.method):lower() and method:lower() ~= "all" and method:lower() ~= (req._method or ""):lower() then 316 | return next() 317 | end 318 | if req._method and method:lower() ~= (req._method or ""):lower() then 319 | return next() 320 | end 321 | local params 322 | if _path then 323 | local pathname, query = req.url:match("^([^?]*)%??(.*)"); 324 | params = _path(pathname) 325 | if not params then return next() end 326 | end 327 | req.params = params or {} 328 | return fn(req, res, next) 329 | end) 330 | return self 331 | end 332 | 333 | function MoonCake:get(path, fn) 334 | self:route("get", path, fn) 335 | end 336 | function MoonCake:post(path, fn) 337 | self:route("post", path, fn) 338 | end 339 | function MoonCake:put(path, fn) 340 | self:route("put", path, fn) 341 | end 342 | function MoonCake:delete(path, fn) 343 | self:route("delete", path, fn) 344 | end 345 | function MoonCake:patch(path, fn) 346 | self:route("patch", path, fn) 347 | end 348 | function MoonCake:all(path, fn) 349 | self:route("all", path, fn) 350 | end 351 | 352 | function MoonCake:static (fileDir, options) 353 | if type(options) == "string" then 354 | options = { 355 | root = options 356 | } 357 | end 358 | 359 | if type(options) == "number" then 360 | options = { 361 | maxAge = options 362 | } 363 | end 364 | options = options or {} 365 | options.root = options.root or "/" 366 | options.index = options.index or false 367 | print("Serving Directory:" .. fileDir) 368 | local headers = {} 369 | local maxAge = options.maxAge or options.age or 15552000 -- half a year 370 | headers["Cache-Control"] = "public, max-age=" .. tostring(maxAge) 371 | local routePath = path.join(options.root, ":file:") 372 | self:get(routePath, function(req, res, next) 373 | local trimdPath = req.params.file:match("([^?]*)(?*)(.*)") 374 | local filePath = path.resolve(fileDir, trimdPath) 375 | local trimedRoutePath = path.resolve(options.root, trimdPath) 376 | if fs.existsSync(filePath) then 377 | if fs.statSync(filePath).type == "file" then 378 | res:sendFile(filePath, headers) 379 | else 380 | if options.index then 381 | self.indexDirectory(filePath, trimedRoutePath, req, res) 382 | else 383 | self.notAuthorizedRequest(req, res, next) 384 | end 385 | 386 | end 387 | else 388 | next() 389 | end 390 | end) 391 | return self 392 | end 393 | 394 | return MoonCake 395 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | return require("./index.lua") 2 | -------------------------------------------------------------------------------- /libs/Response.lua: -------------------------------------------------------------------------------- 1 | local ServerResponse = require("http").ServerResponse 2 | ServerResponse.flashData = {} 3 | local template = require("./resty-template") 4 | local etlua = require("./etlua") 5 | local path = require("path") 6 | local env = require("env") 7 | local fs = require("fs") 8 | local JSON = require("json") 9 | local Cookie = require("./cookie") 10 | local mime = require('./mime') 11 | local helpers = require('./helpers') 12 | 13 | local extend = function(obj, with_obj) 14 | for k, v in pairs(with_obj) do 15 | obj[k] = v 16 | end 17 | return obj 18 | end 19 | 20 | local function copy(a, b) 21 | a = a or {} 22 | b = b or {} 23 | local obj = {} 24 | for key, value in pairs(b) do 25 | obj[key] = value 26 | end 27 | for key, value in pairs(a) do 28 | obj[key] = value 29 | end 30 | return obj 31 | end 32 | 33 | function ServerResponse:send (data, code, header) 34 | if self._headerSent then 35 | return true 36 | end 37 | self:sendHead(code, header, data) 38 | if data then 39 | self:write(data) 40 | end 41 | self:finish() 42 | collectgarbage() 43 | end 44 | 45 | function ServerResponse:sendHead(code, header, data) 46 | if self._headerSent then 47 | p("------------------------------") 48 | p("Error", "Header Has Been Sent.") 49 | p("------------------------------") 50 | return true 51 | end 52 | self._headerSent = true 53 | code = code or self.statusCode or 200 54 | self:status(code) 55 | header = copy(copy(header, self.headers), { 56 | ["Connection"] = "keep-alive", 57 | ["Content-Type"] = "text/html; charset=utf-8", 58 | ["X-Served-By"] = "MoonCake", 59 | ["Content-Length"] = data and #data or 0 60 | }) 61 | self:writeHead(self.statusCode, header) 62 | end 63 | 64 | function ServerResponse:setCookie(name, value, options) 65 | options = options or {} 66 | if type(options.httpOnly) == "nil" then 67 | options.httpOnly = true 68 | end 69 | local cookieStr = Cookie:serialize (name, value, options) 70 | self:setHeader("Set-Cookie", cookieStr) 71 | return self 72 | end 73 | 74 | function ServerResponse:deleteCookie(name) 75 | local options = { 76 | expires = 0, 77 | path = "/" 78 | } 79 | self:setCookie(name, "" , options) 80 | return self 81 | end 82 | 83 | ServerResponse.removeCookie = ServerResponse.deleteCookie 84 | 85 | function ServerResponse:render(tpl, data) 86 | local callerSource = debug.getinfo(2).source 87 | 88 | if callerSource:sub(1,1) == "@" then 89 | callerSource = callerSource:sub(2) 90 | elseif callerSource:sub(1, 7) == "bundle:" then 91 | callerSource = callerSource 92 | end 93 | 94 | local filePath = path.resolve(path.dirname(callerSource), tpl) 95 | local viewEngine = env.get("viewEngine") 96 | local renderer = viewEngine == "etlua" and etlua or template 97 | local key = "no-cache" 98 | if env.get("PROD") == "TRUE" then 99 | key = nil 100 | end 101 | local localData = self._local or {} 102 | 103 | local flashData = { flash = {}} 104 | if self.req.session and self.req.session.sid then 105 | local sid = self.req.session.sid 106 | flashData = {flash = ServerResponse.flashData[sid] or {} } 107 | ServerResponse.flashData[sid] = nil 108 | end 109 | local renderData = extend(extend(localData, data or {}), flashData) 110 | if viewEngine == "etlua" or path.extname(filePath) == ".etlua" then 111 | local templateString = fs.readFileSync(filePath) 112 | local include = function(fpath, data) 113 | local fpath = path.resolve(path.dirname(filePath), fpath) 114 | local tplString = fs.readFileSync(fpath) 115 | if not tplString then 116 | p("[Error]: File " .. fpath .. " Not Found.") 117 | return "
File: `".. fpath .. "` not found.
" 118 | end 119 | local renderData = extend(extend(localData or {}, {currentPath = fpath, include = include }),data or {}) 120 | local tplResult, err = etlua.render(tplString, renderData) 121 | if tplResult then 122 | return tplResult 123 | else 124 | p("[Error Rendering HTML](:include) ", err) 125 | return "

Internal Error

Error while render template :(

" 126 | end 127 | end 128 | renderData = extend(localData, { 129 | currentPath = filePath, 130 | include = include 131 | }) 132 | if not templateString then 133 | templateString = tpl 134 | end 135 | local result, error = etlua.render(templateString, renderData) 136 | if not result then 137 | p("[Error Rendering HTML] ", error) 138 | self:status(500):render("./template/500.html") 139 | else 140 | self:send(result) 141 | end 142 | else 143 | local status, result = pcall(function() return template.render(filePath, renderData, key) end) 144 | if status then 145 | self:send(result) 146 | else 147 | p("[Error Rendering HTML] ",result) 148 | self:fail("Internal Error") 149 | end 150 | end 151 | end 152 | 153 | function ServerResponse:renderToFile(tpl, data, file, continueOnError) 154 | local callerSource = debug.getinfo(2).source 155 | 156 | if callerSource:sub(1,1) == "@" then 157 | callerSource = callerSource:sub(2) 158 | elseif callerSource:sub(1, 7) == "bundle:" then 159 | callerSource = callerSource 160 | end 161 | 162 | local filePath = path.resolve(path.dirname(callerSource), tpl) 163 | local viewEngine = env.get("viewEngine") 164 | local renderer = viewEngine == "etlua" and etlua or template 165 | local key = "no-cache" 166 | if env.get("PROD") == "TRUE" then 167 | key = nil 168 | end 169 | local localData = self._local or {} 170 | 171 | local flashData = { flash = nil} 172 | if self.req.session and self.req.session.sid then 173 | local sid = self.req.session.sid 174 | flashData = {flash = ServerResponse.flashData[sid] or {} } 175 | ServerResponse.flashData[sid] = nil 176 | end 177 | local renderData = extend(extend(localData, data or {}), flashData) 178 | 179 | local status, result = pcall(function() return template.render(filePath, renderData, key) end) 180 | if status then 181 | fs.writeFileSync(file, result) 182 | else 183 | p("[Error Rendering HTML] ",result) 184 | if (continueOnError) then 185 | fs.writeFileSync(file, result) 186 | end 187 | end 188 | end 189 | 190 | function ServerResponse:status (statusCode) 191 | self.statusCode = statusCode 192 | return self 193 | end 194 | 195 | function ServerResponse:sendFile(filePath, headers) 196 | headers = headers or {} 197 | local callerSource = debug.getinfo(2).source 198 | 199 | if callerSource:sub(1,1) == "@" then 200 | callerSource = callerSource:sub(2) 201 | elseif callerSource:sub(1, 7) == "bundle:" then 202 | callerSource = callerSource 203 | end 204 | 205 | filePath = path.resolve(path.dirname(callerSource), filePath) 206 | local stat = fs.statSync(filePath) 207 | if not(stat) then 208 | return self:send("

Can't get "..filePath .. "

", 404) 209 | end 210 | local fileType = mime.guess(filePath) or "application/octet-stream: charset=utf8" 211 | local etag = helpers.calcEtag(stat) 212 | local lastModified = os.date("%a, %d %b %Y %H:%M:%S GMT", stat.mtime.sec) 213 | local header = extend({ 214 | ["Content-Type"] = fileType, 215 | ["Content-Length"] = stat.size, 216 | ['ETag'] = etag, 217 | ['Last-Modified'] = lastModified 218 | }, headers or {}) 219 | local statusCode = 200 220 | local content = fs.readFileSync(filePath) 221 | if self.req.headers["if-none-match"] == etag or self.req.headers["if-modified-since"] == lastModified then 222 | statusCode = 304 223 | content = nil 224 | end 225 | if self._headerSent then 226 | return true 227 | end 228 | self:sendHead(statusCode, header, nil) 229 | fs.createReadStream(filePath):pipe(self) 230 | end 231 | 232 | function ServerResponse:redirect(url, code) 233 | code = code or 302 234 | self:status(code):setHeader("Location", url) 235 | self:send() 236 | return self 237 | end 238 | 239 | function ServerResponse:json(obj, code, headers) 240 | headers = copy(headers, { 241 | ["Content-Type"] = "application/json" 242 | }) 243 | self:send(JSON.stringify(obj), code, headers) 244 | end 245 | 246 | function ServerResponse:flash(type, flash) 247 | local sid 248 | if self.req.session and self.req.session.sid then 249 | sid = self.req.session.sid 250 | ServerResponse.flashData[sid] = ServerResponse.flashData[sid] or {} 251 | ServerResponse.flashData[sid][type] = flash 252 | end 253 | end 254 | 255 | function ServerResponse:locals(data) 256 | local localData = self._local or {} 257 | self._local = extend(localData, data) 258 | end 259 | 260 | function ServerResponse:fail(reason) 261 | self:status(500):send(reason) 262 | end 263 | 264 | function ServerResponse:not_modified(header) 265 | self:send(nil, 304, header) 266 | end 267 | -------------------------------------------------------------------------------- /libs/ansicolors.lua: -------------------------------------------------------------------------------- 1 | -- ansicolors.lua v1.0.2 (2012-08) 2 | 3 | -- Copyright (c) 2009 Rob Hoelz 4 | -- Copyright (c) 2011 Enrique García Cota 5 | -- 6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 7 | -- of this software and associated documentation files (the "Software"), to deal 8 | -- in the Software without restriction, including without limitation the rights 9 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | -- copies of the Software, and to permit persons to whom the Software is 11 | -- furnished to do so, subject to the following conditions: 12 | -- 13 | -- The above copyright notice and this permission notice shall be included in 14 | -- all copies or substantial portions of the Software. 15 | -- 16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | -- THE SOFTWARE. 23 | 24 | 25 | -- support detection 26 | local function isWindows() 27 | return type(package) == 'table' and type(package.config) == 'string' and package.config:sub(1,1) == '\\' 28 | end 29 | 30 | local supported = not isWindows() 31 | if isWindows() then supported = os.getenv("ANSICON") end 32 | 33 | local keys = { 34 | -- reset 35 | reset = 0, 36 | 37 | -- misc 38 | bright = 1, 39 | dim = 2, 40 | italic = 3, 41 | underline = 4, 42 | blink = 5, 43 | invert = 7, 44 | hidden = 8, 45 | 46 | -- foreground colors 47 | black = 30, 48 | red = 31, 49 | green = 32, 50 | yellow = 33, 51 | blue = 34, 52 | magenta = 35, 53 | cyan = 36, 54 | white = 37, 55 | 56 | -- background colors 57 | blackbg = 40, 58 | redbg = 41, 59 | greenbg = 42, 60 | yellowbg = 43, 61 | bluebg = 44, 62 | magentabg = 45, 63 | cyanbg = 46, 64 | whitebg = 47 65 | } 66 | 67 | local escapeString = string.char(27) .. '[%dm' 68 | local function escapeNumber(number) 69 | return escapeString:format(number) 70 | end 71 | 72 | local function escapeKeys(str) 73 | 74 | if not supported then return "" end 75 | 76 | local buffer = {} 77 | local number 78 | for word in str:gmatch("%w+") do 79 | number = keys[word] 80 | assert(number, "Unknown key: " .. word) 81 | table.insert(buffer, escapeNumber(number) ) 82 | end 83 | 84 | return table.concat(buffer) 85 | end 86 | 87 | local function replaceCodes(str) 88 | str = string.gsub(str,"(%%{(.-)})", function(_, str) return escapeKeys(str) end ) 89 | return str 90 | end 91 | 92 | -- public 93 | 94 | local function ansicolors( str ) 95 | str = tostring(str or '') 96 | 97 | return replaceCodes('%{reset}' .. str .. '%{reset}') 98 | end 99 | 100 | 101 | -- function print(...) 102 | -- io.write(table.concat({...})) 103 | -- io.flush() 104 | -- end 105 | 106 | 107 | for key, value in pairs(keys) do 108 | string[key] = function(self, str) 109 | if self:find("%%{(.-)}", 1) then 110 | str = self:gsub("%%{(.-)}", function(str) 111 | return "%{"..key.." "..str.."}" 112 | end,1) 113 | if self:sub(-8) == "%{reset}" then 114 | self = self .. "%{reset}" 115 | end 116 | else 117 | str = "%{"..key.."}".. self .. "%{reset}" 118 | end 119 | return str 120 | end 121 | end 122 | 123 | function string:print() 124 | print(self) 125 | end 126 | 127 | old_print = print 128 | _G.d = function(...) 129 | local allString = true 130 | local _temp = ... 131 | if _temp == nil then 132 | return old_print(...) 133 | end 134 | for _, value in pairs({...}) do 135 | if type(value) ~= "string" then 136 | allString = false 137 | end 138 | end 139 | if not allString then 140 | old_print(...) 141 | else 142 | old_print(ansicolors(table.concat({...}))) 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /libs/cookie.lua: -------------------------------------------------------------------------------- 1 | -- Code From: https://github.com/voronianski/luvit-connect/blob/master/lib/cookie.lua 2 | 3 | -- Cookies module 4 | -- TO DO: extract to separate luvit module 5 | 6 | local os = require('os') 7 | local qs = require('querystring') 8 | local table = require('table') 9 | local openssl = require("openssl") 10 | local json = require('json') 11 | local Object = require('core').Object 12 | local helpers = require('./helpers') 13 | 14 | local Cookie = Object:extend() 15 | 16 | function Cookie.meta.__tostring () 17 | return '' 18 | end 19 | 20 | local function trim(str, what) 21 | if what == nil then 22 | what = '%s+' 23 | end 24 | str = string.gsub(str, '^' .. what, '') 25 | str = string.gsub(str, what .. '$', '') 26 | return str 27 | end 28 | 29 | -- Serialize a "name-value" pair into a cookie string suitable for http headers. 30 | -- An optional options table specifies cookie parameters. 31 | -- @param {String} name 32 | -- @param {String} value 33 | -- @param {Table} options 34 | -- @return {String} 35 | 36 | function Cookie:serialize (name, value, options) 37 | options = options or {} 38 | options.encode = options.encode or qs.urlencode 39 | 40 | local cookiePairs = { name .. '=' .. options.encode(value) } 41 | 42 | if options.maxAge then table.insert(cookiePairs, 'Max-Age=' .. options.maxAge) end 43 | if options.domain then table.insert(cookiePairs, 'Domain=' .. options.domain) end 44 | if options.path then table.insert(cookiePairs, 'Path=' .. options.path) end 45 | if options.expires then table.insert(cookiePairs, 'Expires=' .. os.date('!%a, %d %b %Y %H:%M:%S GMT', options.expires)) end 46 | if options.httpOnly then table.insert(cookiePairs, 'HttpOnly') end 47 | if options.secure then table.insert(cookiePairs, 'Secure') end 48 | 49 | return table.concat(cookiePairs, '; ') 50 | end 51 | 52 | -- Parse the given cookie header string into a table 53 | -- @param {String} str 54 | -- @return {Table} 55 | 56 | function Cookie:parse (str, options) 57 | options = options or {} 58 | options.decode = options.decode or qs.urldecode 59 | 60 | local tbl = {} 61 | local cookiePairs = helpers.split(str, '%;%,') 62 | 63 | table.foreach(cookiePairs, function (index, pair) 64 | local eqIndex = helpers.indexOf(pair, '=') 65 | 66 | if not eqIndex then return end 67 | 68 | local key = trim(pair:sub(1, eqIndex-1)) 69 | local val = pair:sub(eqIndex+1, #pair) 70 | 71 | -- quoted values 72 | if val:find('%"', 1) == 1 then 73 | val = val:sub(2, #val-1) 74 | end 75 | 76 | if not tbl[key] then 77 | tbl[key] = options.decode(val) 78 | end 79 | end) 80 | 81 | return tbl 82 | end 83 | 84 | -- Sign value with secret. 85 | -- @param {String} value 86 | -- @param {String} secret 87 | -- @return {String} 88 | 89 | function Cookie:sign (value, secret) 90 | if type(value) ~= 'string' then error('cookie required') end 91 | if type(secret) ~= 'string' then error('secret required') end 92 | 93 | local sha256 = openssl.digest.get("sha256") 94 | local d = openssl.digest.new(sha256) 95 | d:update(secret) 96 | d:update(value) 97 | local signed = value .. '.' .. d:final():gsub('%=+$', '') 98 | return signed 99 | end 100 | 101 | -- Unsign and decode value with secret, returns 'false' if signature is invalid. 102 | -- @param {String} value 103 | -- @param {String} secret 104 | -- @return {String} 105 | 106 | function Cookie:unsign (value, secret) 107 | if type(value) ~= 'string' then error('cookie required') end 108 | if type(secret) ~= 'string' then error('secret required') end 109 | 110 | local str = value:sub(1, helpers.lastIndexOf(value, '%.') - 1) 111 | 112 | if self:sign(str, secret) == value then 113 | return str 114 | end 115 | 116 | return false 117 | end 118 | 119 | -- Parse JSON values in cookies 120 | -- @param {Table} tbl 121 | -- return {Table} 122 | 123 | function Cookie:parseJSONCookies (tbl) 124 | table.foreach(tbl, function (index, str) 125 | if helpers.indexOf(str, 'j:') == 1 then 126 | local parseStatus, result = pcall(json.parse, str:sub(3, #str)) 127 | 128 | if not parseStatus then return nil end 129 | 130 | tbl[index] = result 131 | end 132 | end) 133 | 134 | return tbl 135 | end 136 | 137 | -- Parse signed cookies. 138 | -- Returns a table containing the decoded key/value pairs, while removing the signed key from 'tbl'. 139 | -- @param {Table} tbl 140 | -- @param {String} secret 141 | -- return {Table} 142 | 143 | function Cookie:parseSignedCookies (tbl, secret) 144 | local result 145 | 146 | table.foreach(tbl, function (index, str) 147 | if helpers.indexOf(str, 's:') == 1 then 148 | local value = self.unsign(str:sub(3, #str), secret) 149 | if value then 150 | result[index] = value 151 | tbl[index] = nil 152 | end 153 | end 154 | end) 155 | 156 | return result 157 | end 158 | 159 | return Cookie 160 | -------------------------------------------------------------------------------- /libs/etlua.lua: -------------------------------------------------------------------------------- 1 | -- Copyright : 2 | -- https://github.com/leafo/etlua/blob/master/etlua.lua 3 | 4 | local VERSION = "1.0.2" 5 | local insert, concat 6 | do 7 | local _obj_0 = table 8 | insert, concat = _obj_0.insert, _obj_0.concat 9 | end 10 | local load, setfenv, assert, type, error, tostring, tonumber, setmetatable 11 | do 12 | local _obj_0 = _G 13 | load, setfenv, assert, type, error, tostring, tonumber, setmetatable = _obj_0.load, _obj_0.setfenv, _obj_0.assert, _obj_0.type, _obj_0.error, _obj_0.tostring, _obj_0.tonumber, _obj_0.setmetatable 14 | end 15 | setfenv = setfenv or function(fn, env) 16 | local name 17 | local i = 1 18 | while true do 19 | name = debug.getupvalue(fn, i) 20 | if not name or name == "_ENV" then 21 | break 22 | end 23 | i = i + 1 24 | end 25 | if name then 26 | debug.upvaluejoin(fn, i, (function() 27 | return env 28 | end), 1) 29 | end 30 | return fn 31 | end 32 | local html_escape_entities = { 33 | ['&'] = '&', 34 | ['<'] = '<', 35 | ['>'] = '>', 36 | ['"'] = '"', 37 | ["'"] = ''' 38 | } 39 | local html_escape 40 | html_escape = function(str) 41 | return (str:gsub([=[["><'&]]=], html_escape_entities)) 42 | end 43 | local get_line 44 | get_line = function(str, line_num) 45 | for line in str:gmatch("([^\n]*)\n?") do 46 | if line_num == 1 then 47 | return line 48 | end 49 | line_num = line_num - 1 50 | end 51 | end 52 | local pos_to_line 53 | pos_to_line = function(str, pos) 54 | local line = 1 55 | for _ in str:sub(1, pos):gmatch("\n") do 56 | line = line + 1 57 | end 58 | return line 59 | end 60 | local Compiler 61 | do 62 | local _base_0 = { 63 | render = function(self) 64 | return table.concat(self.buffer) 65 | end, 66 | push = function(self, str, ...) 67 | local i = self.i + 1 68 | self.buffer[i] = str 69 | self.i = i 70 | if ... then 71 | return self:push(...) 72 | end 73 | end, 74 | header = function(self) 75 | return self:push("local _tostring, _escape, _b, _b_i = ...\n") 76 | end, 77 | footer = function(self) 78 | return self:push("return _b") 79 | end, 80 | increment = function(self) 81 | return self:push("_b_i = _b_i + 1\n") 82 | end, 83 | mark = function(self, pos) 84 | return self:push("--[[", tostring(pos), "]] ") 85 | end, 86 | assign = function(self, ...) 87 | self:push("_b[_b_i] = ", ...) 88 | if ... then 89 | return self:push("\n") 90 | end 91 | end 92 | } 93 | _base_0.__index = _base_0 94 | local _class_0 = setmetatable({ 95 | __init = function(self) 96 | self.buffer = { } 97 | self.i = 0 98 | end, 99 | __base = _base_0, 100 | __name = "Compiler" 101 | }, { 102 | __index = _base_0, 103 | __call = function(cls, ...) 104 | local _self_0 = setmetatable({}, _base_0) 105 | cls.__init(_self_0, ...) 106 | return _self_0 107 | end 108 | }) 109 | _base_0.__class = _class_0 110 | Compiler = _class_0 111 | end 112 | local Parser 113 | do 114 | local _base_0 = { 115 | open_tag = "<%", 116 | close_tag = "%>", 117 | modifiers = "^[=-]", 118 | html_escape = true, 119 | next_tag = function(self) 120 | local start, stop = self.str:find(self.open_tag, self.pos, true) 121 | if not (start) then 122 | self:push_raw(self.pos, #self.str) 123 | return false 124 | end 125 | if not (start == self.pos) then 126 | self:push_raw(self.pos, start - 1) 127 | end 128 | self.pos = stop + 1 129 | local modifier 130 | if self.str:match(self.modifiers, self.pos) then 131 | do 132 | local _with_0 = self.str:sub(self.pos, self.pos) 133 | self.pos = self.pos + 1 134 | modifier = _with_0 135 | end 136 | end 137 | local close_start, close_stop = self.str:find(self.close_tag, self.pos, true) 138 | if not (close_start) then 139 | return nil, self:error_for_pos(start, "failed to find closing tag") 140 | end 141 | while self:in_string(self.pos, close_start) do 142 | close_start, close_stop = self.str:find(self.close_tag, close_stop, true) 143 | end 144 | local trim_newline 145 | if "-" == self.str:sub(close_start - 1, close_start - 1) then 146 | close_start = close_start - 1 147 | trim_newline = true 148 | end 149 | self:push_code(modifier or "code", self.pos, close_start - 1) 150 | self.pos = close_stop + 1 151 | if trim_newline then 152 | do 153 | local match = self.str:match("^\n", self.pos) 154 | if match then 155 | self.pos = self.pos + #match 156 | end 157 | end 158 | end 159 | return true 160 | end, 161 | in_string = function(self, start, stop) 162 | local in_string = false 163 | local end_delim = nil 164 | local escape = false 165 | local pos = 0 166 | local skip_until = nil 167 | local chunk = self.str:sub(start, stop) 168 | for char in chunk:gmatch(".") do 169 | local _continue_0 = false 170 | repeat 171 | pos = pos + 1 172 | if skip_until then 173 | if pos <= skip_until then 174 | _continue_0 = true 175 | break 176 | end 177 | skip_until = nil 178 | end 179 | if end_delim then 180 | if end_delim == char and not escape then 181 | in_string = false 182 | end_delim = nil 183 | end 184 | else 185 | if char == "'" or char == '"' then 186 | end_delim = char 187 | in_string = true 188 | end 189 | if char == "[" then 190 | do 191 | local lstring = chunk:match("^%[=*%[", pos) 192 | if lstring then 193 | local lstring_end = lstring:gsub("%[", "]") 194 | local lstring_p1, lstring_p2 = chunk:find(lstring_end, pos, true) 195 | if not (lstring_p1) then 196 | return true 197 | end 198 | skip_until = lstring_p2 199 | end 200 | end 201 | end 202 | end 203 | escape = char == "\\" 204 | _continue_0 = true 205 | until true 206 | if not _continue_0 then 207 | break 208 | end 209 | end 210 | return in_string 211 | end, 212 | push_raw = function(self, start, stop) 213 | return insert(self.chunks, self.str:sub(start, stop)) 214 | end, 215 | push_code = function(self, kind, start, stop) 216 | return insert(self.chunks, { 217 | kind, 218 | self.str:sub(start, stop), 219 | start 220 | }) 221 | end, 222 | compile = function(self, str) 223 | local success, err = self:parse(str) 224 | if not (success) then 225 | return nil, err 226 | end 227 | local fn 228 | fn, err = self:load(self:chunks_to_lua()) 229 | if not (fn) then 230 | return nil, err 231 | end 232 | return function(...) 233 | local success, result 234 | local args = ... 235 | success, result = pcall(function() 236 | local result, err = self:run(fn, args) 237 | if result then 238 | return result 239 | else 240 | return error(err) 241 | end 242 | end) 243 | if success then 244 | return concat(result) 245 | else 246 | return nil, result 247 | end 248 | end 249 | end, 250 | parse = function(self, str) 251 | self.str = str 252 | assert(type(self.str) == "string", "expecting string for parse") 253 | self.pos = 1 254 | self.chunks = { } 255 | while true do 256 | local found, err = self:next_tag() 257 | if err then 258 | return nil, err 259 | end 260 | if not (found) then 261 | break 262 | end 263 | end 264 | return true 265 | end, 266 | parse_error = function(self, err, code) 267 | local line_no, err_msg = err:match("%[.-%]:(%d+): (.*)$") 268 | line_no = tonumber(line_no) 269 | if not (line_no) then 270 | return 271 | end 272 | local line = get_line(code, line_no) 273 | local source_pos = tonumber(line:match("^%-%-%[%[(%d+)%]%]")) 274 | if not (source_pos) then 275 | return 276 | end 277 | return self:error_for_pos(source_pos, err_msg) 278 | end, 279 | error_for_pos = function(self, source_pos, err_msg) 280 | local source_line_no = pos_to_line(self.str, source_pos) 281 | local source_line = get_line(self.str, source_line_no) 282 | return tostring(err_msg) .. " [" .. tostring(source_line_no) .. "]: " .. tostring(source_line) 283 | end, 284 | load = function(self, code, name) 285 | if name == nil then 286 | name = "etlua" 287 | end 288 | local code_fn 289 | do 290 | local code_ref = code 291 | code_fn = function() 292 | do 293 | local ret = code_ref 294 | code_ref = nil 295 | return ret 296 | end 297 | end 298 | end 299 | local fn, err = load(code_fn, name) 300 | if not (fn) then 301 | do 302 | local err_msg = self:parse_error(err, code) 303 | if err_msg then 304 | return nil, err_msg 305 | end 306 | end 307 | return nil, err 308 | end 309 | return fn 310 | end, 311 | run = function(self, fn, env, buffer, i, ...) 312 | if env == nil then 313 | env = { } 314 | end 315 | local combined_env = setmetatable({ }, { 316 | __index = function(self, name) 317 | local val = env[name] 318 | if val == nil then 319 | val = _G[name] 320 | end 321 | return val 322 | end 323 | }) 324 | if not (buffer) then 325 | buffer = { } 326 | i = 0 327 | end 328 | setfenv(fn, combined_env) 329 | return fn(tostring, html_escape, buffer, i, ...) 330 | end, 331 | compile_to_lua = function(self, str, ...) 332 | local success, err = self:parse(str) 333 | if not (success) then 334 | return nil, err 335 | end 336 | return self:chunks_to_lua(...) 337 | end, 338 | chunks_to_lua = function(self, compiler_cls) 339 | if compiler_cls == nil then 340 | compiler_cls = Compiler 341 | end 342 | local r = compiler_cls() 343 | r:header() 344 | local _list_0 = self.chunks 345 | for _index_0 = 1, #_list_0 do 346 | local chunk = _list_0[_index_0] 347 | local t = type(chunk) 348 | if t == "table" then 349 | t = chunk[1] 350 | end 351 | local _exp_0 = t 352 | if "string" == _exp_0 then 353 | r:increment() 354 | r:assign(("%q"):format(chunk)) 355 | elseif "code" == _exp_0 then 356 | r:mark(chunk[3]) 357 | r:push(chunk[2], "\n") 358 | elseif "=" == _exp_0 or "-" == _exp_0 then 359 | r:increment() 360 | r:mark(chunk[3]) 361 | r:assign() 362 | if t == "=" and self.html_escape then 363 | r:push("_escape(_tostring(", chunk[2], "))\n") 364 | else 365 | r:push("_tostring(", chunk[2], ")\n") 366 | end 367 | else 368 | error("unknown type " .. tostring(t)) 369 | end 370 | end 371 | r:footer() 372 | return r:render() 373 | end 374 | } 375 | _base_0.__index = _base_0 376 | local _class_0 = setmetatable({ 377 | __init = function() end, 378 | __base = _base_0, 379 | __name = "Parser" 380 | }, { 381 | __index = _base_0, 382 | __call = function(cls, ...) 383 | local _self_0 = setmetatable({}, _base_0) 384 | cls.__init(_self_0, ...) 385 | return _self_0 386 | end 387 | }) 388 | _base_0.__class = _class_0 389 | Parser = _class_0 390 | end 391 | local compile 392 | do 393 | local _base_0 = Parser() 394 | local _fn_0 = _base_0.compile 395 | compile = function(...) 396 | return _fn_0(_base_0, ...) 397 | end 398 | end 399 | local render 400 | render = function(str, ...) 401 | local fn, err = compile(str) 402 | if fn then 403 | return fn(...) 404 | else 405 | return nil, err 406 | end 407 | end 408 | return { 409 | compile = compile, 410 | render = render, 411 | Parser = Parser, 412 | Compiler = Compiler, 413 | _version = VERSION 414 | } -------------------------------------------------------------------------------- /libs/fse.lua: -------------------------------------------------------------------------------- 1 | local fs = require("fs"); 2 | local pathJoin = require('luvi').path.join 3 | 4 | function getDirData (dir, files, rootDir) 5 | local dirFiles = fs.readdirSync(dir) 6 | files = files or {} 7 | for _, file in pairs(dirFiles) do 8 | local objPath = pathJoin(dir, file) 9 | local stat = fs.statSync(objPath) 10 | if stat.type == "file" then 11 | local filePath = file 12 | if rootDir and rootDir ~= dir then 13 | filePath = string.gsub(objPath, rootDir, "") 14 | end 15 | files[filePath] = stat 16 | else 17 | getDirData(objPath, files, rootDir or dir) 18 | end 19 | end 20 | return files 21 | end 22 | 23 | function readDirFile (dir, options) 24 | return getDirData(dir, nil, dir) 25 | end 26 | 27 | return readDirFile 28 | -------------------------------------------------------------------------------- /libs/helpers.lua: -------------------------------------------------------------------------------- 1 | -- Some code from: https://github.com/voronianski/luvit-connect/blob/master/lib/helpers.lua 2 | 3 | local floor = require('math').floor 4 | local http = require('http') 5 | local table = require('table') 6 | local string = require('string') 7 | local math = require('math') 8 | require("./ansicolors") 9 | 10 | local digits = { 11 | "0", "1", "2", "3", "4", "5", "6", "7", 12 | "8", "9", "A", "B", "C", "D", "E", "F", 13 | "G", "H", "I", "J", "K", "L", "M", "N", 14 | "O", "P", "Q", "R", "S", "T", "U", "V", 15 | "W", "X", "Y", "Z", "a", "b", "c", "d", 16 | "e", "f", "g", "h", "i", "j", "k", "l", 17 | "m", "n", "o", "p", "q", "r", "s", "t", 18 | "u", "v", "w", "x", "y", "z", "_", "$" 19 | } 20 | 21 | local function numToBase(num, base) 22 | local parts = {} 23 | repeat 24 | table.insert(parts, digits[(num % base) + 1]) 25 | num = floor(num / base) 26 | until num == 0 27 | return table.concat(parts) 28 | end 29 | 30 | -- microsecond precision 31 | local ffi = require("ffi") 32 | 33 | local getTime; 34 | do 35 | if jit.os == "Windows" then 36 | ffi.cdef [[ 37 | typedef unsigned long DWORD, *PDWORD, *LPDWORD; 38 | typedef struct _FILETIME { 39 | DWORD dwLowDateTime; 40 | DWORD dwHighDateTime; 41 | } FILETIME, *PFILETIME; 42 | 43 | void GetSystemTimeAsFileTime ( FILETIME* ); 44 | ]] 45 | local ft = ffi.new ( "FILETIME[1]" ) 46 | getTime = function ( ) -- As found in luasocket's timeout.c 47 | ffi.C.GetSystemTimeAsFileTime ( ft ) 48 | local t = tonumber ( ft[0].dwLowDateTime ) / 1e7 + tonumber ( ft[0].dwHighDateTime ) * ( 2^32 / 1e7 ) 49 | -- Convert to Unix Epoch time (time since January 1, 1970 (UTC)) 50 | t = t - 11644473600 51 | return math.floor(t * 1000 + 0.5) 52 | end 53 | else -- Assume posix 54 | 55 | if pcall(ffi.typeof, "struct timeval") then 56 | else 57 | ffi.cdef[[ 58 | typedef long time_t; 59 | 60 | typedef struct timeval { 61 | time_t tv_sec; 62 | time_t tv_usec; 63 | } timeval; 64 | 65 | int gettimeofday(struct timeval* t, void* tzp); 66 | ]] 67 | end 68 | 69 | local gettimeofday_struct = ffi.new("timeval") 70 | getTime = function () 71 | ffi.C.gettimeofday(gettimeofday_struct, nil) 72 | return tonumber(gettimeofday_struct.tv_sec) * 1000 + tonumber(gettimeofday_struct.tv_usec / 1000) 73 | end 74 | end 75 | end 76 | 77 | local function log (req, res) 78 | local currentDate = os.date("[%Y-%m-%d %H:%M:%S]"):dim() 79 | local statusCode = res.statusCode 80 | local stCode = "" 81 | if 100 <= statusCode and statusCode < 200 then 82 | stCode = tostring(statusCode):red() 83 | elseif 200 <= statusCode and statusCode < 300 then 84 | stCode = tostring(statusCode):green() 85 | elseif 300 <= statusCode and statusCode < 400 then 86 | stCode = tostring(statusCode):blue() 87 | elseif 400 <= statusCode and statusCode < 500 then 88 | stCode = tostring(statusCode):yellow() 89 | else 90 | stCode = tostring(statusCode):red() 91 | end 92 | local timeCosted = getTime() - req.start_time 93 | d(currentDate:dim(), " - [", stCode, "]", (" " .. tostring(req.method) .. " "):yellow(), (tostring(timeCosted) .. "ms "):cyan(), req.url:blue(), (" UserAgent: "):magenta(), req.headers["user-agent"]) 94 | end 95 | 96 | local function calcEtag(stat) 97 | return (not stat.type == "file" and 'W/' or '') .. 98 | '"' .. numToBase(stat.ino or 0, 64) .. 99 | '-' .. numToBase(stat.size, 64) .. 100 | '-' .. numToBase(stat.mtime.sec, 64) .. '"' 101 | end 102 | 103 | local function copy(obj, seen) 104 | if type(obj) ~= 'table' then return obj end 105 | if seen and seen[obj] then return seen[obj] end 106 | local s = seen or {} 107 | local res = setmetatable({}, getmetatable(obj)) 108 | s[obj] = res 109 | for k, v in pairs(obj) do res[copy(k, s)] = copy(v, s) end 110 | return res 111 | end 112 | 113 | -- nil, boolean, number, string, userdata, function, thread, and table. 114 | function colorise (anything) 115 | typeStr = type(anything) 116 | str = tostring(anything) 117 | if typeStr == "nil" then 118 | return str:dim() 119 | elseif typeStr == "boolean" then 120 | return str:cyan() 121 | elseif typeStr == "number" then 122 | return str:red() 123 | elseif typeStr == "string" then 124 | return str:yellow() 125 | elseif typeStr == "userdata" then 126 | return str:cyan() 127 | elseif typeStr == "function" then 128 | return str:blue() 129 | elseif typeStr == "thread" then 130 | return str:white() 131 | elseif typeStr == "table" then 132 | return str:magenta() 133 | end 134 | end 135 | 136 | -- pretty print of tables to console 137 | function tprint (tbl, indent) 138 | indent = indent or 0 139 | if indent >= 5 then return false end 140 | for key, value in pairs(tbl) do 141 | 142 | key = colorise(key) 143 | 144 | formatting = string.rep(' ', indent) .. key .. ': ' 145 | 146 | if type(value) == 'table' then 147 | print(formatting) 148 | tprint(value, indent + 1) 149 | else 150 | print(formatting .. colorise(value)) 151 | end 152 | end 153 | end 154 | 155 | -- filter values from table 156 | function filter (tbl, fn) 157 | local result = {} 158 | 159 | if not tbl or type(tbl) ~= 'table' then return result end 160 | 161 | for key, value in pairs(tbl) do 162 | if fn(value, key, tbl) then 163 | table.insert(result, value) 164 | end 165 | end 166 | 167 | return result 168 | end 169 | 170 | -- round number to decimals, defaults to 2 decimals 171 | function roundToDecimals (num, decimals) 172 | decimals = decimals or 2 173 | local shift = 10 ^ decimals 174 | local result = math.floor(num * shift + 0.5) / shift 175 | return result 176 | end 177 | 178 | -- merge table2 into table1 179 | function merge (table1, table2) 180 | for key, value in pairs(table2) do 181 | table1[key] = value 182 | end 183 | return table1 184 | end 185 | 186 | -- get index of field in table or character in string 187 | function indexOf (target, field) 188 | if type(target) == 'string' then 189 | return target:find(field, 1, true) 190 | end 191 | 192 | for index, value in pairs(target) do 193 | if value == field then 194 | return index 195 | end 196 | end 197 | 198 | return nil 199 | end 200 | 201 | -- last index of an element in string 202 | function lastIndexOf (str, elem) 203 | if type(str) ~= 'string' then error('string required') end 204 | if type(str) ~= 'string' then error('elem required') end 205 | 206 | local index = str:match('.*' .. elem .. '()') 207 | if not index then 208 | return nil 209 | else 210 | return index - 1 211 | end 212 | end 213 | 214 | -- split string 215 | function split (str, sep) 216 | sep = sep or '%s+' 217 | 218 | local result = {} 219 | local i = 1 220 | 221 | for value in str:gmatch('([^' .. sep .. ']+)') do 222 | result[i] = value 223 | i = i + 1 224 | end 225 | 226 | return result 227 | end 228 | 229 | local find , sub= string.find, string.sub 230 | function split2(str, sep, nmax) 231 | if sep == nil then 232 | sep = '%s+' 233 | end 234 | local r = { } 235 | if #str <= 0 then 236 | return r 237 | end 238 | local plain = false 239 | nmax = nmax or -1 240 | local nf = 1 241 | local ns = 1 242 | local nfr, nl = find(str, sep, ns, plain) 243 | while nfr and nmax ~= 0 do 244 | r[nf] = sub(str, ns, nfr - 1) 245 | nf = nf + 1 246 | ns = nl + 1 247 | nmax = nmax - 1 248 | nfr, nl = find(str, sep, ns, plain) 249 | end 250 | r[nf] = sub(str, ns) 251 | return r 252 | end 253 | 254 | -- create an error table to throw 255 | function throwError (code, msg) 256 | return { 257 | status = code, 258 | msg = msg or http.STATUS_CODES[code] 259 | } 260 | end 261 | 262 | return { 263 | merge = merge, 264 | tprint = tprint, 265 | filter = filter, 266 | split = split, 267 | split2 = split2, 268 | indexOf = indexOf, 269 | lastIndexOf = lastIndexOf, 270 | mime = mime, 271 | throwError = throwError, 272 | roundToDecimals = roundToDecimals, 273 | supportMethod = supportMethod, 274 | hasBody = hasBody, 275 | copy = copy, 276 | calcEtag = calcEtag, 277 | log = log, 278 | getTime = getTime 279 | } 280 | -------------------------------------------------------------------------------- /libs/mime.lua: -------------------------------------------------------------------------------- 1 | -- mimetypes.lua 2 | -- Version 1.0.0 3 | 4 | --[[ 5 | Copyright (c) 2011 Matthew "LeafStorm" Frazier 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following 14 | conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | ====== 29 | 30 | In addition, the MIME types contained in the Software were 31 | originally obtained from the Apache HTTP Server available under the 32 | Apache Software License, Version 2.0 license 33 | (http://directory.fsf.org/wiki/License:Apache2.0) 34 | ]] 35 | 36 | -- This table is the one that actually contains the exported functions. 37 | 38 | local mimetypes = {} 39 | 40 | mimetypes.version = '1.0.0' 41 | 42 | 43 | -- Extracts the extension from a filename and returns it. 44 | -- The extension must be at the end of the string, and preceded by a dot and 45 | -- at least one other character. Only the last part will be returned (so 46 | -- "package-1.2.tar.gz" will return "gz"). 47 | -- If there is no extension, this function will return nil. 48 | 49 | local function extension (filename) 50 | return filename:match(".+%.([%a%d]+)$") 51 | end 52 | 53 | 54 | -- Creates a deep copy of the given table. 55 | 56 | local function copy (tbl) 57 | local ntbl = {} 58 | for key, value in pairs(tbl) do 59 | if type(value) == 'table' then 60 | ntbl[key] = copy(value) 61 | else 62 | ntbl[key] = value 63 | end 64 | end 65 | return ntbl 66 | end 67 | 68 | 69 | -- This is the default MIME type database. 70 | -- It is a table with two members - "extensions" and "filenames". 71 | -- The filenames table maps complete file names (like README) to MIME types. 72 | -- The extensions just maps the files' extensions (like jpg) to types. 73 | 74 | local defaultdb = { 75 | -- The MIME types. Remember to not include the dot on the extension. 76 | extensions = { 77 | ['mods'] = 'application/mods+xml', 78 | ['hps'] = 'application/vnd.hp-hps', 79 | ['sfv'] = 'text/x-sfv', 80 | ['pcl'] = 'application/vnd.hp-pcl', 81 | ['oth'] = 'application/vnd.oasis.opendocument.text-web', 82 | ['arc'] = 'application/x-freearc', 83 | ['txd'] = 'application/vnd.genomatix.tuxedo', 84 | ['tcl'] = 'application/x-tcl', 85 | ['apr'] = 'application/vnd.lotus-approach', 86 | ['viv'] = 'video/vnd.vivo', 87 | ['wmlsc'] = 'application/vnd.wap.wmlscriptc', 88 | ['inkml'] = 'application/inkml+xml', 89 | ['sdc'] = 'application/vnd.stardivision.calc', 90 | ['dll'] = 'application/x-msdownload', 91 | ['sdkd'] = 'application/vnd.solent.sdkm+xml', 92 | ['kpr'] = 'application/vnd.kde.kpresenter', 93 | ['uvd'] = 'application/vnd.dece.data', 94 | ['gtw'] = 'model/vnd.gtw', 95 | ['svd'] = 'application/vnd.svd', 96 | ['m14'] = 'application/x-msmediaview', 97 | ['spp'] = 'application/scvp-vp-response', 98 | ['qt'] = 'video/quicktime', 99 | ['c4g'] = 'application/vnd.clonk.c4group', 100 | ['dp'] = 'application/vnd.osgi.dp', 101 | ['oda'] = 'application/oda', 102 | ['log'] = 'text/plain', 103 | ['gre'] = 'application/vnd.geometry-explorer', 104 | ['sda'] = 'application/vnd.stardivision.draw', 105 | ['rms'] = 'application/vnd.jcp.javame.midlet-rms', 106 | ['ico'] = 'image/x-icon', 107 | ['cab'] = 'application/vnd.ms-cab-compressed', 108 | ['p7c'] = 'application/pkcs7-mime', 109 | ['cb7'] = 'application/x-cbr', 110 | ['src'] = 'application/x-wais-source', 111 | ['uvf'] = 'application/vnd.dece.data', 112 | ['dms'] = 'application/octet-stream', 113 | ['ccxml'] = 'application/ccxml+xml', 114 | ['uvvd'] = 'application/vnd.dece.data', 115 | ['abw'] = 'application/x-abiword', 116 | ['gex'] = 'application/vnd.geometry-explorer', 117 | ['es3'] = 'application/vnd.eszigno3+xml', 118 | ['mmr'] = 'image/vnd.fujixerox.edmics-mmr', 119 | ['wgt'] = 'application/widget', 120 | ['mp4a'] = 'audio/mp4', 121 | ['gram'] = 'application/srgs', 122 | ['p7m'] = 'application/pkcs7-mime', 123 | ['org'] = 'application/vnd.lotus-organizer', 124 | ['silo'] = 'model/mesh', 125 | ['scq'] = 'application/scvp-cv-request', 126 | ['cxx'] = 'text/x-c', 127 | ['ots'] = 'application/vnd.oasis.opendocument.spreadsheet-template', 128 | ['fhc'] = 'image/x-freehand', 129 | ['uvvf'] = 'application/vnd.dece.data', 130 | ['jpeg'] = 'image/jpeg', 131 | ['ma'] = 'application/mathematica', 132 | ['odm'] = 'application/vnd.oasis.opendocument.text-master', 133 | ['uvvh'] = 'video/vnd.dece.hd', 134 | ['bed'] = 'application/vnd.realvnc.bed', 135 | ['doc'] = 'application/msword', 136 | ['ice'] = 'x-conference/x-cooltalk', 137 | ['fg5'] = 'application/vnd.fujitsu.oasysgp', 138 | ['ustar'] = 'application/x-ustar', 139 | ['mesh'] = 'model/mesh', 140 | ['smv'] = 'video/x-smv', 141 | ['imp'] = 'application/vnd.accpac.simply.imp', 142 | ['movie'] = 'video/x-sgi-movie', 143 | ['cmp'] = 'application/vnd.yellowriver-custom-menu', 144 | ['avi'] = 'video/x-msvideo', 145 | ['cpt'] = 'application/mac-compactpro', 146 | ['lha'] = 'application/x-lzh-compressed', 147 | ['cmdf'] = 'chemical/x-cmdf', 148 | ['wvx'] = 'video/x-ms-wvx', 149 | ['prf'] = 'application/pics-rules', 150 | ['wmx'] = 'video/x-ms-wmx', 151 | ['wmv'] = 'video/x-ms-wmv', 152 | ['ggt'] = 'application/vnd.geogebra.tool', 153 | ['kpt'] = 'application/vnd.kde.kpresenter', 154 | ['kmz'] = 'application/vnd.google-earth.kmz', 155 | ['mpt'] = 'application/vnd.ms-project', 156 | ['caf'] = 'audio/x-caf', 157 | ['mp4'] = 'video/mp4', 158 | ['dcr'] = 'application/x-director', 159 | ['wm'] = 'video/x-ms-wm', 160 | ['ai'] = 'application/postscript', 161 | ['lrf'] = 'application/octet-stream', 162 | ['qbo'] = 'application/vnd.intu.qbo', 163 | ['vob'] = 'video/x-ms-vob', 164 | ['nml'] = 'application/vnd.enliven', 165 | ['xlf'] = 'application/x-xliff+xml', 166 | ['dcurl'] = 'text/vnd.curl.dcurl', 167 | ['hpid'] = 'application/vnd.hp-hpid', 168 | ['kwd'] = 'application/vnd.kde.kword', 169 | ['asx'] = 'video/x-ms-asf', 170 | ['asf'] = 'video/x-ms-asf', 171 | ['acu'] = 'application/vnd.acucobol', 172 | ['fm'] = 'application/vnd.framemaker', 173 | ['plf'] = 'application/vnd.pocketlearn', 174 | ['mng'] = 'video/x-mng', 175 | ['gramps'] = 'application/x-gramps-xml', 176 | ['mks'] = 'video/x-matroska', 177 | ['vcard'] = 'text/vcard', 178 | ['sgml'] = 'text/sgml', 179 | ['mvb'] = 'application/x-msmediaview', 180 | ['rp9'] = 'application/vnd.cloanto.rp9', 181 | ['mkv'] = 'video/x-matroska', 182 | ['xhtml'] = 'application/xhtml+xml', 183 | ['dssc'] = 'application/dssc+der', 184 | ['m4v'] = 'video/x-m4v', 185 | ['xml'] = 'application/xml', 186 | ['fli'] = 'video/x-fli', 187 | ['mpm'] = 'application/vnd.blueice.multipass', 188 | ['pskcxml'] = 'application/pskc+xml', 189 | ['webm'] = 'video/webm', 190 | ['pml'] = 'application/vnd.ctc-posml', 191 | ['cu'] = 'application/cu-seeme', 192 | ['rm'] = 'application/vnd.rn-realmedia', 193 | ['spx'] = 'audio/ogg', 194 | ['dic'] = 'text/x-c', 195 | ['esa'] = 'application/vnd.osgi.subsystem', 196 | ['odi'] = 'application/vnd.oasis.opendocument.image', 197 | ['irm'] = 'application/vnd.ibm.rights-management', 198 | ['m4u'] = 'video/vnd.mpegurl', 199 | ['cdmid'] = 'application/cdmi-domain', 200 | ['elc'] = 'application/octet-stream', 201 | ['mxu'] = 'video/vnd.mpegurl', 202 | ['mdi'] = 'image/vnd.ms-modi', 203 | ['rld'] = 'application/resource-lists-diff+xml', 204 | ['sdw'] = 'application/vnd.stardivision.writer', 205 | ['fvt'] = 'video/vnd.fvt', 206 | ['pptx'] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 207 | ['dvb'] = 'video/vnd.dvb.file', 208 | ['book'] = 'application/vnd.framemaker', 209 | ['pfx'] = 'application/x-pkcs12', 210 | ['qwd'] = 'application/vnd.quark.quarkxpress', 211 | ['nbp'] = 'application/vnd.wolfram.player', 212 | ['vcx'] = 'application/vnd.vcx', 213 | ['gpx'] = 'application/gpx+xml', 214 | ['uvvs'] = 'video/vnd.dece.sd', 215 | ['shf'] = 'application/shf+xml', 216 | ['n-gage'] = 'application/vnd.nokia.n-gage.symbian.install', 217 | ['hvd'] = 'application/vnd.yamaha.hv-dic', 218 | ['lostxml'] = 'application/lost+xml', 219 | ['knp'] = 'application/vnd.kinar', 220 | ['dvi'] = 'application/x-dvi', 221 | ['uvvp'] = 'video/vnd.dece.pd', 222 | ['psb'] = 'application/vnd.3gpp.pic-bw-small', 223 | ['asc'] = 'application/pgp-signature', 224 | ['wsdl'] = 'application/wsdl+xml', 225 | ['stk'] = 'application/hyperstudio', 226 | ['x3dbz'] = 'model/x3d+binary', 227 | ['uvvm'] = 'video/vnd.dece.mobile', 228 | ['3gp'] = 'video/3gpp', 229 | ['uvm'] = 'video/vnd.dece.mobile', 230 | ['sfd-hdstx'] = 'application/vnd.hydrostatix.sof-data', 231 | ['mpeg'] = 'video/mpeg', 232 | ['uvh'] = 'video/vnd.dece.hd', 233 | ['mov'] = 'video/quicktime', 234 | ['cla'] = 'application/vnd.claymore', 235 | ['ogv'] = 'video/ogg', 236 | ['m2v'] = 'video/mpeg', 237 | ['sse'] = 'application/vnd.kodak-descriptor', 238 | ['atomsvc'] = 'application/atomsvc+xml', 239 | ['djv'] = 'image/vnd.djvu', 240 | ['fe_launch'] = 'application/vnd.denovo.fcselayout-link', 241 | ['mpe'] = 'video/mpeg', 242 | ['x3dz'] = 'model/x3d+xml', 243 | ['et3'] = 'application/vnd.eszigno3+xml', 244 | ['u32'] = 'application/x-authorware-bin', 245 | ['mpg'] = 'video/mpeg', 246 | ['mpg4'] = 'video/mp4', 247 | ['mp4v'] = 'video/mp4', 248 | ['nsf'] = 'application/vnd.lotus-notes', 249 | ['dwg'] = 'image/vnd.dwg', 250 | ['teicorpus'] = 'application/tei+xml', 251 | ['sus'] = 'application/vnd.sus-calendar', 252 | ['uvvz'] = 'application/vnd.dece.zip', 253 | ['uvvx'] = 'application/vnd.dece.unspecified', 254 | ['psd'] = 'image/vnd.adobe.photoshop', 255 | ['ami'] = 'application/vnd.amiga.ami', 256 | ['nb'] = 'application/mathematica', 257 | ['nzb'] = 'application/x-nzb', 258 | ['tsd'] = 'application/timestamped-data', 259 | ['f'] = 'text/x-fortran', 260 | ['c11amc'] = 'application/vnd.cluetrust.cartomobile-config', 261 | ['torrent'] = 'application/x-bittorrent', 262 | ['cdkey'] = 'application/vnd.mediastation.cdkey', 263 | ['mj2'] = 'video/mj2', 264 | ['nns'] = 'application/vnd.noblenet-sealer', 265 | ['jpgm'] = 'video/jpm', 266 | ['jpm'] = 'video/jpm', 267 | ['jpgv'] = 'video/jpeg', 268 | ['h264'] = 'video/h264', 269 | ['mus'] = 'application/vnd.musician', 270 | ['ppsm'] = 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', 271 | ['appcache'] = 'text/cache-manifest', 272 | ['rpss'] = 'application/vnd.nokia.radio-presets', 273 | ['smi'] = 'application/smil+xml', 274 | ['h263'] = 'video/h263', 275 | ['h261'] = 'video/h261', 276 | ['3g2'] = 'video/3gpp2', 277 | ['rtx'] = 'text/richtext', 278 | ['vcf'] = 'text/x-vcard', 279 | ['texi'] = 'application/x-texinfo', 280 | ['cdbcmsg'] = 'application/vnd.contact.cmsg', 281 | ['uu'] = 'text/x-uuencode', 282 | ['nsc'] = 'application/x-conference', 283 | ['pic'] = 'image/x-pict', 284 | ['cil'] = 'application/vnd.ms-artgalry', 285 | ['t'] = 'text/troff', 286 | ['psf'] = 'application/x-font-linux-psf', 287 | ['p'] = 'text/x-pascal', 288 | ['vsf'] = 'application/vnd.vsf', 289 | ['odc'] = 'application/vnd.oasis.opendocument.chart', 290 | ['lbe'] = 'application/vnd.llamagraphics.life-balance.exchange+xml', 291 | ['java'] = 'text/x-java-source', 292 | ['tfm'] = 'application/x-tex-tfm', 293 | ['cpio'] = 'application/x-cpio', 294 | ['gnumeric'] = 'application/x-gnumeric', 295 | ['for'] = 'text/x-fortran', 296 | ['uvu'] = 'video/vnd.uvvu.mp4', 297 | ['qxt'] = 'application/vnd.quark.quarkxpress', 298 | ['fxpl'] = 'application/vnd.adobe.fxp', 299 | ['hh'] = 'text/x-c', 300 | ['uvi'] = 'image/vnd.dece.graphic', 301 | ['cpp'] = 'text/x-c', 302 | ['x3db'] = 'model/x3d+binary', 303 | ['obd'] = 'application/x-msbinder', 304 | ['cc'] = 'text/x-c', 305 | ['rtf'] = 'application/rtf', 306 | ['ddd'] = 'application/vnd.fujixerox.ddd', 307 | ['ttf'] = 'application/x-font-ttf', 308 | ['iif'] = 'application/vnd.shana.informed.interchange', 309 | ['tao'] = 'application/vnd.tao.intent-module-archive', 310 | ['potm'] = 'application/vnd.ms-powerpoint.template.macroenabled.12', 311 | ['oxt'] = 'application/vnd.openofficeorg.extension', 312 | ['mif'] = 'application/vnd.mif', 313 | ['mk3d'] = 'video/x-matroska', 314 | ['mrc'] = 'application/marc', 315 | ['cxt'] = 'application/x-director', 316 | ['thmx'] = 'application/vnd.ms-officetheme', 317 | ['ext'] = 'application/vnd.novadigm.ext', 318 | ['tr'] = 'text/troff', 319 | ['gxt'] = 'application/vnd.geonext', 320 | ['rcprofile'] = 'application/vnd.ipunplugged.rcprofile', 321 | ['xlsx'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 322 | ['wmls'] = 'text/vnd.wap.wmlscript', 323 | ['wml'] = 'text/vnd.wap.wml', 324 | ['dpg'] = 'application/vnd.dpgraph', 325 | ['wri'] = 'application/x-mswrite', 326 | ['cif'] = 'chemical/x-cif', 327 | ['wks'] = 'application/vnd.ms-works', 328 | ['ods'] = 'application/vnd.oasis.opendocument.spreadsheet', 329 | ['lasxml'] = 'application/vnd.las.las+xml', 330 | ['jpg'] = 'image/jpeg', 331 | ['ltf'] = 'application/vnd.frogans.ltf', 332 | ['spot'] = 'text/vnd.in3d.spot', 333 | ['3dml'] = 'text/vnd.in3d.3dml', 334 | ['curl'] = 'text/vnd.curl', 335 | ['xdp'] = 'application/vnd.adobe.xdp+xml', 336 | ['flx'] = 'text/vnd.fmi.flexstor', 337 | ['fly'] = 'text/vnd.fly', 338 | ['uvva'] = 'audio/vnd.dece.audio', 339 | ['p7r'] = 'application/x-pkcs7-certreqresp', 340 | ['cryptonote'] = 'application/vnd.rig.cryptonote', 341 | ['cdmio'] = 'application/cdmi-object', 342 | ['igx'] = 'application/vnd.micrografx.igx', 343 | ['smzip'] = 'application/vnd.stepmania.package', 344 | ['ogx'] = 'application/ogg', 345 | ['scs'] = 'application/scvp-cv-response', 346 | ['gv'] = 'text/vnd.graphviz', 347 | ['urls'] = 'text/uri-list', 348 | ['otc'] = 'application/vnd.oasis.opendocument.chart-template', 349 | ['png'] = 'image/png', 350 | ['uri'] = 'text/uri-list', 351 | ['ttl'] = 'text/turtle', 352 | ['metalink'] = 'application/metalink+xml', 353 | ['cmc'] = 'application/vnd.cosmocaller', 354 | ['ahead'] = 'application/vnd.ahead.space', 355 | ['taglet'] = 'application/vnd.mynfc', 356 | ['list'] = 'text/plain', 357 | ['dae'] = 'model/vnd.collada+xml', 358 | ['me'] = 'text/troff', 359 | ['srt'] = 'application/x-subrip', 360 | ['fgd'] = 'application/x-director', 361 | ['man'] = 'text/troff', 362 | ['jpe'] = 'image/jpeg', 363 | ['clkw'] = 'application/vnd.crick.clicker.wordbank', 364 | ['mfm'] = 'application/vnd.mfmp', 365 | ['pfb'] = 'application/x-font-type1', 366 | ['roff'] = 'text/troff', 367 | ['dtd'] = 'application/xml-dtd', 368 | ['ifm'] = 'application/vnd.shana.informed.formdata', 369 | ['pas'] = 'text/x-pascal', 370 | ['bz'] = 'application/x-bzip', 371 | ['ras'] = 'image/x-cmu-raster', 372 | ['car'] = 'application/vnd.curl.car', 373 | ['list3820'] = 'application/vnd.ibm.modcap', 374 | ['mgz'] = 'application/vnd.proteus.magazine', 375 | ['crt'] = 'application/x-x509-ca-cert', 376 | ['sbml'] = 'application/sbml+xml', 377 | ['pcurl'] = 'application/vnd.curl.pcurl', 378 | ['dbk'] = 'application/docbook+xml', 379 | ['sgm'] = 'text/sgml', 380 | ['ac'] = 'application/pkix-attr-cert', 381 | ['opf'] = 'application/oebps-package+xml', 382 | ['mar'] = 'application/octet-stream', 383 | ['asm'] = 'text/x-asm', 384 | ['wav'] = 'audio/x-wav', 385 | ['x3dvz'] = 'model/x3d+vrml', 386 | ['bpk'] = 'application/octet-stream', 387 | ['dtb'] = 'application/x-dtbook+xml', 388 | ['dsc'] = 'text/prs.lines.tag', 389 | ['wpd'] = 'application/vnd.wordperfect', 390 | ['xsl'] = 'application/xml', 391 | ['in'] = 'text/plain', 392 | ['stc'] = 'application/vnd.sun.xml.calc.template', 393 | ['musicxml'] = 'application/vnd.recordare.musicxml+xml', 394 | ['sc'] = 'application/vnd.ibm.secure-container', 395 | ['def'] = 'text/plain', 396 | ['uvs'] = 'video/vnd.dece.sd', 397 | ['wma'] = 'audio/x-ms-wma', 398 | ['text'] = 'text/plain', 399 | ['obj'] = 'application/x-tgif', 400 | ['cap'] = 'application/vnd.tcpdump.pcap', 401 | ['lvp'] = 'audio/vnd.lucent.voice', 402 | ['kfo'] = 'application/vnd.kde.kformula', 403 | ['sitx'] = 'application/x-stuffitx', 404 | ['jnlp'] = 'application/x-java-jnlp-file', 405 | ['rmvb'] = 'application/vnd.rn-realmedia-vbr', 406 | ['blb'] = 'application/x-blorb', 407 | ['evy'] = 'application/x-envoy', 408 | ['htm'] = 'text/html', 409 | ['html'] = 'text/html', 410 | ['sv4cpio'] = 'application/x-sv4cpio', 411 | ['mpkg'] = 'application/vnd.apple.installer+xml', 412 | ['xer'] = 'application/patch-ops-error+xml', 413 | ['cdmic'] = 'application/cdmi-container', 414 | ['csv'] = 'text/csv', 415 | ['aso'] = 'application/vnd.accpac.simply.aso', 416 | ['css'] = 'text/css', 417 | ['pki'] = 'application/pkixcmp', 418 | ['sdkm'] = 'application/vnd.solent.sdkm+xml', 419 | ['ifb'] = 'text/calendar', 420 | ['mcd'] = 'application/vnd.mcd', 421 | ['nnd'] = 'application/vnd.noblenet-directory', 422 | ['pbm'] = 'image/x-portable-bitmap', 423 | ['srx'] = 'application/sparql-results+xml', 424 | ['cdmiq'] = 'application/cdmi-queue', 425 | ['qxl'] = 'application/vnd.quark.quarkxpress', 426 | ['ics'] = 'text/calendar', 427 | ['icm'] = 'application/vnd.iccprofile', 428 | ['x3dv'] = 'model/x3d+vrml', 429 | ['vrml'] = 'model/vrml', 430 | ['xbm'] = 'image/x-xbitmap', 431 | ['spl'] = 'application/x-futuresplash', 432 | ['gtm'] = 'application/vnd.groove-tool-message', 433 | ['paw'] = 'application/vnd.pawaafile', 434 | ['csp'] = 'application/vnd.commonspace', 435 | ['jlt'] = 'application/vnd.hp-jlyt', 436 | ['rip'] = 'audio/vnd.rip', 437 | ['mxl'] = 'application/vnd.recordare.musicxml', 438 | ['rep'] = 'application/vnd.businessobjects', 439 | ['mts'] = 'model/vnd.mts', 440 | ['mads'] = 'application/mads+xml', 441 | ['gdl'] = 'model/vnd.gdl', 442 | ['ksp'] = 'application/vnd.kde.kspread', 443 | ['dwf'] = 'model/vnd.dwf', 444 | ['ms'] = 'text/troff', 445 | ['udeb'] = 'application/x-debian-package', 446 | ['iges'] = 'model/iges', 447 | ['fcdt'] = 'application/vnd.adobe.formscentral.fcdt', 448 | ['xo'] = 'application/vnd.olpc-sugar', 449 | ['xlt'] = 'application/vnd.ms-excel', 450 | ['gtar'] = 'application/x-gtar', 451 | ['p12'] = 'application/x-pkcs12', 452 | ['gqf'] = 'application/vnd.grafeq', 453 | ['wbmp'] = 'image/vnd.wap.wbmp', 454 | ['mime'] = 'message/rfc822', 455 | ['sv4crc'] = 'application/x-sv4crc', 456 | ['xwd'] = 'image/x-xwindowdump', 457 | ['qxb'] = 'application/vnd.quark.quarkxpress', 458 | ['xpm'] = 'image/x-xpixmap', 459 | ['tga'] = 'image/x-tga', 460 | ['seed'] = 'application/vnd.fdsn.seed', 461 | ['rss'] = 'application/rss+xml', 462 | ['ppm'] = 'image/x-portable-pixmap', 463 | ['fsc'] = 'application/vnd.fsc.weblaunch', 464 | ['cat'] = 'application/vnd.ms-pki.seccat', 465 | ['mwf'] = 'application/vnd.mfer', 466 | ['pnm'] = 'image/x-portable-anymap', 467 | ['pct'] = 'image/x-pict', 468 | ['pcx'] = 'image/x-pcx', 469 | ['gph'] = 'application/vnd.flographit', 470 | ['sid'] = 'image/x-mrsid-image', 471 | ['class'] = 'application/java-vm', 472 | ['c4p'] = 'application/vnd.clonk.c4group', 473 | ['mp3'] = 'audio/mpeg', 474 | ['uvvi'] = 'image/vnd.dece.graphic', 475 | ['fh4'] = 'image/x-freehand', 476 | ['rgb'] = 'image/x-rgb', 477 | ['vcg'] = 'application/vnd.groove-vcard', 478 | ['cmx'] = 'image/x-cmx', 479 | ['3ds'] = 'image/x-3ds', 480 | ['cdy'] = 'application/vnd.cinderella', 481 | ['webp'] = 'image/webp', 482 | ['xif'] = 'image/vnd.xiff', 483 | ['p10'] = 'application/pkcs10', 484 | ['wax'] = 'audio/x-ms-wax', 485 | ['npx'] = 'image/vnd.net-fpx', 486 | ['ufdl'] = 'application/vnd.ufdl', 487 | ['rlc'] = 'image/vnd.fujixerox.edmics-rlc', 488 | ['fst'] = 'image/vnd.fst', 489 | ['setreg'] = 'application/set-registration-initiation', 490 | ['mbox'] = 'application/mbox', 491 | ['kpxx'] = 'application/vnd.ds-keypoint', 492 | ['fbs'] = 'image/vnd.fastbidsheet', 493 | ['z1'] = 'application/x-zmachine', 494 | ['weba'] = 'audio/webm', 495 | ['clkt'] = 'application/vnd.crick.clicker.template', 496 | ['m13'] = 'application/x-msmediaview', 497 | ['cww'] = 'application/prs.cww', 498 | ['sxd'] = 'application/vnd.sun.xml.draw', 499 | ['xpw'] = 'application/vnd.intercon.formnet', 500 | ['dd2'] = 'application/vnd.oma.dd2+xml', 501 | ['odf'] = 'application/vnd.oasis.opendocument.formula', 502 | ['fzs'] = 'application/vnd.fuzzysheet', 503 | ['portpkg'] = 'application/vnd.macports.portpkg', 504 | ['oti'] = 'application/vnd.oasis.opendocument.image-template', 505 | ['hlp'] = 'application/winhlp', 506 | ['cst'] = 'application/x-director', 507 | ['mpn'] = 'application/vnd.mophun.application', 508 | ['xvm'] = 'application/xv+xml', 509 | ['cgm'] = 'image/cgm', 510 | ['fh5'] = 'image/x-freehand', 511 | ['h'] = 'text/x-c', 512 | ['xar'] = 'application/vnd.xara', 513 | ['vis'] = 'application/vnd.visionary', 514 | ['wqd'] = 'application/vnd.wqd', 515 | ['cbr'] = 'application/x-cbr', 516 | ['svgz'] = 'image/svg+xml', 517 | ['wpl'] = 'application/vnd.ms-wpl', 518 | ['lwp'] = 'application/vnd.lotus-wordpro', 519 | ['igm'] = 'application/vnd.insors.igm', 520 | ['flo'] = 'application/vnd.micrografx.flo', 521 | ['zirz'] = 'application/vnd.zul', 522 | ['svg'] = 'image/svg+xml', 523 | ['m3u8'] = 'application/vnd.apple.mpegurl', 524 | ['dra'] = 'audio/vnd.dra', 525 | ['oprc'] = 'application/vnd.palm', 526 | ['vxml'] = 'application/voicexml+xml', 527 | ['dis'] = 'application/vnd.mobius.dis', 528 | ['qps'] = 'application/vnd.publishare-delta-tree', 529 | ['vsw'] = 'application/vnd.visio', 530 | ['ntf'] = 'application/vnd.nitf', 531 | ['btif'] = 'image/prs.btif', 532 | ['uris'] = 'text/uri-list', 533 | ['mxf'] = 'application/mxf', 534 | ['wps'] = 'application/vnd.ms-works', 535 | ['aas'] = 'application/x-authorware-seg', 536 | ['c4d'] = 'application/vnd.clonk.c4group', 537 | ['zip'] = 'application/zip', 538 | ['gif'] = 'image/gif', 539 | ['susp'] = 'application/vnd.sus-calendar', 540 | ['air'] = 'application/vnd.adobe.air-application-installer-package+zip', 541 | ['ris'] = 'application/x-research-info-systems', 542 | ['pgn'] = 'application/x-chess-pgn', 543 | ['gxf'] = 'application/gxf', 544 | ['g3'] = 'image/g3fax', 545 | ['sdd'] = 'application/vnd.stardivision.impress', 546 | ['bmp'] = 'image/bmp', 547 | ['otg'] = 'application/vnd.oasis.opendocument.graphics-template', 548 | ['eps'] = 'application/postscript', 549 | ['xyz'] = 'chemical/x-xyz', 550 | ['sema'] = 'application/vnd.sema', 551 | ['csml'] = 'chemical/x-csml', 552 | ['swf'] = 'application/x-shockwave-flash', 553 | ['qxd'] = 'application/vnd.quark.quarkxpress', 554 | ['cdx'] = 'chemical/x-cdx', 555 | ['123'] = 'application/vnd.lotus-1-2-3', 556 | ['dotx'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 557 | ['xm'] = 'audio/xm', 558 | ['spq'] = 'application/scvp-vp-request', 559 | ['jisp'] = 'application/vnd.jisp', 560 | ['rmp'] = 'audio/x-pn-realaudio-plugin', 561 | ['tex'] = 'application/x-tex', 562 | ['xlm'] = 'application/vnd.ms-excel', 563 | ['ppam'] = 'application/vnd.ms-powerpoint.addin.macroenabled.12', 564 | ['xht'] = 'application/xhtml+xml', 565 | ['odb'] = 'application/vnd.oasis.opendocument.database', 566 | ['ram'] = 'audio/x-pn-realaudio', 567 | ['xfdl'] = 'application/vnd.xfdl', 568 | ['mka'] = 'audio/x-matroska', 569 | ['flac'] = 'audio/x-flac', 570 | ['aifc'] = 'audio/x-aiff', 571 | ['sfs'] = 'application/vnd.spotfire.sfs', 572 | ['rl'] = 'application/resource-lists+xml', 573 | ['wdb'] = 'application/vnd.ms-works', 574 | ['tra'] = 'application/vnd.trueapp', 575 | ['dfac'] = 'application/vnd.dreamfactory', 576 | ['aif'] = 'audio/x-aiff', 577 | ['mp2'] = 'audio/mpeg', 578 | ['vtu'] = 'model/vnd.vtu', 579 | ['mny'] = 'application/x-msmoney', 580 | ['ecelp7470'] = 'audio/vnd.nuera.ecelp7470', 581 | ['ppsx'] = 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 582 | ['kon'] = 'application/vnd.kde.kontour', 583 | ['odp'] = 'application/vnd.oasis.opendocument.presentation', 584 | ['twds'] = 'application/vnd.simtech-mindmapper', 585 | ['dtshd'] = 'audio/vnd.dts.hd', 586 | ['dts'] = 'audio/vnd.dts', 587 | ['sit'] = 'application/x-stuffit', 588 | ['ipfix'] = 'application/ipfix', 589 | ['sgi'] = 'image/sgi', 590 | ['application'] = 'application/x-ms-application', 591 | ['roa'] = 'application/rpki-roa', 592 | ['xaml'] = 'application/xaml+xml', 593 | ['sil'] = 'audio/silk', 594 | ['smil'] = 'application/smil+xml', 595 | ['s3m'] = 'audio/s3m', 596 | ['ogg'] = 'audio/ogg', 597 | ['chm'] = 'application/vnd.ms-htmlhelp', 598 | ['mscml'] = 'application/mediaservercontrol+xml', 599 | ['atom'] = 'application/atom+xml', 600 | ['eva'] = 'application/x-eva', 601 | ['oga'] = 'audio/ogg', 602 | ['prc'] = 'application/x-mobipocket-ebook', 603 | ['sldm'] = 'application/vnd.ms-powerpoint.slide.macroenabled.12', 604 | ['m2a'] = 'audio/mpeg', 605 | ['frame'] = 'application/vnd.framemaker', 606 | ['fh7'] = 'image/x-freehand', 607 | ['mp2a'] = 'audio/mpeg', 608 | ['aac'] = 'audio/x-aac', 609 | ['mpga'] = 'audio/mpeg', 610 | ['rmi'] = 'audio/midi', 611 | ['m21'] = 'application/mp21', 612 | ['kar'] = 'audio/midi', 613 | ['p7b'] = 'application/x-pkcs7-certificates', 614 | ['gac'] = 'application/vnd.groove-account', 615 | ['midi'] = 'audio/midi', 616 | ['mid'] = 'audio/midi', 617 | ['ott'] = 'application/vnd.oasis.opendocument.text-template', 618 | ['rif'] = 'application/reginfo+xml', 619 | ['cfs'] = 'application/x-cfs-compressed', 620 | ['au'] = 'audio/basic', 621 | ['adp'] = 'audio/adpcm', 622 | ['pfr'] = 'application/font-tdpfr', 623 | ['ief'] = 'image/ief', 624 | ['xul'] = 'application/vnd.mozilla.xul+xml', 625 | ['dart'] = 'application/vnd.dart', 626 | ['yin'] = 'application/yin+xml', 627 | ['pya'] = 'audio/vnd.ms-playready.media.pya', 628 | ['uvg'] = 'image/vnd.dece.graphic', 629 | ['str'] = 'application/vnd.pg.format', 630 | ['tiff'] = 'image/tiff', 631 | ['crd'] = 'application/x-mscardfile', 632 | ['c4f'] = 'application/vnd.clonk.c4group', 633 | ['xhvml'] = 'application/xv+xml', 634 | ['ssdl'] = 'application/ssdl+xml', 635 | ['mxml'] = 'application/xv+xml', 636 | ['g2w'] = 'application/vnd.geoplan', 637 | ['xslt'] = 'application/xslt+xml', 638 | ['nnw'] = 'application/vnd.noblenet-web', 639 | ['xop'] = 'application/xop+xml', 640 | ['flv'] = 'video/x-flv', 641 | ['ra'] = 'audio/x-pn-realaudio', 642 | ['tpt'] = 'application/vnd.trid.tpt', 643 | ['bdm'] = 'application/vnd.syncml.dm+wbxml', 644 | ['davmount'] = 'application/davmount+xml', 645 | ['z6'] = 'application/x-zmachine', 646 | ['xenc'] = 'application/xenc+xml', 647 | ['yang'] = 'application/yang', 648 | ['uva'] = 'audio/vnd.dece.audio', 649 | ['ez2'] = 'application/vnd.ezpix-album', 650 | ['dmp'] = 'application/vnd.tcpdump.pcap', 651 | ['mets'] = 'application/mets+xml', 652 | ['z8'] = 'application/x-zmachine', 653 | ['xfdf'] = 'application/vnd.adobe.xfdf', 654 | ['fcs'] = 'application/vnd.isac.fcs', 655 | ['z7'] = 'application/x-zmachine', 656 | ['xdssc'] = 'application/dssc+xml', 657 | ['z5'] = 'application/x-zmachine', 658 | ['z4'] = 'application/x-zmachine', 659 | ['daf'] = 'application/vnd.mobius.daf', 660 | ['sql'] = 'application/x-sql', 661 | ['z3'] = 'application/x-zmachine', 662 | ['z2'] = 'application/x-zmachine', 663 | ['uvvt'] = 'application/vnd.dece.ttml+xml', 664 | ['xz'] = 'application/x-xz', 665 | ['xdm'] = 'application/vnd.syncml.dm+xml', 666 | ['xpi'] = 'application/x-xpinstall', 667 | ['fig'] = 'application/x-xfig', 668 | ['der'] = 'application/x-x509-ca-cert', 669 | ['chrt'] = 'application/vnd.kde.kchart', 670 | ['vcs'] = 'text/x-vcalendar', 671 | ['pgp'] = 'application/pgp-encrypted', 672 | ['onetmp'] = 'application/onenote', 673 | ['mjp2'] = 'video/mj2', 674 | ['pvb'] = 'application/vnd.3gpp.pic-bw-var', 675 | ['f90'] = 'text/x-fortran', 676 | ['tar'] = 'application/x-tar', 677 | ['hvp'] = 'application/vnd.yamaha.hv-voice', 678 | ['afm'] = 'application/x-font-type1', 679 | ['semd'] = 'application/vnd.semd', 680 | ['mp4s'] = 'application/mp4', 681 | ['eml'] = 'message/rfc822', 682 | ['otp'] = 'application/vnd.oasis.opendocument.presentation-template', 683 | ['gml'] = 'application/gml+xml', 684 | ['itp'] = 'application/vnd.shana.informed.formtemplate', 685 | ['st'] = 'application/vnd.sailingtracker.track', 686 | ['svc'] = 'application/vnd.dvb.service', 687 | ['cml'] = 'chemical/x-cml', 688 | ['pkipath'] = 'application/pkix-pkipath', 689 | ['sh'] = 'application/x-sh', 690 | ['spf'] = 'application/vnd.yamaha.smaf-phrase', 691 | ['mcurl'] = 'text/vnd.curl.mcurl', 692 | ['spc'] = 'application/x-pkcs7-certificates', 693 | ['rq'] = 'application/sparql-query', 694 | ['uvvv'] = 'video/vnd.dece.video', 695 | ['cdf'] = 'application/x-netcdf', 696 | ['atomcat'] = 'application/atomcat+xml', 697 | ['jad'] = 'text/vnd.sun.j2me.app-descriptor', 698 | ['gqs'] = 'application/vnd.grafeq', 699 | ['bin'] = 'application/octet-stream', 700 | ['m3u'] = 'audio/x-mpegurl', 701 | ['scd'] = 'application/x-msschedule', 702 | ['pub'] = 'application/x-mspublisher', 703 | ['sm'] = 'application/vnd.stepmania.stepchart', 704 | ['ecelp9600'] = 'audio/vnd.nuera.ecelp9600', 705 | ['mpy'] = 'application/vnd.ibm.minipay', 706 | ['xlam'] = 'application/vnd.ms-excel.addin.macroenabled.12', 707 | ['vox'] = 'application/x-authorware-bin', 708 | ['emf'] = 'application/x-msmetafile', 709 | ['mc1'] = 'application/vnd.medcalcdata', 710 | ['zmm'] = 'application/vnd.handheld-entertainment+xml', 711 | ['mxs'] = 'application/vnd.triscape.mxs', 712 | ['msi'] = 'application/x-msdownload', 713 | ['mseq'] = 'application/vnd.mseq', 714 | ['emma'] = 'application/emma+xml', 715 | ['bat'] = 'application/x-msdownload', 716 | ['com'] = 'application/x-msdownload', 717 | ['exe'] = 'application/x-msdownload', 718 | ['xpx'] = 'application/vnd.intercon.formnet', 719 | ['twd'] = 'application/vnd.simtech-mindmapper', 720 | ['clp'] = 'application/x-msclip', 721 | ['mdb'] = 'application/x-msaccess', 722 | ['xbap'] = 'application/x-ms-xbap', 723 | ['xlc'] = 'application/vnd.ms-excel', 724 | ['fpx'] = 'image/vnd.fpx', 725 | ['wmd'] = 'application/x-ms-wmd', 726 | ['lnk'] = 'application/x-ms-shortcut', 727 | ['lrm'] = 'application/vnd.ms-lrm', 728 | ['latex'] = 'application/x-latex', 729 | ['xdw'] = 'application/vnd.fujixerox.docuworks', 730 | ['dump'] = 'application/octet-stream', 731 | ['plc'] = 'application/vnd.mobius.plc', 732 | ['cdxml'] = 'application/vnd.chemdraw+xml', 733 | ['umj'] = 'application/vnd.umajin', 734 | ['osfpvg'] = 'application/vnd.yamaha.openscoreformat.osfpvg+xml', 735 | ['mobi'] = 'application/x-mobipocket-ebook', 736 | ['dxr'] = 'application/x-director', 737 | ['mie'] = 'application/x-mie', 738 | ['ei6'] = 'application/vnd.pg.osasli', 739 | ['lzh'] = 'application/x-lzh-compressed', 740 | ['dxp'] = 'application/vnd.spotfire.dxp', 741 | ['iso'] = 'application/x-iso9660-image', 742 | ['install'] = 'application/x-install-instructions', 743 | ['hdf'] = 'application/x-hdf', 744 | ['igs'] = 'model/iges', 745 | ['ims'] = 'application/vnd.ms-ims', 746 | ['dist'] = 'application/octet-stream', 747 | ['efif'] = 'application/vnd.picsel', 748 | ['wrl'] = 'model/vrml', 749 | ['esf'] = 'application/vnd.epson.esf', 750 | ['link66'] = 'application/vnd.route66.link66+xml', 751 | ['woff'] = 'application/font-woff', 752 | ['gim'] = 'application/vnd.groove-identity-message', 753 | ['t3'] = 'application/x-t3vm-image', 754 | ['mp21'] = 'application/mp21', 755 | ['xla'] = 'application/vnd.ms-excel', 756 | ['odft'] = 'application/vnd.oasis.opendocument.formula-template', 757 | ['msf'] = 'application/vnd.epson.msf', 758 | ['ktr'] = 'application/vnd.kahootz', 759 | ['box'] = 'application/vnd.previewsystems.box', 760 | ['crl'] = 'application/pkix-crl', 761 | ['skp'] = 'application/vnd.koan', 762 | ['ttc'] = 'application/x-font-ttf', 763 | ['osf'] = 'application/vnd.yamaha.openscoreformat', 764 | ['oa2'] = 'application/vnd.fujitsu.oasys2', 765 | ['pcf'] = 'application/x-font-pcf', 766 | ['fxp'] = 'application/vnd.adobe.fxp', 767 | ['trm'] = 'application/x-msterminal', 768 | ['oxps'] = 'application/oxps', 769 | ['gsf'] = 'application/x-font-ghostscript', 770 | ['swa'] = 'application/x-director', 771 | ['mb'] = 'application/mathematica', 772 | ['bdf'] = 'application/x-font-bdf', 773 | ['mmf'] = 'application/vnd.smaf', 774 | ['ufd'] = 'application/vnd.ufdl', 775 | ['n3'] = 'text/n3', 776 | ['rdz'] = 'application/vnd.data-vision.rdz', 777 | ['pot'] = 'application/vnd.ms-powerpoint', 778 | ['ncx'] = 'application/x-dtbncx+xml', 779 | ['wad'] = 'application/x-doom', 780 | ['w3d'] = 'application/x-director', 781 | ['mpc'] = 'application/vnd.mophun.certificate', 782 | ['ktz'] = 'application/vnd.kahootz', 783 | ['cct'] = 'application/x-director', 784 | ['dir'] = 'application/x-director', 785 | ['distz'] = 'application/octet-stream', 786 | ['rpst'] = 'application/vnd.nokia.radio-preset', 787 | ['csh'] = 'application/x-csh', 788 | ['skd'] = 'application/vnd.koan', 789 | ['jsonml'] = 'application/jsonml+json', 790 | ['sisx'] = 'application/vnd.symbian.install', 791 | ['boz'] = 'application/x-bzip2', 792 | ['deploy'] = 'application/octet-stream', 793 | ['msh'] = 'model/mesh', 794 | ['epub'] = 'application/epub+zip', 795 | ['smf'] = 'application/vnd.stardivision.math', 796 | ['dmg'] = 'application/x-apple-diskimage', 797 | ['deb'] = 'application/x-debian-package', 798 | ['f77'] = 'text/x-fortran', 799 | ['ssf'] = 'application/vnd.epson.ssf', 800 | ['etx'] = 'text/x-setext', 801 | ['chat'] = 'application/x-chat', 802 | ['shar'] = 'application/x-shar', 803 | ['wbxml'] = 'application/vnd.wap.wbxml', 804 | ['atx'] = 'application/vnd.antix.game-component', 805 | ['unityweb'] = 'application/vnd.unity', 806 | ['cbz'] = 'application/x-cbr', 807 | ['cbt'] = 'application/x-cbr', 808 | ['ait'] = 'application/vnd.dvb.ait', 809 | ['cba'] = 'application/x-cbr', 810 | ['bz2'] = 'application/x-bzip2', 811 | ['blorb'] = 'application/x-blorb', 812 | ['txt'] = 'text/plain', 813 | ['bcpio'] = 'application/x-bcpio', 814 | ['ktx'] = 'image/ktx', 815 | ['scm'] = 'application/vnd.lotus-screencam', 816 | ['lbd'] = 'application/vnd.llamagraphics.life-balance.desktop', 817 | ['mmd'] = 'application/vnd.chipnuts.karaoke-mmd', 818 | ['x32'] = 'application/x-authorware-bin', 819 | ['ecma'] = 'application/ecmascript', 820 | ['pbd'] = 'application/vnd.powerbuilder6', 821 | ['docx'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 822 | ['stf'] = 'application/vnd.wt.stf', 823 | ['hal'] = 'application/vnd.hal+xml', 824 | ['aab'] = 'application/x-authorware-bin', 825 | ['json'] = 'application/json', 826 | ['uvt'] = 'application/vnd.dece.ttml+xml', 827 | ['pfa'] = 'application/x-font-type1', 828 | ['xbd'] = 'application/vnd.fujixerox.docuworks.binder', 829 | ['7z'] = 'application/x-7z-compressed', 830 | ['wspolicy'] = 'application/wspolicy+xml', 831 | ['zaz'] = 'application/vnd.zzazz.deck+xml', 832 | ['aam'] = 'application/x-authorware-map', 833 | ['rar'] = 'application/x-rar-compressed', 834 | ['saf'] = 'application/vnd.yamaha.smaf-audio', 835 | ['exi'] = 'application/exi', 836 | ['azf'] = 'application/vnd.airzip.filesecure.azf', 837 | ['msty'] = 'application/vnd.muvee.style', 838 | ['gam'] = 'application/x-tads', 839 | ['grxml'] = 'application/srgs+xml', 840 | ['ghf'] = 'application/vnd.groove-help', 841 | ['conf'] = 'text/plain', 842 | ['otf'] = 'application/x-font-otf', 843 | ['hqx'] = 'application/mac-binhex40', 844 | ['ssml'] = 'application/ssml+xml', 845 | ['tif'] = 'image/tiff', 846 | ['xvml'] = 'application/xv+xml', 847 | ['pkg'] = 'application/octet-stream', 848 | ['qam'] = 'application/vnd.epson.quickanime', 849 | ['bmi'] = 'application/vnd.bmi', 850 | ['pcap'] = 'application/vnd.tcpdump.pcap', 851 | ['ftc'] = 'application/vnd.fluxtime.clip', 852 | ['wmlc'] = 'application/vnd.wap.wmlc', 853 | ['vss'] = 'application/vnd.visio', 854 | ['std'] = 'application/vnd.sun.xml.draw.template', 855 | ['vst'] = 'application/vnd.visio', 856 | ['vsd'] = 'application/vnd.visio', 857 | ['msl'] = 'application/vnd.mobius.msl', 858 | ['uvv'] = 'video/vnd.dece.video', 859 | ['uoml'] = 'application/vnd.uoml+xml', 860 | ['sig'] = 'application/pgp-signature', 861 | ['utz'] = 'application/vnd.uiq.theme', 862 | ['wdp'] = 'image/vnd.ms-photo', 863 | ['tmo'] = 'application/vnd.tmobile-livetv', 864 | ['mqy'] = 'application/vnd.mobius.mqy', 865 | ['skt'] = 'application/vnd.koan', 866 | ['sdp'] = 'application/sdp', 867 | ['vor'] = 'application/vnd.stardivision.writer', 868 | ['geo'] = 'application/vnd.dynageo', 869 | ['sru'] = 'application/sru+xml', 870 | ['tei'] = 'application/tei+xml', 871 | ['xap'] = 'application/x-silverlight-app', 872 | ['c'] = 'text/x-c', 873 | ['ez'] = 'application/andrew-inset', 874 | ['ink'] = 'application/inkml+xml', 875 | ['edm'] = 'application/vnd.novadigm.edm', 876 | ['gca'] = 'application/x-gca-compressed', 877 | ['cii'] = 'application/vnd.anser-web-certificate-issue-initiation', 878 | ['oas'] = 'application/vnd.fujitsu.oasys', 879 | ['onetoc'] = 'application/onenote', 880 | ['oa3'] = 'application/vnd.fujitsu.oasys3', 881 | ['pptm'] = 'application/vnd.ms-powerpoint.presentation.macroenabled.12', 882 | ['wtb'] = 'application/vnd.webturbo', 883 | ['xlw'] = 'application/vnd.ms-excel', 884 | ['xsm'] = 'application/vnd.syncml+xml', 885 | ['dataless'] = 'application/vnd.fdsn.seed', 886 | ['iota'] = 'application/vnd.astraea-software.iota', 887 | ['dotm'] = 'application/vnd.ms-word.template.macroenabled.12', 888 | ['odt'] = 'application/vnd.oasis.opendocument.text', 889 | ['sxi'] = 'application/vnd.sun.xml.impress', 890 | ['uvp'] = 'video/vnd.dece.pd', 891 | ['pfm'] = 'application/x-font-type1', 892 | ['gbr'] = 'application/rpki-ghostbusters', 893 | ['pyv'] = 'video/vnd.ms-playready.media.pyv', 894 | ['f4v'] = 'video/x-f4v', 895 | ['uvvg'] = 'image/vnd.dece.graphic', 896 | ['pdf'] = 'application/pdf', 897 | ['apk'] = 'application/vnd.android.package-archive', 898 | ['swi'] = 'application/vnd.aristanetworks.swi', 899 | ['nc'] = 'application/x-netcdf', 900 | ['mrcx'] = 'application/marcxml+xml', 901 | ['meta4'] = 'application/metalink4+xml', 902 | ['ulx'] = 'application/x-glulx', 903 | ['rs'] = 'application/rls-services+xml', 904 | ['tcap'] = 'application/vnd.3gpp2.tcap', 905 | ['ipk'] = 'application/vnd.shana.informed.package', 906 | ['acc'] = 'application/vnd.americandynamics.acc', 907 | ['potx'] = 'application/vnd.openxmlformats-officedocument.presentationml.template', 908 | ['bh2'] = 'application/vnd.fujitsu.oasysprs', 909 | ['nfo'] = 'text/x-nfo', 910 | ['ppd'] = 'application/vnd.cups-ppd', 911 | ['mathml'] = 'application/mathml+xml', 912 | ['onepkg'] = 'application/onenote', 913 | ['azs'] = 'application/vnd.airzip.filesecure.azs', 914 | ['icc'] = 'application/vnd.iccprofile', 915 | ['eol'] = 'audio/vnd.digital-winds', 916 | ['fti'] = 'application/vnd.anser-web-funds-transfer-initiation', 917 | ['joda'] = 'application/vnd.joost.joda-archive', 918 | ['ivp'] = 'application/vnd.immervision-ivp', 919 | ['acutc'] = 'application/vnd.acucorp', 920 | ['uvvu'] = 'video/vnd.uvvu.mp4', 921 | ['ser'] = 'application/java-serialized-object', 922 | ['fnc'] = 'application/vnd.frogans.fnc', 923 | ['wg'] = 'application/vnd.pmi.widget', 924 | ['irp'] = 'application/vnd.irepository.package+xml', 925 | ['karbon'] = 'application/vnd.kde.karbon', 926 | ['p7s'] = 'application/pkcs7-signature', 927 | ['mseed'] = 'application/vnd.fdsn.mseed', 928 | ['pre'] = 'application/vnd.lotus-freelance', 929 | ['pwn'] = 'application/vnd.3m.post-it-notes', 930 | ['semf'] = 'application/vnd.semf', 931 | ['nitf'] = 'application/vnd.nitf', 932 | ['m1v'] = 'video/mpeg', 933 | ['aw'] = 'application/applixware', 934 | ['jar'] = 'application/java-archive', 935 | ['hvs'] = 'application/vnd.yamaha.hv-script', 936 | ['kia'] = 'application/vnd.kidspiration', 937 | ['flw'] = 'application/vnd.kde.kivio', 938 | ['ace'] = 'application/x-ace-compressed', 939 | ['vcd'] = 'application/x-cdlink', 940 | ['s'] = 'text/x-asm', 941 | ['plb'] = 'application/vnd.3gpp.pic-bw-large', 942 | ['i2g'] = 'application/vnd.intergeo', 943 | ['pclxl'] = 'application/vnd.hp-pclxl', 944 | ['qfx'] = 'application/vnd.intu.qfx', 945 | ['qwt'] = 'application/vnd.quark.quarkxpress', 946 | ['snd'] = 'audio/basic', 947 | ['xls'] = 'application/vnd.ms-excel', 948 | ['listafp'] = 'application/vnd.ibm.modcap', 949 | ['mag'] = 'application/vnd.ecowin.chart', 950 | ['slt'] = 'application/vnd.epson.salt', 951 | ['tfi'] = 'application/thraud+xml', 952 | ['x3d'] = 'model/x3d+xml', 953 | ['sxm'] = 'application/vnd.sun.xml.math', 954 | ['teacher'] = 'application/vnd.smart.teacher', 955 | ['pls'] = 'application/pls+xml', 956 | ['rsd'] = 'application/rsd+xml', 957 | ['ps'] = 'application/postscript', 958 | ['so'] = 'application/octet-stream', 959 | ['wmz'] = 'application/x-msmetafile', 960 | ['aiff'] = 'audio/x-aiff', 961 | ['nlu'] = 'application/vnd.neurolanguage.nlu', 962 | ['les'] = 'application/vnd.hhe.lesson-player', 963 | ['grv'] = 'application/vnd.groove-injector', 964 | ['wcm'] = 'application/vnd.ms-works', 965 | ['omdoc'] = 'application/omdoc+xml', 966 | ['hpgl'] = 'application/vnd.hp-hpgl', 967 | ['kwt'] = 'application/vnd.kde.kword', 968 | ['res'] = 'application/x-dtbresource+xml', 969 | ['cer'] = 'application/pkix-cert', 970 | ['ecelp4800'] = 'audio/vnd.nuera.ecelp4800', 971 | ['ggb'] = 'application/vnd.geogebra.file', 972 | ['edx'] = 'application/vnd.novadigm.edx', 973 | ['xltm'] = 'application/vnd.ms-excel.template.macroenabled.12', 974 | ['ivu'] = 'application/vnd.immervision-ivu', 975 | ['djvu'] = 'image/vnd.djvu', 976 | ['ngdat'] = 'application/vnd.nokia.n-gage.data', 977 | ['dna'] = 'application/vnd.dna', 978 | ['snf'] = 'application/x-font-snf', 979 | ['js'] = 'application/javascript', 980 | ['xpr'] = 'application/vnd.is-xpr', 981 | ['jam'] = 'application/vnd.jam', 982 | ['stl'] = 'application/vnd.ms-pki.stl', 983 | ['afp'] = 'application/vnd.ibm.modcap', 984 | ['p8'] = 'application/pkcs8', 985 | ['kne'] = 'application/vnd.kinar', 986 | ['xltx'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 987 | ['ez3'] = 'application/vnd.ezpix-package', 988 | ['dgc'] = 'application/x-dgc-compressed', 989 | ['sxc'] = 'application/vnd.sun.xml.calc', 990 | ['clkk'] = 'application/vnd.crick.clicker.keyboard', 991 | ['c11amz'] = 'application/vnd.cluetrust.cartomobile-config-pkg', 992 | ['hbci'] = 'application/vnd.hbci', 993 | ['clkp'] = 'application/vnd.crick.clicker.palette', 994 | ['skm'] = 'application/vnd.koan', 995 | ['zir'] = 'application/vnd.zul', 996 | ['atc'] = 'application/vnd.acucorp', 997 | ['sxg'] = 'application/vnd.sun.xml.writer.global', 998 | ['wmf'] = 'application/x-msmetafile', 999 | ['texinfo'] = 'application/x-texinfo', 1000 | ['mbk'] = 'application/vnd.mobius.mbk', 1001 | ['wbs'] = 'application/vnd.criticaltools.wbs+xml', 1002 | ['g3w'] = 'application/vnd.geospace', 1003 | ['aep'] = 'application/vnd.audiograph', 1004 | ['sxw'] = 'application/vnd.sun.xml.writer', 1005 | ['emz'] = 'application/x-msmetafile', 1006 | ['mlp'] = 'application/vnd.dolby.mlp', 1007 | ['odg'] = 'application/vnd.oasis.opendocument.graphics', 1008 | ['xlsb'] = 'application/vnd.ms-excel.sheet.binary.macroenabled.12', 1009 | ['ptid'] = 'application/vnd.pvi.ptid1', 1010 | ['xlsm'] = 'application/vnd.ms-excel.sheet.macroenabled.12', 1011 | ['setpay'] = 'application/set-payment-initiation', 1012 | ['maker'] = 'application/vnd.framemaker', 1013 | ['igl'] = 'application/vnd.igloader', 1014 | ['dot'] = 'application/msword', 1015 | ['eot'] = 'application/vnd.ms-fontobject', 1016 | ['fdf'] = 'application/vnd.fdf', 1017 | ['ppt'] = 'application/vnd.ms-powerpoint', 1018 | ['uvx'] = 'application/vnd.dece.unspecified', 1019 | ['sgl'] = 'application/vnd.stardivision.writer-global', 1020 | ['rdf'] = 'application/rdf+xml', 1021 | ['m3a'] = 'audio/mpeg', 1022 | ['mft'] = 'application/rpki-manifest', 1023 | ['mpp'] = 'application/vnd.ms-project', 1024 | ['docm'] = 'application/vnd.ms-word.document.macroenabled.12', 1025 | ['tsv'] = 'text/tab-separated-values', 1026 | ['tpl'] = 'application/vnd.groove-tool-template', 1027 | ['rnc'] = 'application/relax-ng-compact-syntax', 1028 | ['onetoc2'] = 'application/onenote', 1029 | ['clkx'] = 'application/vnd.crick.clicker', 1030 | ['xpl'] = 'application/xproc+xml', 1031 | ['c4u'] = 'application/vnd.clonk.c4group', 1032 | ['opml'] = 'text/x-opml', 1033 | ['cdmia'] = 'application/cdmi-capability', 1034 | ['xdf'] = 'application/xcap-diff+xml', 1035 | ['gmx'] = 'application/vnd.gmx', 1036 | ['pgm'] = 'image/x-portable-graymap', 1037 | ['sub'] = 'text/vnd.dvb.subtitle', 1038 | ['sldx'] = 'application/vnd.openxmlformats-officedocument.presentationml.slide', 1039 | ['pps'] = 'application/vnd.ms-powerpoint', 1040 | ['txf'] = 'application/vnd.mobius.txf', 1041 | ['mgp'] = 'application/vnd.osgeo.mapguide.package', 1042 | ['pdb'] = 'application/vnd.palm', 1043 | ['pqa'] = 'application/vnd.palm', 1044 | ['xspf'] = 'application/xspf+xml', 1045 | ['cod'] = 'application/vnd.rim.cod', 1046 | ['htke'] = 'application/vnd.kenameaapp', 1047 | ['xps'] = 'application/vnd.ms-xpsdocument', 1048 | ['kml'] = 'application/vnd.google-earth.kml+xml', 1049 | ['scurl'] = 'text/vnd.curl.scurl', 1050 | ['uvz'] = 'application/vnd.dece.zip', 1051 | ['fh'] = 'image/x-freehand', 1052 | ['sis'] = 'application/vnd.symbian.install', 1053 | ['azw'] = 'application/vnd.amazon.ebook', 1054 | ['see'] = 'application/vnd.seemail', 1055 | ['stw'] = 'application/vnd.sun.xml.writer.template', 1056 | ['dxf'] = 'image/vnd.dxf', 1057 | ['sti'] = 'application/vnd.sun.xml.impress.template', 1058 | 1059 | -- aditional extensions 1060 | 1061 | ['vtt'] = 'text/vtt', 1062 | ['crx'] = 'application/x-chrome-extension', 1063 | ['htc'] = 'text/x-component', 1064 | ['manifest'] = 'text/cache-manifest', 1065 | ['buffer'] = 'application/octet-stream', 1066 | ['m4p'] = 'application/mp4', 1067 | ['m4a'] = 'audio/mp4', 1068 | ['ts'] = 'video/MP2T', 1069 | ['webapp'] = 'application/x-web-app-manifest+json', 1070 | ['lua'] = 'text/x-lua', 1071 | ['luac'] = 'application/x-lua-bytecode', 1072 | ['markdown'] = 'text/x-markdown', 1073 | ['md'] = 'text/x-markdown', 1074 | ['mkd'] = 'text/x-markdown', 1075 | ['ini'] = 'text/plain', 1076 | ['mdp'] = 'application/dash+xml', 1077 | ['map'] = 'application/json', 1078 | ['xsd'] = 'application/xml', 1079 | ['opus'] = 'audio/ogg', 1080 | ['gz'] = 'application/x-gzip' 1081 | }, 1082 | 1083 | -- This contains filename overrides for certain files, like README files. 1084 | -- Sort them in the same order as extensions. 1085 | 1086 | filenames = { 1087 | ['COPYING'] = 'text/plain', 1088 | ['LICENSE'] = 'text/plain', 1089 | ['Makefile'] = 'text/x-makefile', 1090 | ['README'] = 'text/plain' 1091 | } 1092 | } 1093 | 1094 | 1095 | -- Creates a copy of the MIME types database for customization. 1096 | 1097 | function mimetypes.copy (db) 1098 | db = db or defaultdb 1099 | return copy(db) 1100 | end 1101 | 1102 | 1103 | -- Guesses the MIME type of the file with the given name. 1104 | -- It is returned as a string. If the type cannot be guessed, then nil is 1105 | -- returned. 1106 | 1107 | function mimetypes.guess (filename, db) 1108 | local filename = filename:lower() 1109 | db = db or defaultdb 1110 | if db.filenames[filename] then 1111 | return db.filenames[filename] 1112 | end 1113 | local ext = extension(filename) 1114 | if ext then 1115 | return db.extensions[ext] 1116 | end 1117 | return nil 1118 | end 1119 | 1120 | return mimetypes 1121 | -------------------------------------------------------------------------------- /libs/resty-template.lua: -------------------------------------------------------------------------------- 1 | local setmetatable = setmetatable 2 | local tostring = tostring 3 | local setfenv = setfenv 4 | local concat = table.concat 5 | local assert = assert 6 | local open = io.open 7 | local load = load 8 | local type = type 9 | local dump = string.dump 10 | local find = string.find 11 | local gsub = string.gsub 12 | local byte = string.byte 13 | local sub = string.sub 14 | 15 | local lpath = require("path") 16 | 17 | local HTML_ENTITIES = { 18 | ["&"] = "&", 19 | ["<"] = "<", 20 | [">"] = ">", 21 | ['"'] = """, 22 | ["'"] = "'", 23 | ["/"] = "/" 24 | } 25 | 26 | local CODE_ENTITIES = { 27 | ["{"] = "{", 28 | ["}"] = "}", 29 | ["&"] = "&", 30 | ["<"] = "<", 31 | [">"] = ">", 32 | ['"'] = """, 33 | ["'"] = "'", 34 | ["/"] = "/" 35 | } 36 | 37 | local ok, newtab = pcall(require, "table.new") 38 | if not ok then newtab = function() return {} end end 39 | 40 | local caching = true 41 | local template = newtab(0, 13); 42 | 43 | template._VERSION = "1.5" 44 | template.cache = {} 45 | template.concat = concat 46 | 47 | local function rpos(view, s) 48 | while s > 0 do 49 | local c = sub(view, s, s) 50 | if c == " " or c == "\t" or c == "\0" or c == "\x0B" then 51 | s = s - 1 52 | else 53 | break; 54 | end 55 | end 56 | return s 57 | end 58 | 59 | local function read_file(path) 60 | local file = open(path, "rb") 61 | if not file then return nil end 62 | local content = file:read "*a" 63 | file:close() 64 | return content 65 | end 66 | 67 | local function load_lua(path) 68 | return read_file(path) or path 69 | end 70 | 71 | 72 | template.load = load_lua 73 | 74 | local load_chunk 75 | 76 | if _VERSION == "Lua 5.1" then 77 | local context = { __index = function(t, k) 78 | return t.context[k] or t.template[k] or _G[k] 79 | end } 80 | if jit then 81 | load_chunk = function(view) 82 | return assert(load(view, nil, "tb", setmetatable({ template = template }, context))) 83 | end 84 | else 85 | load_chunk = function(view) 86 | local func = assert(loadstring(view)) 87 | setfenv(func, setmetatable({ template = template }, context)) 88 | return func 89 | end 90 | end 91 | else 92 | local context = { __index = function(t, k) 93 | return t.context[k] or t.template[k] or _ENV[k] 94 | end } 95 | load_chunk = function(view) 96 | return assert(load(view, nil, "tb", setmetatable({ template = template }, context))) 97 | end 98 | end 99 | 100 | function template.caching(enable) 101 | if enable ~= nil then caching = enable == true end 102 | return caching 103 | end 104 | 105 | function template.output(s) 106 | if s == nil then return "" end 107 | if type(s) == "function" then return template.output(s()) end 108 | return tostring(s) 109 | end 110 | 111 | function template.escape(s, c) 112 | if type(s) == "string" then 113 | if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end 114 | return gsub(s, "[\">/<'&]", HTML_ENTITIES) 115 | end 116 | return template.output(s) 117 | end 118 | 119 | function template.new(view, layout) 120 | assert(view, "view was not provided for template.new(view, layout).") 121 | local render, compile = template.render, template.compile 122 | if layout then 123 | return setmetatable({ render = function(self, context) 124 | local context = context or self 125 | context.blocks = context.blocks or {} 126 | context.view = compile(view)(context) 127 | return render(layout, context) 128 | end }, { __tostring = function(self) 129 | local context = context or self 130 | context.blocks = context.blocks or {} 131 | context.view = compile(view)(context) 132 | return compile(layout)(context) 133 | end }) 134 | end 135 | return setmetatable({ render = function(self, context) 136 | return render(view, context or self) 137 | end }, { __tostring = function(self) 138 | return compile(view)(context or self) 139 | end }) 140 | end 141 | 142 | function template.precompile(view, path, strip) 143 | local chunk = dump(template.compile(view), strip ~= false) 144 | if path then 145 | local file = open(path, "wb") 146 | file:write(chunk) 147 | file:close() 148 | end 149 | return chunk 150 | end 151 | 152 | function template.compile(view, key, plain) 153 | assert(view, "view was not provided for template.compile(view, key, plain).") 154 | if key == "no-cache" then 155 | return load_chunk(template.parse(view, plain, key)), false 156 | end 157 | key = key or view 158 | local cache = template.cache 159 | if cache[key] then return cache[key], true end 160 | local func = load_chunk(template.parse(view, plain, key)) 161 | if caching then cache[key] = func end 162 | return func, false 163 | end 164 | 165 | function template.parse(view, plain, key) 166 | assert(view, "view was not provided for template.parse(view, plain).") 167 | local concat, rpos, find, byte, sub = concat, rpos, find, byte, sub 168 | if not plain then 169 | filePath = view 170 | baseDir = lpath.dirname(filePath) 171 | view = template.load(view) 172 | if byte(sub(view, 1, 1)) == 27 then return view end 173 | end 174 | local c = {[[ 175 | context=(...) or {} 176 | local function include(v, c, key) 177 | return template.compile(v, key)(c or context) 178 | end 179 | local ___,blocks,layout={},blocks or {} 180 | ]]} 181 | local i, s = 1, find(view, "{", 1, true) 182 | while s do 183 | local t, p, d, z, r = sub(view, s + 1, s + 1), s + 2 184 | if t == "{" then 185 | local e = find(view, "}}", p, true) 186 | if e then 187 | d = concat{"___[#___+1]=template.escape(", sub(view, p, e - 1), ")\n" } 188 | z = e + 1 189 | end 190 | elseif t == "*" then 191 | local e = (find(view, "*}", p, true)) 192 | if e then 193 | d = concat{"___[#___+1]=template.output(", sub(view, p, e - 1), ")\n" } 194 | z = e + 1 195 | end 196 | elseif t == "%" then 197 | local e = find(view, "%}", p, true) 198 | if e then 199 | local n = e + 2 200 | if sub(view, n, n) == "\n" then 201 | n = n + 1 202 | end 203 | d = concat{sub(view, p, e - 1), "\n" } 204 | z, r = n - 1, true 205 | end 206 | elseif t == "(" then 207 | local e = find(view, ")}", p, true) 208 | if e then 209 | local f = sub(view, p, e - 1) 210 | local x = (find(f, ",", 2, true)) 211 | if x then 212 | f = f:gsub(" ", function()return "" end) 213 | local x = (find(f, ",", 2, true)) 214 | local fpt = lpath.resolve(baseDir,f:sub(0,f:find(',',2, true)-1)) 215 | d = concat{"___[#___+1]=include([=[", fpt, "]=],", sub(f, x + 1), ",'",key,"'", ")\n"} 216 | else 217 | d = concat{"___[#___+1]=include([=[", lpath.resolve(baseDir,f), "]=], nil, '", key, "')\n" } 218 | end 219 | z = e + 1 220 | end 221 | elseif t == "[" then 222 | local e = find(view, "]}", p, true) 223 | if e then 224 | d = concat{"___[#___+1]=include(", sub(view, p, e - 1), ")\n" } 225 | z = e + 1 226 | end 227 | elseif t == "-" then 228 | local e = find(view, "-}", p, true) 229 | if e then 230 | local x, y = find(view, sub(view, s, e + 1), e + 2, true) 231 | if x then 232 | y = y + 1 233 | x = x - 1 234 | if sub(view, y, y) == "\n" then 235 | y = y + 1 236 | end 237 | local b = sub(view, p, e - 1) 238 | if b == "verbatim" or b == "raw" then 239 | d = concat{"___[#___+1]=[=[", sub(view, e + 2, x), "]=]\n" } 240 | z, r = y - 1, false 241 | else 242 | if sub(view, x, x) == "\n" then 243 | x = x - 1 244 | end 245 | d = concat{'blocks["', b, '"]=include[=[', sub(view, e + 2, x), "]=]\n" } 246 | z, r = y - 1, true 247 | end 248 | 249 | end 250 | end 251 | elseif t == "#" then 252 | local e = find(view, "#}", p, true) 253 | if e then 254 | e = e + 2 255 | if sub(view, e, e) == "\n" then 256 | e = e + 1 257 | end 258 | d = "" 259 | z, r = e - 1, true 260 | end 261 | end 262 | if d then 263 | c[#c+1] = concat{"___[#___+1]=[=[\n", sub(view, i, r and rpos(view, s - 1) or s - 1), "]=]\n" } 264 | if d ~= "" then 265 | c[#c+1] = d 266 | end 267 | s, i = z, z + 1 268 | end 269 | s = find(view, "{", s + 1, true) 270 | end 271 | c[#c+1] = concat{"___[#___+1]=[=[\n", sub(view, i), "]=]\n"} 272 | c[#c+1] = "return layout and include(layout,setmetatable({view=template.concat(___),blocks=blocks},{__index=context})) or template.concat(___)" 273 | return concat(c) 274 | end 275 | 276 | function template.render(view, context, key, plain) 277 | assert(view, "view was not provided for template.render(view, context, key, plain).") 278 | return template.compile(view, key, plain)(context) 279 | end 280 | 281 | return template 282 | -------------------------------------------------------------------------------- /libs/template/403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 403 6 | 27 | 28 | 29 |

403

30 |

Your are not authorized to view this page.

31 | 32 | 33 | -------------------------------------------------------------------------------- /libs/template/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 6 | 27 | 28 | 29 |

404

30 |

The page your are looking for doesn't exist.

31 | 32 | 33 | -------------------------------------------------------------------------------- /libs/template/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 500 6 | 27 | 28 | 29 |

500

30 |

Internal Server Error.

31 | 32 | 33 | -------------------------------------------------------------------------------- /libs/template/directory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Index of {{ currentPath }} 6 | 7 | 8 |

Index of {{ currentPath }}

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 28 | 29 | {% for idx, file in pairs(files) do %} 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | {% end %} 43 | 44 |
TypeNameSizeDescriptionLast Modified

23 | 24 | 26 | __Parent Directory__ 27 |
{% if file.type == "file" then %} 32 | 33 | {% else %} 34 | 35 | {% end %} 36 | {{ file.name }}{{ file.size }} - {{ file.lastModified }}
45 | 46 | 47 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by: Cyril. 3 | -- Created at: 15/11/21 下午4:19 4 | -- Email: houshoushuai@gmail.com 5 | -- 6 | 7 | require("./init") 8 | 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mooncake", 3 | "version": "0.0.10", 4 | "description": "Express like web framework but powered by luvit.", 5 | "main": "index.lua", 6 | "scripts": { 7 | "test": "luvit test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/cyrilis/mooncake.git" 12 | }, 13 | "keywords": [ 14 | "luvit", 15 | "lua", 16 | "expressjs", 17 | "mooncake", 18 | "web", 19 | "framework", 20 | "server", 21 | "moonscript" 22 | ], 23 | "author": "Cyril Hou (http://cyrilis.com/)", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/cyrilis/mooncake/issues" 27 | }, 28 | "homepage": "https://github.com/cyrilis/mooncake" 29 | } 30 | -------------------------------------------------------------------------------- /package.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by Cyril Hou 3 | -- Date: 15/9/28 4 | -- Time: 2015-09-28 16:24:56 5 | -- 6 | return { 7 | name = "cyrilis/mooncake", 8 | version = "0.1.11", 9 | homepage = "https://github.com/cyrilis/luvit-mooncake", 10 | description = "Web framework for Luvit lang.", 11 | tags = {"luvit", "web framework", "web", "application", "express", "mooncake", "framework"}, 12 | license = "MIT", 13 | author = { name = "Cyril Hou", email = "houshoushuai@gmail.com"}, 14 | dependencies = { 15 | "luvit/require", 16 | "luvit/pretty-print", 17 | "luvit/http", 18 | "luvit/https", 19 | "luvit/path", 20 | "luvit/fs", 21 | "luvit/json" 22 | }, 23 | files = { 24 | "**.lua", 25 | "**.md", 26 | "**.html", 27 | "**.elua" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/init.lua: -------------------------------------------------------------------------------- 1 | local MoonCake = require("../") 2 | local server = MoonCake:new() 3 | local env = require("env") 4 | 5 | server:use(function(req, res, next) 6 | next() 7 | end) 8 | 9 | server:route("get", "/", function(req, res) 10 | res:render("./views/index.html", { 11 | title= "Hello world from MoonCake!", 12 | address = req.socket:address(), 13 | message = "You are welcome!", 14 | names = {"Tom", "Jerry", "Wof"}, 15 | jquery = '' 16 | }) 17 | end) 18 | 19 | server:get("/etlua", function(req, res) 20 | res:render("./views/index.elua", { 21 | title= "Hello world from MoonCake!", 22 | message = "You are welcome!", 23 | names = {"Tom", "Jerry", "Wof"}, 24 | jquery = '' 25 | }) 26 | end) 27 | 28 | server:route("get", "/users/:id", function(q, s) 29 | s:send("List User in Databases => " .. q.params.id) 30 | end) 31 | 32 | server:get("/setCookie", function(req, res) 33 | res:setCookie("WTF", "Test", { 34 | path = "/", 35 | httpOnly = true 36 | }):send("Set Cookie Test.") 37 | end) 38 | 39 | server:get("/removeCookie", function(req, res) 40 | res:removeCookie("WTF"):send("RemoveCookie") 41 | end) 42 | 43 | server:all("/hello.test", function(q, s) 44 | s:send("HELLO!") 45 | end) 46 | 47 | server:all("/hello.hello/*", function(q, s) 48 | s:send("Splat!") 49 | end) 50 | 51 | server:get("/WTF", function(q, s) 52 | s:redirect("/hello") 53 | end) 54 | 55 | server:get("/posts", function(q,s) 56 | s:send("Post list: ...") 57 | end) 58 | 59 | server:post("/posts/new", function(q,s) 60 | if q.body.title and q.body.content then 61 | p("new post") 62 | -- Save to DB: 63 | -- DB.save("post", {title = q.body.title, content = q.body.content}) 64 | s:redirect("/posts") 65 | end 66 | end) 67 | 68 | server:post("/", function(req, res) 69 | p(req.files) 70 | res:json(req.files) 71 | end) 72 | 73 | server:static("./libs/", { 74 | root = "/static/", 75 | maxAge = 31536000 -- one year 76 | }) 77 | 78 | -- Test render to file : renderToFile 79 | server:get("/cache", function (req, res, next) 80 | p("Cache test....") 81 | res:renderToFile("./views/index.html", { 82 | title= "Hello world from MoonCake!", 83 | message = "You are welcome!", 84 | names = {"Tom", "Jerry", "Wof"}, 85 | jquery = '' 86 | }, "./cache.html"); 87 | res:sendFile("./cache.html") 88 | end) 89 | 90 | server:get("/testError", function (req, res, next) 91 | next("ERROR: SOMETHING HAPPEND.") 92 | -- next() 93 | end) 94 | 95 | MoonCake.serverError = function (req, res, error) 96 | res:status(500):json(error) 97 | end 98 | 99 | server:start(8081) 100 | -------------------------------------------------------------------------------- /test/views/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/views/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 13 | 14 | -------------------------------------------------------------------------------- /test/views/index.elua: -------------------------------------------------------------------------------- 1 | <%- include("../header.html") %> 2 |

<%= message %>

3 |
    4 | <% for _, name in ipairs(names) do %> 5 |
  • <%= name %>
  • 6 | <% end %> 7 |
8 | 9 |
10 | 11 | 12 | 13 |
14 | <%- include("../footer.html") %> -------------------------------------------------------------------------------- /test/views/index.html: -------------------------------------------------------------------------------- 1 | {(./header.html)} 2 |

{{message}}

3 |

{{address.ip}}

4 |
    5 | {% for _, name in ipairs(names) do %} 6 |
  • {{name}}
  • 7 | {% end %} 8 |
9 |
10 | 11 | 12 | 13 |
14 | {(./footer.html)} 15 | --------------------------------------------------------------------------------