├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── website ├── start.sh ├── svr │ ├── cert │ │ ├── certificate.pem │ │ └── privatekey.pem │ └── sitetest.js ├── tmp │ ├── session │ │ └── readme.txt │ └── upload │ │ └── readme.txt └── web │ ├── header.xml │ ├── login.htm │ ├── setting.htm │ ├── template.jade │ └── template.node └── websvr ├── test ├── lib │ ├── MIME.types │ ├── MIMETYPES.js │ ├── mime.js │ ├── test.js │ ├── types.js │ ├── types │ │ ├── mime.types │ │ └── node.types │ ├── underscore.js │ └── uuid.js ├── testExtend.js ├── testFormat.js ├── testMIME.js ├── testSession.js ├── testString.js └── testUUID.js └── websvr.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) Kris Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WebSvr 2 | ============== 3 | A simple web server, implement with filter and handler. 4 | 5 | 6 | Features 7 | -------------- 8 | - Filter (Middleware): A request will try to match all the filters first. 9 | - Handler: When a request matched a handler, it will returned, only one handler will be executed. 10 | - Session: By config sessionDir, you can store session in files, with JSON format 11 | - File: Support uploading files 12 | - Cache: Cahce template in release mode 13 | 14 | Install 15 | -------------- 16 | 17 | npm install websvr 18 | 19 | 20 | Start 21 | -------------- 22 | It's simple to start the websvr. 23 | 24 | //import WebSvr module 25 | var WebSvr = require("websvr"); 26 | 27 | //Start the WebSvr, runnting at parent folder, default port is 8054, directory browser enabled; 28 | //Trying at: http://localhost:8054 29 | var webSvr = WebSvr({ 30 | home: "./web" 31 | , listDir: true 32 | , debug: true 33 | , sessionTimeout: 60 * 1000 34 | }); 35 | 36 | 37 | Filter (HttpModule) 38 | -------------- 39 | Session based authentication, basically useage: 40 | 41 | /* 42 | Session support; 43 | */ 44 | webSvr.filter(function(req, res) { 45 | //Link to next filter 46 | req.filter.next(); 47 | }, {session:true}); 48 | 49 | 50 | /* 51 | * filter equal to use 52 | */ 53 | webSvr.use('/home', function(req, res) { 54 | //do sth. 55 | req.filter.next(); 56 | }); 57 | 58 | 59 | Filter all the requests that begin with "test/", check the user permission in session, except the "index.htm" and "login.do". 60 | 61 | /* 62 | Session Filter: protect web/* folder => (validation by session); 63 | */ 64 | webSvr.filter(function(req, res) { 65 | //It's not index.htm/login.do, do the session validation 66 | if (req.url.indexOf("login.htm") < 0 && req.url.indexOf("login.do") < 0 && req.url !== '/') { 67 | var val = req.session.get("username"); 68 | 69 | console.log("session username:", val); 70 | !val && res.end("You must login, first!"); 71 | 72 | //Link to next filter 73 | req.filter.next(); 74 | } else { 75 | req.filter.next(); 76 | } 77 | }, { session: true }); 78 | 79 | Handler (HttpHandler, Servlet) 80 | -------------- 81 | Handle Login and put the username in session 82 | 83 | /* 84 | Handler: login.do => (validate the username & password) 85 | username: admin 86 | password: 12345678 87 | webSvr.url equal to webSvr.get/webSvr.post/webSvr.handle 88 | */ 89 | webSvr.url("login.do", function(req, res) { 90 | var qs = req.body; 91 | console.log(qs); 92 | if (qs.username == "admin" && qs.password == "12345678") { 93 | //Add username in session 94 | var session = req.session.set("username", qs.username); 95 | console.log(session); 96 | res.redirect("setting.htm"); 97 | } else { 98 | res.writeHead(401); 99 | res.end("Wrong username/password"); 100 | } 101 | }, 'qs'); 102 | 103 | Note: 104 | -------------- 105 | Filter and Handler doesn't have the same match rules when you sending a request 106 | 107 | Filter : Match any section in the request url, for example 108 | 109 | websvr.filter(".svr", cb); 110 | 111 | The result is 112 | 113 | request: "domain.com/admin/root/login.svr" match: true 114 | 115 | Handler : Match from the begining (ignore the first '/'), etc: 116 | 117 | websvr.handle("root/login", cb) //equal to 118 | websvr.handle("/root/login", cb) 119 | 120 | etc: 121 | 122 | request: "domain.com/root/login.svr" match: true 123 | request: "domain.com/admin/root/login.svr" match: false 124 | 125 | You can use regular expression to match any part of url in Handler. 126 | 127 | Cookies 128 | -------------- 129 | 130 | //get cookie value by key 131 | req.cookies[key]; 132 | 133 | 134 | Template 135 | -------------- 136 | Render template with params, using doT template engine 137 | 138 | res.render([view, model]); 139 | 140 | View is optional, it will get the location of template from req.url 141 | 142 | res.render({json: true}); 143 | 144 | View is a relative path, relative to web home 145 | 146 | //means related to Setting.home 147 | res.render("list.tmpl", {json: true}); 148 | 149 | View is a absolute path, relative to web root 150 | 151 | //means related to Setting.root 152 | res.render("/list.tmpl", {json: true}); 153 | 154 | Render raw HTML views 155 | 156 | res.renderRaw(viewContent, model); 157 | 158 | You can change template engine, 159 | 160 | webSvr.engine(engineFunc); 161 | 162 | for example: 163 | 164 | webSvr.engine(require("doT").compile); 165 | webSvr.engine(require("jade").compile); 166 | 167 | You can define some default properties in model, for example header/footer, this parameters will be overridden if they have the same key in your custom model. 168 | 169 | webSvr.model({ 170 | title : "WebSvr Page" 171 | , username: "WebSvr" 172 | }); 173 | 174 | You can use template and render it by using websvr.render(tmplPath, model, callback), tmplPath relative to webSvr.home; 175 | 176 | //pre-defined model 177 | var model = {}; 178 | webSvr.model(model); 179 | 180 | //render a template using model, callbak argument is result html 181 | webSvr.render("header.tmpl", {categoryList: category.categoryList}, function(html) { 182 | //store rendered html to header 183 | model.header = html; 184 | console.log(model); 185 | }); 186 | 187 | Include file, you can using "#include" to include a file during rendering a template, in order to make the process easier, the file will fetched from the cache pool so the first refresh will not work when you first start the server; 188 | 189 | ###Be ware: include file: relative to web home, not the template file itself.### 190 | 191 | 192 | 193 |
194 | 195 | Cache templates, by default, server will cache the templates(include the "include file" in the templates), turn it off via: 196 | 197 | var webSvr = WebSvr({ 198 | templateCache: false 199 | }); 200 | 201 | Clear the cached templates 202 | 203 | webSvr.clear() 204 | 205 | 206 | 207 | Enable template engine and '', using: res.render()/res.render(model)/res.render(tmplPath, model), etc 208 | 209 | webSvr.url(['login.htm', 'setting.htm'], function(req, res) { 210 | res.render(); 211 | }); 212 | 213 | 214 | It also support include file in include files, but you need to refresh more times after the first running. 215 | 216 | 217 | Settings 218 | -------------- 219 | Return configuration of current WebSvr instance 220 | 221 | webSvr.settings 222 | 223 | Settings API: 224 | 225 | var Settings = { 226 | //home folder of web 227 | home: "../" 228 | 229 | //http start 230 | //default port of http 231 | , port: 8054 232 | 233 | //default port of https 234 | , httpsPort: 8443 235 | , httpsKey: "" 236 | , httpsCert: "" 237 | 238 | //list files in directory 239 | , listDir: false 240 | //enable client-side cache(304) 241 | , cache: true 242 | //enable debug information output 243 | , debug: true 244 | //enable cache of template/include file (when enabled templates will not be refreshed before restart) 245 | , templateCache: true 246 | 247 | //default pages, only one is supported 248 | , defaultPage: "index.html" 249 | //404 template/static file 250 | , 404: "404.tmpl" 251 | //show errors to user(displayed in response) 252 | , showError: true 253 | 254 | /* 255 | Session timeout, in milliseconds. 256 | */ 257 | , sessionTimeout: 1440000 258 | 259 | //session file stored here 260 | , sessionDir: os.tmpDir() 261 | 262 | //session domain, e.g. ".google.com" 263 | , sessionDomain: "" 264 | 265 | //tempary upload file stored here 266 | , uploadDir: os.tmpDir() 267 | }; 268 | 269 | Response 270 | -------------- 271 | Extension on reponse object 272 | 273 | Ouput file, filepath relative to the root 274 | 275 | res.sendRootFile(filePath, [callback]); 276 | 277 | Ouput file, filepath relative to the home (web dir) 278 | 279 | res.sendFile(filePath); 280 | res.sendHomeFile(filePath); 281 | 282 | Reidrect request 283 | 284 | res.redirect(url); 285 | 286 | Return request object 287 | 288 | res.req 289 | 290 | Set Content-Type 291 | 292 | res.type('xml'); 293 | 294 | Set/Remove Cookie 295 | 296 | //Set Cookie 297 | res.cookie(name, value [, {domain: string, path: string, expires: date, secure, httponly }]) 298 | //Remove Cookie 299 | res.cookie(name, null); 300 | 301 | 302 | Change default charset 303 | 304 | res.charset = 'utf-8' 305 | 306 | 307 | WebSvr APIs 308 | -------------- 309 | Mapping url to file, webSvr.url equal to webSvr.handle 310 | 311 | webSvr.url("sitetest", ["svr/sitetest.js"]); 312 | 313 | Mapping url to string 314 | 315 | webSvr.url("hello", "Hello WebSvr!") 316 | 317 | Handle post 318 | 319 | webSvr.post("post.htm", function(req, res) { 320 | res.end('Received : ' + req.body); 321 | }); 322 | 323 | //Equal to 324 | webSvr.handle("post.htm", function(req, res) { 325 | res.end('Received : ' + req.body); 326 | }, {post: true}); 327 | 328 | Post type 329 | 330 | post: true/"json"/"qs" 331 | 332 | Handle session 333 | 334 | webSvr.session("session required url", function(req, res) { 335 | console.log(req.session); 336 | res.end(); 337 | }); 338 | 339 | 340 | Handle upload file, it's a specfic filter 341 | 342 | webSvr.file("upload.do", function(req, res) { 343 | res.writeHead(200, {"Content-Type": "text/plain"}); 344 | //Upload file is stored in req.files 345 | //form fields is stored in req.body 346 | res.write(JSON.stringify(req.body)); 347 | res.end(JSON.stringify(req.files)); 348 | }); 349 | 350 | Valid File beofre receing it 351 | 352 | /* 353 | * Valid request before receiving 354 | */ 355 | webSvr.file("upload.do", function(req, res) { 356 | res.writeHead(200, {"Content-Type": "text/plain"}); 357 | res.send(req.files); 358 | }).before(function(req, res) { 359 | if ((req.headers['content-length'] || 0) > 245760) { 360 | res.send('Posting is too large, should less than 240K') 361 | } else { 362 | return true 363 | } 364 | }); 365 | 366 | Multi-Mapping in Handler or Filter 367 | 368 | webSvr.handle(["about", "help", "welcome"], function(req, res) { 369 | res.writeFile(req.url + ".shtml"); 370 | }, {post: true}); 371 | 372 | Pickup parameters from url expression 373 | 374 | webSvr.handle("/verify/:id", function(req, res) { 375 | var id = req.params.id; 376 | }); 377 | 378 | Parse parameters in url 379 | 380 | * expression = /home/:key/:pager 381 | * /home/JavaScript => { id: 'JavaScript', pager: '' } 382 | * /key/JavaScript => false 383 | 384 | var params = webSvr.parseUrl(expression, reqUrl); 385 | 386 | Send API 387 | 388 | webSvr.send([type or statusCode, ] content); 389 | 390 | Send JSON 391 | 392 | webSvr.send('json', { a: 1, b: 2 }); 393 | 394 | Send String 395 | 396 | webSvr.send(401, 'No permission'); 397 | 398 | 399 | Multi-instance support 400 | -------------- 401 | Start a https server, make sure that the port will no conflict with others. 402 | 403 | var httpsSvr = new WebSvr({ 404 | home: "./" 405 | 406 | //disable http server 407 | , port: null 408 | 409 | //enable https server 410 | , httpsPort: 8443 411 | , httpsKey: require("fs").readFileSync("svr/cert/privatekey.pem") 412 | , httpsCert: require("fs").readFileSync("svr/cert/certificate.pem") 413 | 414 | }).start(); 415 | 416 | Do you want to re-use the filters & handlers? 417 | 418 | httpsSvr.filters = webSvr.filters; 419 | httpsSvr.handlers = webSvr.handlers; 420 | 421 | 422 | Store session in redis 423 | -------------- 424 | Install: npm install websvr-redis 425 | 426 | var RedisStore = require('websvr-redis'); 427 | 428 | RedisStore.start({ 429 | port: 6379 430 | , host: 'ourjs.org' 431 | , auth: 'your-password-if-needed' 432 | , select: 0 433 | }); 434 | 435 | httpsSvr.sessionStore = RedisStore; 436 | 437 | 438 | Clear expired sessions, only 1 refresh timer is needed 439 | 440 | setInterval(RedisStore.clear, 1000000); 441 | 442 | 443 | 444 | 445 | Lincenses 446 | ---- 447 | MIT, see our license file 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | Demo Sites 460 | ---- 461 | 1. ourjs: url [ourjs.com](http://ourjs.com) 462 | 2. icalc: url [icalc.cn](http://icalc.cn), source code [github](https://github.com/newghost/websvr-icalc/) 463 | 464 | 465 | Websvr 466 | ==== 467 | 基于NodeJS的一个极简Web服务器, 专为ARM设计。 468 | 假设嵌入式设备需要保持长时间稳定运行,当遇到问题时也可自动重启并恢复此前用户的Session会话。 469 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "websvr", 3 | "description": "A simple web server, implement http module(filter) and handler(servlet), autorecover persistent session", 4 | "version": "0.1.41", 5 | "author": "Kris Zhang ", 6 | "dependencies": { 7 | "dot": "1.1.3", 8 | "formidable": "1.0.14", 9 | "mime": "1.2.9" 10 | }, 11 | "keywords": [ 12 | "web", 13 | "server", 14 | "https", 15 | "filter", 16 | "handler" 17 | ], 18 | "repository": "git://github.com/newghost/websvr.git", 19 | "main": "./websvr/websvr.js" 20 | } 21 | -------------------------------------------------------------------------------- /website/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo start sitetest 3 | 4 | #change current dir, in order to call it from anywhere. 5 | cd $(dirname $0) 6 | 7 | while true; do 8 | node svr/sitetest.js 9 | 10 | echo *************************************** 11 | echo Server stop working, restarting... 12 | echo *************************************** 13 | 14 | #wait 2 seconds 15 | sleep 2 16 | done -------------------------------------------------------------------------------- /website/svr/cert/certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICpTCCAg4CCQCISrlY6+bq+DANBgkqhkiG9w0BAQUFADCBljELMAkGA1UEBhMC 3 | Q04xETAPBgNVBAgMCFNoYW5naGFpMREwDwYDVQQHDAhTaGFuZ2hhaTEQMA4GA1UE 4 | CgwHRW5sb2dpYzEfMB0GA1UECwwWUmVzZWFyY2ggJiBEZXZlbG9wbWVudDESMBAG 5 | A1UEAwwJbG9jYWxob3N0MRowGAYJKoZIhvcNAQkBFgtjMnVAbGl2ZS5jbjAeFw0x 6 | MjEwMjgwNzU1MTZaFw0xMjExMjcwNzU1MTZaMIGWMQswCQYDVQQGEwJDTjERMA8G 7 | A1UECAwIU2hhbmdoYWkxETAPBgNVBAcMCFNoYW5naGFpMRAwDgYDVQQKDAdFbmxv 8 | Z2ljMR8wHQYDVQQLDBZSZXNlYXJjaCAmIERldmVsb3BtZW50MRIwEAYDVQQDDAls 9 | b2NhbGhvc3QxGjAYBgkqhkiG9w0BCQEWC2MydUBsaXZlLmNuMIGfMA0GCSqGSIb3 10 | DQEBAQUAA4GNADCBiQKBgQCgWDgMTdIEafBqczxi4BpFOL741bf3nttkJxS5eMbj 11 | bQvfDX9LHkNpqZFx3jDWecmjBhUY+B2Q1PmiBnwuRLIBaYsg3jT2nWp0fphzBcJ5 12 | JsPF3S9GjeWrzYZIuK+YRyCarlmzbjn4omidm/4wAtXuXxQYiouTMe9hyuLnxb6s 13 | mQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAB/dirZrcdB4aGUTlSGWt+LmFuz/Xl6x 14 | iWkfjwvQP7h4oCGoSUjoB9vqy5hIkCKxI+PWhW76S7YpJNXHIdjrC7BRyYVIlAmp 15 | Efm4GUlBAdSYo222my1xTF8SaEx1debDfzq2Ho8xeozpnKae4x62MoA+Fp2kphsq 16 | tLTB8YxbjIQy 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /website/svr/cert/privatekey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQCgWDgMTdIEafBqczxi4BpFOL741bf3nttkJxS5eMbjbQvfDX9L 3 | HkNpqZFx3jDWecmjBhUY+B2Q1PmiBnwuRLIBaYsg3jT2nWp0fphzBcJ5JsPF3S9G 4 | jeWrzYZIuK+YRyCarlmzbjn4omidm/4wAtXuXxQYiouTMe9hyuLnxb6smQIDAQAB 5 | AoGAWDpSlMqZPh6A0EIaPxmqut4PjuIiORlrBL/QUoHXhjpxZsmJem7rjw9j3XDy 6 | FIGs5owpPbUAp7nYpkPFPrxD6U3qVwZk/C7K2Lx2vWYlJ9vf8TTWAvIt6yyNuq5x 7 | ocFosz346viky3K5QkyvmT9y3UuQS65GKj8Hv5S9xSolAhUCQQDSGzabd9c1kgex 8 | EH96Fu/EWIE2G2U3f5/MRalAPlCIrVkBo1YjNoO8QDIb61mW0KdunQrtxrOoKDJp 9 | Opxwpkj7AkEAw15qkHWd2yfU4yw7cFWsWOLojs3pyLdSX+7++vT3gLL8y2otbyKi 10 | mWlrQJDgCnbXywUyRJtMGmLrn9X0y5IUewJBAJxGlYVpy+8SoRn4dXjwGoLmeaUv 11 | F0gCa29a2RrpvqkKlst7HBSw9adN8HeHxGlC5WaG9JwLUZHf5C8U40t+w4UCQClz 12 | keaeneSO2fNtQhs+gjfFxRPviofEpZynJ8B1U0IiN9Ks74Dh91/XZyMm2fI+buCr 13 | dJPr40TB8j5SdgLvNpsCQFaYxoFde72QZHBQKYMsOmKldhcSuQKeZ/oppuhjgYqp 14 | 9qrtIyQ2MTchc8Yu27WCU975F0UGWQTsNG0BqVtGLik= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /website/svr/sitetest.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newghost/websvr/48152562b7b135540ff5592ce29811d23367f9e1/website/svr/sitetest.js -------------------------------------------------------------------------------- /website/tmp/session/readme.txt: -------------------------------------------------------------------------------- 1 | Temporary session here. -------------------------------------------------------------------------------- /website/tmp/upload/readme.txt: -------------------------------------------------------------------------------- 1 | Temporary upload files here. -------------------------------------------------------------------------------- /website/web/header.xml: -------------------------------------------------------------------------------- 1 |
2 |
Hello: {{=it.username}}, this is global header, you can override it in your custom model.
3 |
-------------------------------------------------------------------------------- /website/web/login.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{=it.title}} 7 | 8 | 9 | 10 | 11 |

WebSvr configuration login:

12 |
13 |
    14 |
  • 15 | 16 | 17 | admin 18 |
  • 19 |
  • 20 | 21 | 22 | 12345678 23 |
  • 24 |
  • 25 | 26 | 27 |
  • 28 |
29 |
30 | 31 |
32 | 33 |

Post querystring Testing:

34 |
35 |
    36 |
  • 37 | 38 | 39 | admin 40 |
  • 41 |
  • 42 | 43 | 44 | 12345678 45 |
  • 46 |
  • 47 | 48 | 49 |
  • 50 |
51 |
52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /website/web/setting.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{=it.title}} 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /website/web/template.jade: -------------------------------------------------------------------------------- 1 | doctype 5 2 | html(lang="en") 3 | head 4 | title= pageTitle 5 | script(type='text/javascript'). 6 | if (foo) bar(1 + 5) 7 | body 8 | h1 Jade - node template engine 9 | #container.col 10 | if youAreUsingJade 11 | p You are amazing 12 | else 13 | p Get on it! 14 | p. 15 | Jade is a terse and simple templating language with a 16 | strong focus on performance and powerful features. -------------------------------------------------------------------------------- /website/web/template.node: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Login 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /websvr/test/lib/MIME.types: -------------------------------------------------------------------------------- 1 | var MIMETYPES = module.exports = { 2 | "js": "application/javascript", 3 | "json": "application/json", 4 | "doc": "application/msword", 5 | "pdf": "application/pdf", 6 | "7z": "application/x-7z-compressed", 7 | "torrent": "application/x-bittorrent", 8 | "bz": "application/x-bzip", 9 | "bz2": "application/x-bzip2", 10 | "boz": "application/x-bzip2", 11 | "application": "application/x-ms-application", 12 | "rar": "application/x-rar-compressed", 13 | "sh": "application/x-sh", 14 | "swf": "application/x-shockwave-flash", 15 | "xap": "application/x-silverlight-app", 16 | "tar": "application/x-tar", 17 | "der": "application/x-x509-ca-cert", 18 | "crt": "application/x-x509-ca-cert", 19 | "xhtml": "application/xhtml+xml", 20 | "xht": "application/xhtml+xml", 21 | "xml": "application/xml", 22 | "xsl": "application/xml", 23 | "dtd": "application/xml-dtd", 24 | "xop": "application/xop+xml", 25 | "xslt": "application/xslt+xml", 26 | "xspf": "application/xspf+xml", 27 | "zip": "application/zip", 28 | "adp": "audio/adpcm", 29 | "au": "audio/basic", 30 | "snd": "audio/basic", 31 | "mid": "audio/midi", 32 | "midi": "audio/midi", 33 | "kar": "audio/midi", 34 | "rmi": "audio/midi", 35 | "mp4a": "audio/mp4", 36 | "mpga": "audio/mpeg", 37 | "mp2": "audio/mpeg", 38 | "mp2a": "audio/mpeg", 39 | "mp3": "audio/mpeg", 40 | "m2a": "audio/mpeg", 41 | "m3a": "audio/mpeg", 42 | "oga": "audio/ogg", 43 | "ogg": "audio/ogg", 44 | "weba": "audio/webm", 45 | "wav": "audio/x-wav", 46 | "bmp": "image/bmp", 47 | "cgm": "image/cgm", 48 | "gif": "image/gif", 49 | "jpeg": "image/jpeg", 50 | "jpg": "image/jpeg", 51 | "png": "image/png", 52 | "btif": "image/prs.btif", 53 | "svg": "image/svg+xml", 54 | "svgz": "image/svg+xml", 55 | "tiff": "image/tiff", 56 | "tif": "image/tiff", 57 | "psd": "image/vnd.adobe.photoshop", 58 | "mdi": "image/vnd.ms-modi", 59 | "ico": "image/x-icon", 60 | "eml": "message/rfc822", 61 | "mime": "message/rfc822", 62 | "css": "text/css", 63 | "csv": "text/csv", 64 | "html": "text/html", 65 | "htm": "text/html", 66 | "txt": "text/plain", 67 | "text": "text/plain", 68 | "conf": "text/plain", 69 | "def": "text/plain", 70 | "list": "text/plain", 71 | "log": "text/plain", 72 | "wml": "text/vnd.wap.wml", 73 | "wmls": "text/vnd.wap.wmlscript", 74 | "3gp": "video/3gpp", 75 | "3g2": "video/3gpp2", 76 | "h261": "video/h261", 77 | "h263": "video/h263", 78 | "h264": "video/h264", 79 | "jpgv": "video/jpeg", 80 | "jpm": "video/jpm", 81 | "jpgm": "video/jpm", 82 | "mp4": "video/mp4", 83 | "mp4v": "video/mp4", 84 | "mpg4": "video/mp4", 85 | "mpeg": "video/mpeg", 86 | "mpg": "video/mpeg", 87 | "mpe": "video/mpeg", 88 | "m1v": "video/mpeg", 89 | "m2v": "video/mpeg", 90 | "ogv": "video/ogg", 91 | "qt": "video/quicktime", 92 | "mov": "video/quicktime", 93 | "webm": "video/webm", 94 | "f4v": "video/x-f4v", 95 | "fli": "video/x-fli", 96 | "flv": "video/x-flv", 97 | "m4v": "video/x-m4v", 98 | "asf": "video/x-ms-asf", 99 | "asx": "video/x-ms-asf", 100 | "wm": "video/x-ms-wm", 101 | "wmv": "video/x-ms-wmv", 102 | "wmx": "video/x-ms-wmx", 103 | "wvx": "video/x-ms-wvx", 104 | "avi": "video/x-msvideo", 105 | }; -------------------------------------------------------------------------------- /websvr/test/lib/MIMETYPES.js: -------------------------------------------------------------------------------- 1 | var MIMETYPES = module.exports = { 2 | "123": "application/vnd.lotus-1-2-3", 3 | "ez": "application/andrew-inset", 4 | "aw": "application/applixware", 5 | "atom": "application/atom+xml", 6 | "atomcat": "application/atomcat+xml", 7 | "atomsvc": "application/atomsvc+xml", 8 | "ccxml": "application/ccxml+xml", 9 | "cdmia": "application/cdmi-capability", 10 | "cdmic": "application/cdmi-container", 11 | "cdmid": "application/cdmi-domain", 12 | "cdmio": "application/cdmi-object", 13 | "cdmiq": "application/cdmi-queue", 14 | "cu": "application/cu-seeme", 15 | "davmount": "application/davmount+xml", 16 | "dssc": "application/dssc+der", 17 | "xdssc": "application/dssc+xml", 18 | "ecma": "application/ecmascript", 19 | "emma": "application/emma+xml", 20 | "epub": "application/epub+zip", 21 | "exi": "application/exi", 22 | "pfr": "application/font-tdpfr", 23 | "stk": "application/hyperstudio", 24 | "ink": "application/inkml+xml", 25 | "inkml": "application/inkml+xml", 26 | "ipfix": "application/ipfix", 27 | "jar": "application/java-archive", 28 | "ser": "application/java-serialized-object", 29 | "class": "application/java-vm", 30 | "js": "application/javascript", 31 | "json": "application/json", 32 | "lostxml": "application/lost+xml", 33 | "hqx": "application/mac-binhex40", 34 | "cpt": "application/mac-compactpro", 35 | "mads": "application/mads+xml", 36 | "mrc": "application/marc", 37 | "mrcx": "application/marcxml+xml", 38 | "ma": "application/mathematica", 39 | "nb": "application/mathematica", 40 | "mb": "application/mathematica", 41 | "mathml": "application/mathml+xml", 42 | "mbox": "application/mbox", 43 | "mscml": "application/mediaservercontrol+xml", 44 | "meta4": "application/metalink4+xml", 45 | "mets": "application/mets+xml", 46 | "mods": "application/mods+xml", 47 | "m21": "application/mp21", 48 | "mp21": "application/mp21", 49 | "mp4s": "application/mp4", 50 | "doc": "application/msword", 51 | "dot": "application/msword", 52 | "mxf": "application/mxf", 53 | "bin": "application/octet-stream", 54 | "dms": "application/octet-stream", 55 | "lha": "application/octet-stream", 56 | "lrf": "application/octet-stream", 57 | "lzh": "application/octet-stream", 58 | "so": "application/octet-stream", 59 | "iso": "application/octet-stream", 60 | "dmg": "application/octet-stream", 61 | "dist": "application/octet-stream", 62 | "distz": "application/octet-stream", 63 | "pkg": "application/octet-stream", 64 | "bpk": "application/octet-stream", 65 | "dump": "application/octet-stream", 66 | "elc": "application/octet-stream", 67 | "deploy": "application/octet-stream", 68 | "oda": "application/oda", 69 | "opf": "application/oebps-package+xml", 70 | "ogx": "application/ogg", 71 | "onetoc": "application/onenote", 72 | "onetoc2": "application/onenote", 73 | "onetmp": "application/onenote", 74 | "onepkg": "application/onenote", 75 | "oxps": "application/oxps", 76 | "xer": "application/patch-ops-error+xml", 77 | "pdf": "application/pdf", 78 | "pgp": "application/pgp-encrypted", 79 | "asc": "application/pgp-signature", 80 | "sig": "application/pgp-signature", 81 | "prf": "application/pics-rules", 82 | "p10": "application/pkcs10", 83 | "p7m": "application/pkcs7-mime", 84 | "p7c": "application/pkcs7-mime", 85 | "p7s": "application/pkcs7-signature", 86 | "p8": "application/pkcs8", 87 | "ac": "application/pkix-attr-cert", 88 | "cer": "application/pkix-cert", 89 | "crl": "application/pkix-crl", 90 | "pkipath": "application/pkix-pkipath", 91 | "pki": "application/pkixcmp", 92 | "pls": "application/pls+xml", 93 | "ai": "application/postscript", 94 | "eps": "application/postscript", 95 | "ps": "application/postscript", 96 | "cww": "application/prs.cww", 97 | "pskcxml": "application/pskc+xml", 98 | "rdf": "application/rdf+xml", 99 | "rif": "application/reginfo+xml", 100 | "rnc": "application/relax-ng-compact-syntax", 101 | "rl": "application/resource-lists+xml", 102 | "rld": "application/resource-lists-diff+xml", 103 | "rs": "application/rls-services+xml", 104 | "gbr": "application/rpki-ghostbusters", 105 | "mft": "application/rpki-manifest", 106 | "roa": "application/rpki-roa", 107 | "rsd": "application/rsd+xml", 108 | "rss": "application/rss+xml", 109 | "rtf": "application/rtf", 110 | "sbml": "application/sbml+xml", 111 | "scq": "application/scvp-cv-request", 112 | "scs": "application/scvp-cv-response", 113 | "spq": "application/scvp-vp-request", 114 | "spp": "application/scvp-vp-response", 115 | "sdp": "application/sdp", 116 | "setpay": "application/set-payment-initiation", 117 | "setreg": "application/set-registration-initiation", 118 | "shf": "application/shf+xml", 119 | "smi": "application/smil+xml", 120 | "smil": "application/smil+xml", 121 | "rq": "application/sparql-query", 122 | "srx": "application/sparql-results+xml", 123 | "gram": "application/srgs", 124 | "grxml": "application/srgs+xml", 125 | "sru": "application/sru+xml", 126 | "ssml": "application/ssml+xml", 127 | "tei": "application/tei+xml", 128 | "teicorpus": "application/tei+xml", 129 | "tfi": "application/thraud+xml", 130 | "tsd": "application/timestamped-data", 131 | "plb": "application/vnd.3gpp.pic-bw-large", 132 | "psb": "application/vnd.3gpp.pic-bw-small", 133 | "pvb": "application/vnd.3gpp.pic-bw-var", 134 | "tcap": "application/vnd.3gpp2.tcap", 135 | "pwn": "application/vnd.3m.post-it-notes", 136 | "aso": "application/vnd.accpac.simply.aso", 137 | "imp": "application/vnd.accpac.simply.imp", 138 | "acu": "application/vnd.acucobol", 139 | "atc": "application/vnd.acucorp", 140 | "acutc": "application/vnd.acucorp", 141 | "air": "application/vnd.adobe.air-application-installer-package+zip", 142 | "fxp": "application/vnd.adobe.fxp", 143 | "fxpl": "application/vnd.adobe.fxp", 144 | "xdp": "application/vnd.adobe.xdp+xml", 145 | "xfdf": "application/vnd.adobe.xfdf", 146 | "ahead": "application/vnd.ahead.space", 147 | "azf": "application/vnd.airzip.filesecure.azf", 148 | "azs": "application/vnd.airzip.filesecure.azs", 149 | "azw": "application/vnd.amazon.ebook", 150 | "acc": "application/vnd.americandynamics.acc", 151 | "ami": "application/vnd.amiga.ami", 152 | "apk": "application/vnd.android.package-archive", 153 | "cii": "application/vnd.anser-web-certificate-issue-initiation", 154 | "fti": "application/vnd.anser-web-funds-transfer-initiation", 155 | "atx": "application/vnd.antix.game-component", 156 | "mpkg": "application/vnd.apple.installer+xml", 157 | "m3u8": "application/vnd.apple.mpegurl", 158 | "swi": "application/vnd.aristanetworks.swi", 159 | "iota": "application/vnd.astraea-software.iota", 160 | "aep": "application/vnd.audiograph", 161 | "mpm": "application/vnd.blueice.multipass", 162 | "bmi": "application/vnd.bmi", 163 | "rep": "application/vnd.businessobjects", 164 | "cdxml": "application/vnd.chemdraw+xml", 165 | "mmd": "application/vnd.chipnuts.karaoke-mmd", 166 | "cdy": "application/vnd.cinderella", 167 | "cla": "application/vnd.claymore", 168 | "rp9": "application/vnd.cloanto.rp9", 169 | "c4g": "application/vnd.clonk.c4group", 170 | "c4d": "application/vnd.clonk.c4group", 171 | "c4f": "application/vnd.clonk.c4group", 172 | "c4p": "application/vnd.clonk.c4group", 173 | "c4u": "application/vnd.clonk.c4group", 174 | "c11amc": "application/vnd.cluetrust.cartomobile-config", 175 | "c11amz": "application/vnd.cluetrust.cartomobile-config-pkg", 176 | "csp": "application/vnd.commonspace", 177 | "cdbcmsg": "application/vnd.contact.cmsg", 178 | "cmc": "application/vnd.cosmocaller", 179 | "clkx": "application/vnd.crick.clicker", 180 | "clkk": "application/vnd.crick.clicker.keyboard", 181 | "clkp": "application/vnd.crick.clicker.palette", 182 | "clkt": "application/vnd.crick.clicker.template", 183 | "clkw": "application/vnd.crick.clicker.wordbank", 184 | "wbs": "application/vnd.criticaltools.wbs+xml", 185 | "pml": "application/vnd.ctc-posml", 186 | "ppd": "application/vnd.cups-ppd", 187 | "car": "application/vnd.curl.car", 188 | "pcurl": "application/vnd.curl.pcurl", 189 | "rdz": "application/vnd.data-vision.rdz", 190 | "uvf": "application/vnd.dece.data", 191 | "uvvf": "application/vnd.dece.data", 192 | "uvd": "application/vnd.dece.data", 193 | "uvvd": "application/vnd.dece.data", 194 | "uvt": "application/vnd.dece.ttml+xml", 195 | "uvvt": "application/vnd.dece.ttml+xml", 196 | "uvx": "application/vnd.dece.unspecified", 197 | "uvvx": "application/vnd.dece.unspecified", 198 | "uvz": "application/vnd.dece.zip", 199 | "uvvz": "application/vnd.dece.zip", 200 | "fe_launch": "application/vnd.denovo.fcselayout-link", 201 | "dna": "application/vnd.dna", 202 | "mlp": "application/vnd.dolby.mlp", 203 | "dpg": "application/vnd.dpgraph", 204 | "dfac": "application/vnd.dreamfactory", 205 | "ait": "application/vnd.dvb.ait", 206 | "svc": "application/vnd.dvb.service", 207 | "geo": "application/vnd.dynageo", 208 | "mag": "application/vnd.ecowin.chart", 209 | "nml": "application/vnd.enliven", 210 | "esf": "application/vnd.epson.esf", 211 | "msf": "application/vnd.epson.msf", 212 | "qam": "application/vnd.epson.quickanime", 213 | "slt": "application/vnd.epson.salt", 214 | "ssf": "application/vnd.epson.ssf", 215 | "es3": "application/vnd.eszigno3+xml", 216 | "et3": "application/vnd.eszigno3+xml", 217 | "ez2": "application/vnd.ezpix-album", 218 | "ez3": "application/vnd.ezpix-package", 219 | "fdf": "application/vnd.fdf", 220 | "mseed": "application/vnd.fdsn.mseed", 221 | "seed": "application/vnd.fdsn.seed", 222 | "dataless": "application/vnd.fdsn.seed", 223 | "gph": "application/vnd.flographit", 224 | "ftc": "application/vnd.fluxtime.clip", 225 | "fm": "application/vnd.framemaker", 226 | "frame": "application/vnd.framemaker", 227 | "maker": "application/vnd.framemaker", 228 | "book": "application/vnd.framemaker", 229 | "fnc": "application/vnd.frogans.fnc", 230 | "ltf": "application/vnd.frogans.ltf", 231 | "fsc": "application/vnd.fsc.weblaunch", 232 | "oas": "application/vnd.fujitsu.oasys", 233 | "oa2": "application/vnd.fujitsu.oasys2", 234 | "oa3": "application/vnd.fujitsu.oasys3", 235 | "fg5": "application/vnd.fujitsu.oasysgp", 236 | "bh2": "application/vnd.fujitsu.oasysprs", 237 | "ddd": "application/vnd.fujixerox.ddd", 238 | "xdw": "application/vnd.fujixerox.docuworks", 239 | "xbd": "application/vnd.fujixerox.docuworks.binder", 240 | "fzs": "application/vnd.fuzzysheet", 241 | "txd": "application/vnd.genomatix.tuxedo", 242 | "ggb": "application/vnd.geogebra.file", 243 | "ggt": "application/vnd.geogebra.tool", 244 | "gex": "application/vnd.geometry-explorer", 245 | "gre": "application/vnd.geometry-explorer", 246 | "gxt": "application/vnd.geonext", 247 | "g2w": "application/vnd.geoplan", 248 | "g3w": "application/vnd.geospace", 249 | "gmx": "application/vnd.gmx", 250 | "kml": "application/vnd.google-earth.kml+xml", 251 | "kmz": "application/vnd.google-earth.kmz", 252 | "gqf": "application/vnd.grafeq", 253 | "gqs": "application/vnd.grafeq", 254 | "gac": "application/vnd.groove-account", 255 | "ghf": "application/vnd.groove-help", 256 | "gim": "application/vnd.groove-identity-message", 257 | "grv": "application/vnd.groove-injector", 258 | "gtm": "application/vnd.groove-tool-message", 259 | "tpl": "application/vnd.groove-tool-template", 260 | "vcg": "application/vnd.groove-vcard", 261 | "hal": "application/vnd.hal+xml", 262 | "zmm": "application/vnd.handheld-entertainment+xml", 263 | "hbci": "application/vnd.hbci", 264 | "les": "application/vnd.hhe.lesson-player", 265 | "hpgl": "application/vnd.hp-hpgl", 266 | "hpid": "application/vnd.hp-hpid", 267 | "hps": "application/vnd.hp-hps", 268 | "jlt": "application/vnd.hp-jlyt", 269 | "pcl": "application/vnd.hp-pcl", 270 | "pclxl": "application/vnd.hp-pclxl", 271 | "sfd-hdstx": "application/vnd.hydrostatix.sof-data", 272 | "x3d": "application/vnd.hzn-3d-crossword", 273 | "mpy": "application/vnd.ibm.minipay", 274 | "afp": "application/vnd.ibm.modcap", 275 | "listafp": "application/vnd.ibm.modcap", 276 | "list3820": "application/vnd.ibm.modcap", 277 | "irm": "application/vnd.ibm.rights-management", 278 | "sc": "application/vnd.ibm.secure-container", 279 | "icc": "application/vnd.iccprofile", 280 | "icm": "application/vnd.iccprofile", 281 | "igl": "application/vnd.igloader", 282 | "ivp": "application/vnd.immervision-ivp", 283 | "ivu": "application/vnd.immervision-ivu", 284 | "igm": "application/vnd.insors.igm", 285 | "xpw": "application/vnd.intercon.formnet", 286 | "xpx": "application/vnd.intercon.formnet", 287 | "i2g": "application/vnd.intergeo", 288 | "qbo": "application/vnd.intu.qbo", 289 | "qfx": "application/vnd.intu.qfx", 290 | "rcprofile": "application/vnd.ipunplugged.rcprofile", 291 | "irp": "application/vnd.irepository.package+xml", 292 | "xpr": "application/vnd.is-xpr", 293 | "fcs": "application/vnd.isac.fcs", 294 | "jam": "application/vnd.jam", 295 | "rms": "application/vnd.jcp.javame.midlet-rms", 296 | "jisp": "application/vnd.jisp", 297 | "joda": "application/vnd.joost.joda-archive", 298 | "ktz": "application/vnd.kahootz", 299 | "ktr": "application/vnd.kahootz", 300 | "karbon": "application/vnd.kde.karbon", 301 | "chrt": "application/vnd.kde.kchart", 302 | "kfo": "application/vnd.kde.kformula", 303 | "flw": "application/vnd.kde.kivio", 304 | "kon": "application/vnd.kde.kontour", 305 | "kpr": "application/vnd.kde.kpresenter", 306 | "kpt": "application/vnd.kde.kpresenter", 307 | "ksp": "application/vnd.kde.kspread", 308 | "kwd": "application/vnd.kde.kword", 309 | "kwt": "application/vnd.kde.kword", 310 | "htke": "application/vnd.kenameaapp", 311 | "kia": "application/vnd.kidspiration", 312 | "kne": "application/vnd.kinar", 313 | "knp": "application/vnd.kinar", 314 | "skp": "application/vnd.koan", 315 | "skd": "application/vnd.koan", 316 | "skt": "application/vnd.koan", 317 | "skm": "application/vnd.koan", 318 | "sse": "application/vnd.kodak-descriptor", 319 | "lasxml": "application/vnd.las.las+xml", 320 | "lbd": "application/vnd.llamagraphics.life-balance.desktop", 321 | "lbe": "application/vnd.llamagraphics.life-balance.exchange+xml", 322 | "apr": "application/vnd.lotus-approach", 323 | "pre": "application/vnd.lotus-freelance", 324 | "nsf": "application/vnd.lotus-notes", 325 | "org": "application/vnd.lotus-organizer", 326 | "scm": "application/vnd.lotus-screencam", 327 | "lwp": "application/vnd.lotus-wordpro", 328 | "portpkg": "application/vnd.macports.portpkg", 329 | "mcd": "application/vnd.mcd", 330 | "mc1": "application/vnd.medcalcdata", 331 | "cdkey": "application/vnd.mediastation.cdkey", 332 | "mwf": "application/vnd.mfer", 333 | "mfm": "application/vnd.mfmp", 334 | "flo": "application/vnd.micrografx.flo", 335 | "igx": "application/vnd.micrografx.igx", 336 | "mif": "application/vnd.mif", 337 | "daf": "application/vnd.mobius.daf", 338 | "dis": "application/vnd.mobius.dis", 339 | "mbk": "application/vnd.mobius.mbk", 340 | "mqy": "application/vnd.mobius.mqy", 341 | "msl": "application/vnd.mobius.msl", 342 | "plc": "application/vnd.mobius.plc", 343 | "txf": "application/vnd.mobius.txf", 344 | "mpn": "application/vnd.mophun.application", 345 | "mpc": "application/vnd.mophun.certificate", 346 | "xul": "application/vnd.mozilla.xul+xml", 347 | "cil": "application/vnd.ms-artgalry", 348 | "cab": "application/vnd.ms-cab-compressed", 349 | "xls": "application/vnd.ms-excel", 350 | "xlm": "application/vnd.ms-excel", 351 | "xla": "application/vnd.ms-excel", 352 | "xlc": "application/vnd.ms-excel", 353 | "xlt": "application/vnd.ms-excel", 354 | "xlw": "application/vnd.ms-excel", 355 | "xlam": "application/vnd.ms-excel.addin.macroenabled.12", 356 | "xlsb": "application/vnd.ms-excel.sheet.binary.macroenabled.12", 357 | "xlsm": "application/vnd.ms-excel.sheet.macroenabled.12", 358 | "xltm": "application/vnd.ms-excel.template.macroenabled.12", 359 | "eot": "application/vnd.ms-fontobject", 360 | "chm": "application/vnd.ms-htmlhelp", 361 | "ims": "application/vnd.ms-ims", 362 | "lrm": "application/vnd.ms-lrm", 363 | "thmx": "application/vnd.ms-officetheme", 364 | "cat": "application/vnd.ms-pki.seccat", 365 | "stl": "application/vnd.ms-pki.stl", 366 | "ppt": "application/vnd.ms-powerpoint", 367 | "pps": "application/vnd.ms-powerpoint", 368 | "pot": "application/vnd.ms-powerpoint", 369 | "ppam": "application/vnd.ms-powerpoint.addin.macroenabled.12", 370 | "pptm": "application/vnd.ms-powerpoint.presentation.macroenabled.12", 371 | "sldm": "application/vnd.ms-powerpoint.slide.macroenabled.12", 372 | "ppsm": "application/vnd.ms-powerpoint.slideshow.macroenabled.12", 373 | "potm": "application/vnd.ms-powerpoint.template.macroenabled.12", 374 | "mpp": "application/vnd.ms-project", 375 | "mpt": "application/vnd.ms-project", 376 | "docm": "application/vnd.ms-word.document.macroenabled.12", 377 | "dotm": "application/vnd.ms-word.template.macroenabled.12", 378 | "wps": "application/vnd.ms-works", 379 | "wks": "application/vnd.ms-works", 380 | "wcm": "application/vnd.ms-works", 381 | "wdb": "application/vnd.ms-works", 382 | "wpl": "application/vnd.ms-wpl", 383 | "xps": "application/vnd.ms-xpsdocument", 384 | "mseq": "application/vnd.mseq", 385 | "mus": "application/vnd.musician", 386 | "msty": "application/vnd.muvee.style", 387 | "taglet": "application/vnd.mynfc", 388 | "nlu": "application/vnd.neurolanguage.nlu", 389 | "nnd": "application/vnd.noblenet-directory", 390 | "nns": "application/vnd.noblenet-sealer", 391 | "nnw": "application/vnd.noblenet-web", 392 | "ngdat": "application/vnd.nokia.n-gage.data", 393 | "n-gage": "application/vnd.nokia.n-gage.symbian.install", 394 | "rpst": "application/vnd.nokia.radio-preset", 395 | "rpss": "application/vnd.nokia.radio-presets", 396 | "edm": "application/vnd.novadigm.edm", 397 | "edx": "application/vnd.novadigm.edx", 398 | "ext": "application/vnd.novadigm.ext", 399 | "odc": "application/vnd.oasis.opendocument.chart", 400 | "otc": "application/vnd.oasis.opendocument.chart-template", 401 | "odb": "application/vnd.oasis.opendocument.database", 402 | "odf": "application/vnd.oasis.opendocument.formula", 403 | "odft": "application/vnd.oasis.opendocument.formula-template", 404 | "odg": "application/vnd.oasis.opendocument.graphics", 405 | "otg": "application/vnd.oasis.opendocument.graphics-template", 406 | "odi": "application/vnd.oasis.opendocument.image", 407 | "oti": "application/vnd.oasis.opendocument.image-template", 408 | "odp": "application/vnd.oasis.opendocument.presentation", 409 | "otp": "application/vnd.oasis.opendocument.presentation-template", 410 | "ods": "application/vnd.oasis.opendocument.spreadsheet", 411 | "ots": "application/vnd.oasis.opendocument.spreadsheet-template", 412 | "odt": "application/vnd.oasis.opendocument.text", 413 | "odm": "application/vnd.oasis.opendocument.text-master", 414 | "ott": "application/vnd.oasis.opendocument.text-template", 415 | "oth": "application/vnd.oasis.opendocument.text-web", 416 | "xo": "application/vnd.olpc-sugar", 417 | "dd2": "application/vnd.oma.dd2+xml", 418 | "oxt": "application/vnd.openofficeorg.extension", 419 | "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", 420 | "sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide", 421 | "ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow", 422 | "potx": "application/vnd.openxmlformats-officedocument.presentationml.template", 423 | "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 424 | "xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template", 425 | "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 426 | "dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template", 427 | "mgp": "application/vnd.osgeo.mapguide.package", 428 | "dp": "application/vnd.osgi.dp", 429 | "pdb": "application/vnd.palm", 430 | "pqa": "application/vnd.palm", 431 | "oprc": "application/vnd.palm", 432 | "paw": "application/vnd.pawaafile", 433 | "str": "application/vnd.pg.format", 434 | "ei6": "application/vnd.pg.osasli", 435 | "efif": "application/vnd.picsel", 436 | "wg": "application/vnd.pmi.widget", 437 | "plf": "application/vnd.pocketlearn", 438 | "pbd": "application/vnd.powerbuilder6", 439 | "box": "application/vnd.previewsystems.box", 440 | "mgz": "application/vnd.proteus.magazine", 441 | "qps": "application/vnd.publishare-delta-tree", 442 | "ptid": "application/vnd.pvi.ptid1", 443 | "qxd": "application/vnd.quark.quarkxpress", 444 | "qxt": "application/vnd.quark.quarkxpress", 445 | "qwd": "application/vnd.quark.quarkxpress", 446 | "qwt": "application/vnd.quark.quarkxpress", 447 | "qxl": "application/vnd.quark.quarkxpress", 448 | "qxb": "application/vnd.quark.quarkxpress", 449 | "bed": "application/vnd.realvnc.bed", 450 | "mxl": "application/vnd.recordare.musicxml", 451 | "musicxml": "application/vnd.recordare.musicxml+xml", 452 | "cryptonote": "application/vnd.rig.cryptonote", 453 | "cod": "application/vnd.rim.cod", 454 | "rm": "application/vnd.rn-realmedia", 455 | "link66": "application/vnd.route66.link66+xml", 456 | "st": "application/vnd.sailingtracker.track", 457 | "see": "application/vnd.seemail", 458 | "sema": "application/vnd.sema", 459 | "semd": "application/vnd.semd", 460 | "semf": "application/vnd.semf", 461 | "ifm": "application/vnd.shana.informed.formdata", 462 | "itp": "application/vnd.shana.informed.formtemplate", 463 | "iif": "application/vnd.shana.informed.interchange", 464 | "ipk": "application/vnd.shana.informed.package", 465 | "twd": "application/vnd.simtech-mindmapper", 466 | "twds": "application/vnd.simtech-mindmapper", 467 | "mmf": "application/vnd.smaf", 468 | "teacher": "application/vnd.smart.teacher", 469 | "sdkm": "application/vnd.solent.sdkm+xml", 470 | "sdkd": "application/vnd.solent.sdkm+xml", 471 | "dxp": "application/vnd.spotfire.dxp", 472 | "sfs": "application/vnd.spotfire.sfs", 473 | "sdc": "application/vnd.stardivision.calc", 474 | "sda": "application/vnd.stardivision.draw", 475 | "sdd": "application/vnd.stardivision.impress", 476 | "smf": "application/vnd.stardivision.math", 477 | "sdw": "application/vnd.stardivision.writer", 478 | "vor": "application/vnd.stardivision.writer", 479 | "sgl": "application/vnd.stardivision.writer-global", 480 | "smzip": "application/vnd.stepmania.package", 481 | "sm": "application/vnd.stepmania.stepchart", 482 | "sxc": "application/vnd.sun.xml.calc", 483 | "stc": "application/vnd.sun.xml.calc.template", 484 | "sxd": "application/vnd.sun.xml.draw", 485 | "std": "application/vnd.sun.xml.draw.template", 486 | "sxi": "application/vnd.sun.xml.impress", 487 | "sti": "application/vnd.sun.xml.impress.template", 488 | "sxm": "application/vnd.sun.xml.math", 489 | "sxw": "application/vnd.sun.xml.writer", 490 | "sxg": "application/vnd.sun.xml.writer.global", 491 | "stw": "application/vnd.sun.xml.writer.template", 492 | "sus": "application/vnd.sus-calendar", 493 | "susp": "application/vnd.sus-calendar", 494 | "svd": "application/vnd.svd", 495 | "sis": "application/vnd.symbian.install", 496 | "sisx": "application/vnd.symbian.install", 497 | "xsm": "application/vnd.syncml+xml", 498 | "bdm": "application/vnd.syncml.dm+wbxml", 499 | "xdm": "application/vnd.syncml.dm+xml", 500 | "tao": "application/vnd.tao.intent-module-archive", 501 | "pcap": "application/vnd.tcpdump.pcap", 502 | "cap": "application/vnd.tcpdump.pcap", 503 | "dmp": "application/vnd.tcpdump.pcap", 504 | "tmo": "application/vnd.tmobile-livetv", 505 | "tpt": "application/vnd.trid.tpt", 506 | "mxs": "application/vnd.triscape.mxs", 507 | "tra": "application/vnd.trueapp", 508 | "ufd": "application/vnd.ufdl", 509 | "ufdl": "application/vnd.ufdl", 510 | "utz": "application/vnd.uiq.theme", 511 | "umj": "application/vnd.umajin", 512 | "unityweb": "application/vnd.unity", 513 | "uoml": "application/vnd.uoml+xml", 514 | "vcx": "application/vnd.vcx", 515 | "vsd": "application/vnd.visio", 516 | "vst": "application/vnd.visio", 517 | "vss": "application/vnd.visio", 518 | "vsw": "application/vnd.visio", 519 | "vis": "application/vnd.visionary", 520 | "vsf": "application/vnd.vsf", 521 | "wbxml": "application/vnd.wap.wbxml", 522 | "wmlc": "application/vnd.wap.wmlc", 523 | "wmlsc": "application/vnd.wap.wmlscriptc", 524 | "wtb": "application/vnd.webturbo", 525 | "nbp": "application/vnd.wolfram.player", 526 | "wpd": "application/vnd.wordperfect", 527 | "wqd": "application/vnd.wqd", 528 | "stf": "application/vnd.wt.stf", 529 | "xar": "application/vnd.xara", 530 | "xfdl": "application/vnd.xfdl", 531 | "hvd": "application/vnd.yamaha.hv-dic", 532 | "hvs": "application/vnd.yamaha.hv-script", 533 | "hvp": "application/vnd.yamaha.hv-voice", 534 | "osf": "application/vnd.yamaha.openscoreformat", 535 | "osfpvg": "application/vnd.yamaha.openscoreformat.osfpvg+xml", 536 | "saf": "application/vnd.yamaha.smaf-audio", 537 | "spf": "application/vnd.yamaha.smaf-phrase", 538 | "cmp": "application/vnd.yellowriver-custom-menu", 539 | "zir": "application/vnd.zul", 540 | "zirz": "application/vnd.zul", 541 | "zaz": "application/vnd.zzazz.deck+xml", 542 | "vxml": "application/voicexml+xml", 543 | "wgt": "application/widget", 544 | "hlp": "application/winhlp", 545 | "wsdl": "application/wsdl+xml", 546 | "wspolicy": "application/wspolicy+xml", 547 | "7z": "application/x-7z-compressed", 548 | "abw": "application/x-abiword", 549 | "ace": "application/x-ace-compressed", 550 | "aab": "application/x-authorware-bin", 551 | "x32": "application/x-authorware-bin", 552 | "u32": "application/x-authorware-bin", 553 | "vox": "application/x-authorware-bin", 554 | "aam": "application/x-authorware-map", 555 | "aas": "application/x-authorware-seg", 556 | "bcpio": "application/x-bcpio", 557 | "torrent": "application/x-bittorrent", 558 | "bz": "application/x-bzip", 559 | "bz2": "application/x-bzip2", 560 | "boz": "application/x-bzip2", 561 | "vcd": "application/x-cdlink", 562 | "chat": "application/x-chat", 563 | "pgn": "application/x-chess-pgn", 564 | "cpio": "application/x-cpio", 565 | "csh": "application/x-csh", 566 | "deb": "application/x-debian-package", 567 | "udeb": "application/x-debian-package", 568 | "dir": "application/x-director", 569 | "dcr": "application/x-director", 570 | "dxr": "application/x-director", 571 | "cst": "application/x-director", 572 | "cct": "application/x-director", 573 | "cxt": "application/x-director", 574 | "w3d": "application/x-director", 575 | "fgd": "application/x-director", 576 | "swa": "application/x-director", 577 | "wad": "application/x-doom", 578 | "ncx": "application/x-dtbncx+xml", 579 | "dtb": "application/x-dtbook+xml", 580 | "res": "application/x-dtbresource+xml", 581 | "dvi": "application/x-dvi", 582 | "bdf": "application/x-font-bdf", 583 | "gsf": "application/x-font-ghostscript", 584 | "psf": "application/x-font-linux-psf", 585 | "otf": "application/x-font-otf", 586 | "pcf": "application/x-font-pcf", 587 | "snf": "application/x-font-snf", 588 | "ttf": "application/x-font-ttf", 589 | "ttc": "application/x-font-ttf", 590 | "pfa": "application/x-font-type1", 591 | "pfb": "application/x-font-type1", 592 | "pfm": "application/x-font-type1", 593 | "afm": "application/x-font-type1", 594 | "woff": "application/x-font-woff", 595 | "spl": "application/x-futuresplash", 596 | "gnumeric": "application/x-gnumeric", 597 | "gtar": "application/x-gtar", 598 | "hdf": "application/x-hdf", 599 | "jnlp": "application/x-java-jnlp-file", 600 | "latex": "application/x-latex", 601 | "prc": "application/x-mobipocket-ebook", 602 | "mobi": "application/x-mobipocket-ebook", 603 | "application": "application/x-ms-application", 604 | "wmd": "application/x-ms-wmd", 605 | "wmz": "application/x-ms-wmz", 606 | "xbap": "application/x-ms-xbap", 607 | "mdb": "application/x-msaccess", 608 | "obd": "application/x-msbinder", 609 | "crd": "application/x-mscardfile", 610 | "clp": "application/x-msclip", 611 | "exe": "application/x-msdownload", 612 | "dll": "application/x-msdownload", 613 | "com": "application/x-msdownload", 614 | "bat": "application/x-msdownload", 615 | "msi": "application/x-msdownload", 616 | "mvb": "application/x-msmediaview", 617 | "m13": "application/x-msmediaview", 618 | "m14": "application/x-msmediaview", 619 | "wmf": "application/x-msmetafile", 620 | "mny": "application/x-msmoney", 621 | "pub": "application/x-mspublisher", 622 | "scd": "application/x-msschedule", 623 | "trm": "application/x-msterminal", 624 | "wri": "application/x-mswrite", 625 | "nc": "application/x-netcdf", 626 | "cdf": "application/x-netcdf", 627 | "p12": "application/x-pkcs12", 628 | "pfx": "application/x-pkcs12", 629 | "p7b": "application/x-pkcs7-certificates", 630 | "spc": "application/x-pkcs7-certificates", 631 | "p7r": "application/x-pkcs7-certreqresp", 632 | "rar": "application/x-rar-compressed", 633 | "sh": "application/x-sh", 634 | "shar": "application/x-shar", 635 | "swf": "application/x-shockwave-flash", 636 | "xap": "application/x-silverlight-app", 637 | "sit": "application/x-stuffit", 638 | "sitx": "application/x-stuffitx", 639 | "sv4cpio": "application/x-sv4cpio", 640 | "sv4crc": "application/x-sv4crc", 641 | "tar": "application/x-tar", 642 | "tcl": "application/x-tcl", 643 | "tex": "application/x-tex", 644 | "tfm": "application/x-tex-tfm", 645 | "texinfo": "application/x-texinfo", 646 | "texi": "application/x-texinfo", 647 | "ustar": "application/x-ustar", 648 | "src": "application/x-wais-source", 649 | "der": "application/x-x509-ca-cert", 650 | "crt": "application/x-x509-ca-cert", 651 | "fig": "application/x-xfig", 652 | "xpi": "application/x-xpinstall", 653 | "xdf": "application/xcap-diff+xml", 654 | "xenc": "application/xenc+xml", 655 | "xhtml": "application/xhtml+xml", 656 | "xht": "application/xhtml+xml", 657 | "xml": "application/xml", 658 | "xsl": "application/xml", 659 | "dtd": "application/xml-dtd", 660 | "xop": "application/xop+xml", 661 | "xslt": "application/xslt+xml", 662 | "xspf": "application/xspf+xml", 663 | "mxml": "application/xv+xml", 664 | "xhvml": "application/xv+xml", 665 | "xvml": "application/xv+xml", 666 | "xvm": "application/xv+xml", 667 | "yang": "application/yang", 668 | "yin": "application/yin+xml", 669 | "zip": "application/zip", 670 | "adp": "audio/adpcm", 671 | "au": "audio/basic", 672 | "snd": "audio/basic", 673 | "mid": "audio/midi", 674 | "midi": "audio/midi", 675 | "kar": "audio/midi", 676 | "rmi": "audio/midi", 677 | "mp4a": "audio/mp4", 678 | "mpga": "audio/mpeg", 679 | "mp2": "audio/mpeg", 680 | "mp2a": "audio/mpeg", 681 | "mp3": "audio/mpeg", 682 | "m2a": "audio/mpeg", 683 | "m3a": "audio/mpeg", 684 | "oga": "audio/ogg", 685 | "ogg": "audio/ogg", 686 | "spx": "audio/ogg", 687 | "uva": "audio/vnd.dece.audio", 688 | "uvva": "audio/vnd.dece.audio", 689 | "eol": "audio/vnd.digital-winds", 690 | "dra": "audio/vnd.dra", 691 | "dts": "audio/vnd.dts", 692 | "dtshd": "audio/vnd.dts.hd", 693 | "lvp": "audio/vnd.lucent.voice", 694 | "pya": "audio/vnd.ms-playready.media.pya", 695 | "ecelp4800": "audio/vnd.nuera.ecelp4800", 696 | "ecelp7470": "audio/vnd.nuera.ecelp7470", 697 | "ecelp9600": "audio/vnd.nuera.ecelp9600", 698 | "rip": "audio/vnd.rip", 699 | "weba": "audio/webm", 700 | "aac": "audio/x-aac", 701 | "aif": "audio/x-aiff", 702 | "aiff": "audio/x-aiff", 703 | "aifc": "audio/x-aiff", 704 | "m3u": "audio/x-mpegurl", 705 | "wax": "audio/x-ms-wax", 706 | "wma": "audio/x-ms-wma", 707 | "ram": "audio/x-pn-realaudio", 708 | "ra": "audio/x-pn-realaudio", 709 | "rmp": "audio/x-pn-realaudio-plugin", 710 | "wav": "audio/x-wav", 711 | "cdx": "chemical/x-cdx", 712 | "cif": "chemical/x-cif", 713 | "cmdf": "chemical/x-cmdf", 714 | "cml": "chemical/x-cml", 715 | "csml": "chemical/x-csml", 716 | "xyz": "chemical/x-xyz", 717 | "bmp": "image/bmp", 718 | "cgm": "image/cgm", 719 | "g3": "image/g3fax", 720 | "gif": "image/gif", 721 | "ief": "image/ief", 722 | "jpeg": "image/jpeg", 723 | "jpg": "image/jpeg", 724 | "jpe": "image/jpeg", 725 | "ktx": "image/ktx", 726 | "png": "image/png", 727 | "btif": "image/prs.btif", 728 | "svg": "image/svg+xml", 729 | "svgz": "image/svg+xml", 730 | "tiff": "image/tiff", 731 | "tif": "image/tiff", 732 | "psd": "image/vnd.adobe.photoshop", 733 | "uvi": "image/vnd.dece.graphic", 734 | "uvvi": "image/vnd.dece.graphic", 735 | "uvg": "image/vnd.dece.graphic", 736 | "uvvg": "image/vnd.dece.graphic", 737 | "sub": "text/vnd.dvb.subtitle", 738 | "djvu": "image/vnd.djvu", 739 | "djv": "image/vnd.djvu", 740 | "dwg": "image/vnd.dwg", 741 | "dxf": "image/vnd.dxf", 742 | "fbs": "image/vnd.fastbidsheet", 743 | "fpx": "image/vnd.fpx", 744 | "fst": "image/vnd.fst", 745 | "mmr": "image/vnd.fujixerox.edmics-mmr", 746 | "rlc": "image/vnd.fujixerox.edmics-rlc", 747 | "mdi": "image/vnd.ms-modi", 748 | "npx": "image/vnd.net-fpx", 749 | "wbmp": "image/vnd.wap.wbmp", 750 | "xif": "image/vnd.xiff", 751 | "webp": "image/webp", 752 | "ras": "image/x-cmu-raster", 753 | "cmx": "image/x-cmx", 754 | "fh": "image/x-freehand", 755 | "fhc": "image/x-freehand", 756 | "fh4": "image/x-freehand", 757 | "fh5": "image/x-freehand", 758 | "fh7": "image/x-freehand", 759 | "ico": "image/x-icon", 760 | "pcx": "image/x-pcx", 761 | "pic": "image/x-pict", 762 | "pct": "image/x-pict", 763 | "pnm": "image/x-portable-anymap", 764 | "pbm": "image/x-portable-bitmap", 765 | "pgm": "image/x-portable-graymap", 766 | "ppm": "image/x-portable-pixmap", 767 | "rgb": "image/x-rgb", 768 | "xbm": "image/x-xbitmap", 769 | "xpm": "image/x-xpixmap", 770 | "xwd": "image/x-xwindowdump", 771 | "eml": "message/rfc822", 772 | "mime": "message/rfc822", 773 | "igs": "model/iges", 774 | "iges": "model/iges", 775 | "msh": "model/mesh", 776 | "mesh": "model/mesh", 777 | "silo": "model/mesh", 778 | "dae": "model/vnd.collada+xml", 779 | "dwf": "model/vnd.dwf", 780 | "gdl": "model/vnd.gdl", 781 | "gtw": "model/vnd.gtw", 782 | "mts": "model/vnd.mts", 783 | "vtu": "model/vnd.vtu", 784 | "wrl": "model/vrml", 785 | "vrml": "model/vrml", 786 | "ics": "text/calendar", 787 | "ifb": "text/calendar", 788 | "css": "text/css", 789 | "csv": "text/csv", 790 | "html": "text/html", 791 | "htm": "text/html", 792 | "n3": "text/n3", 793 | "txt": "text/plain", 794 | "text": "text/plain", 795 | "conf": "text/plain", 796 | "def": "text/plain", 797 | "list": "text/plain", 798 | "log": "text/plain", 799 | "in": "text/plain", 800 | "dsc": "text/prs.lines.tag", 801 | "rtx": "text/richtext", 802 | "sgml": "text/sgml", 803 | "sgm": "text/sgml", 804 | "tsv": "text/tab-separated-values", 805 | "t": "text/troff", 806 | "tr": "text/troff", 807 | "roff": "text/troff", 808 | "man": "text/troff", 809 | "me": "text/troff", 810 | "ms": "text/troff", 811 | "ttl": "text/turtle", 812 | "uri": "text/uri-list", 813 | "uris": "text/uri-list", 814 | "urls": "text/uri-list", 815 | "vcard": "text/vcard", 816 | "curl": "text/vnd.curl", 817 | "dcurl": "text/vnd.curl.dcurl", 818 | "scurl": "text/vnd.curl.scurl", 819 | "mcurl": "text/vnd.curl.mcurl", 820 | "fly": "text/vnd.fly", 821 | "flx": "text/vnd.fmi.flexstor", 822 | "gv": "text/vnd.graphviz", 823 | "3dml": "text/vnd.in3d.3dml", 824 | "spot": "text/vnd.in3d.spot", 825 | "jad": "text/vnd.sun.j2me.app-descriptor", 826 | "wml": "text/vnd.wap.wml", 827 | "wmls": "text/vnd.wap.wmlscript", 828 | "s": "text/x-asm", 829 | "asm": "text/x-asm", 830 | "c": "text/x-c", 831 | "cc": "text/x-c", 832 | "cxx": "text/x-c", 833 | "cpp": "text/x-c", 834 | "h": "text/x-c", 835 | "hh": "text/x-c", 836 | "dic": "text/x-c", 837 | "f": "text/x-fortran", 838 | "for": "text/x-fortran", 839 | "f77": "text/x-fortran", 840 | "f90": "text/x-fortran", 841 | "p": "text/x-pascal", 842 | "pas": "text/x-pascal", 843 | "java": "text/x-java-source", 844 | "etx": "text/x-setext", 845 | "uu": "text/x-uuencode", 846 | "vcs": "text/x-vcalendar", 847 | "vcf": "text/x-vcard", 848 | "3gp": "video/3gpp", 849 | "3g2": "video/3gpp2", 850 | "h261": "video/h261", 851 | "h263": "video/h263", 852 | "h264": "video/h264", 853 | "jpgv": "video/jpeg", 854 | "jpm": "video/jpm", 855 | "jpgm": "video/jpm", 856 | "mj2": "video/mj2", 857 | "mjp2": "video/mj2", 858 | "mp4": "video/mp4", 859 | "mp4v": "video/mp4", 860 | "mpg4": "video/mp4", 861 | "mpeg": "video/mpeg", 862 | "mpg": "video/mpeg", 863 | "mpe": "video/mpeg", 864 | "m1v": "video/mpeg", 865 | "m2v": "video/mpeg", 866 | "ogv": "video/ogg", 867 | "qt": "video/quicktime", 868 | "mov": "video/quicktime", 869 | "uvh": "video/vnd.dece.hd", 870 | "uvvh": "video/vnd.dece.hd", 871 | "uvm": "video/vnd.dece.mobile", 872 | "uvvm": "video/vnd.dece.mobile", 873 | "uvp": "video/vnd.dece.pd", 874 | "uvvp": "video/vnd.dece.pd", 875 | "uvs": "video/vnd.dece.sd", 876 | "uvvs": "video/vnd.dece.sd", 877 | "uvv": "video/vnd.dece.video", 878 | "uvvv": "video/vnd.dece.video", 879 | "dvb": "video/vnd.dvb.file", 880 | "fvt": "video/vnd.fvt", 881 | "mxu": "video/vnd.mpegurl", 882 | "m4u": "video/vnd.mpegurl", 883 | "pyv": "video/vnd.ms-playready.media.pyv", 884 | "uvu": "video/vnd.uvvu.mp4", 885 | "uvvu": "video/vnd.uvvu.mp4", 886 | "viv": "video/vnd.vivo", 887 | "webm": "video/webm", 888 | "f4v": "video/x-f4v", 889 | "fli": "video/x-fli", 890 | "flv": "video/x-flv", 891 | "m4v": "video/x-m4v", 892 | "asf": "video/x-ms-asf", 893 | "asx": "video/x-ms-asf", 894 | "wm": "video/x-ms-wm", 895 | "wmv": "video/x-ms-wmv", 896 | "wmx": "video/x-ms-wmx", 897 | "wvx": "video/x-ms-wvx", 898 | "avi": "video/x-msvideo", 899 | "movie": "video/x-sgi-movie", 900 | "ice": "x-conference/x-cooltalk" 901 | }; -------------------------------------------------------------------------------- /websvr/test/lib/mime.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | fs = require('fs'); 3 | 4 | var mime = module.exports = { 5 | // Map of extension to mime type 6 | types: Object.create(null), 7 | 8 | // Map of mime type to extension 9 | extensions :Object.create(null), 10 | 11 | /** 12 | * Define mimetype -> extension mappings. Each key is a mime-type that maps 13 | * to an array of extensions associated with the type. The first extension is 14 | * used as the default extension for the type. 15 | * 16 | * e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']}); 17 | * 18 | * @param map (Object) type definitions 19 | */ 20 | define: function(map) { 21 | for (var type in map) { 22 | var exts = map[type]; 23 | 24 | for (var i = 0; i < exts.length; i++) { 25 | mime.types[exts[i]] = type; 26 | } 27 | 28 | // Default extension is the first one we encounter 29 | if (!mime.extensions[type]) { 30 | mime.extensions[type] = exts[0]; 31 | } 32 | } 33 | }, 34 | 35 | /** 36 | * Load an Apache2-style ".types" file 37 | * 38 | * This may be called multiple times (it's expected). Where files declare 39 | * overlapping types/extensions, the last file wins. 40 | * 41 | * @param file (String) path of file to load. 42 | */ 43 | load: function(file) { 44 | // Read file and split into lines 45 | var map = {}, 46 | content = fs.readFileSync(file, 'ascii'), 47 | lines = content.split(/[\r\n]+/); 48 | 49 | lines.forEach(function(line, lineno) { 50 | // Clean up whitespace/comments, and split into fields 51 | var fields = line.replace(/\s*#.*|^\s*|\s*$/g, '').split(/\s+/); 52 | map[fields.shift()] = fields; 53 | }); 54 | 55 | mime.define(map); 56 | }, 57 | 58 | /** 59 | * Lookup a mime type based on extension 60 | */ 61 | lookup: function(path, fallback) { 62 | var ext = path.replace(/.*[\.\/]/, '').toLowerCase(); 63 | 64 | return mime.types[ext] || fallback || mime.default_type 65 | }, 66 | 67 | /** 68 | * Return file extension associated with a mime type 69 | */ 70 | extension: function(mimeType) { 71 | return mime.extensions[mimeType]; 72 | }, 73 | 74 | /** 75 | * Lookup a charset based on mime type. 76 | */ 77 | charsets: { 78 | lookup: function (mimeType, fallback) { 79 | // Assume text types are utf8. Modify mime logic as needed. 80 | return (/^text\//).test(mimeType) ? 'UTF-8' : fallback; 81 | } 82 | } 83 | }; 84 | 85 | // Load our local copy of 86 | // http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types 87 | mime.load(path.join(__dirname, 'types/mime.types')); 88 | 89 | // Overlay enhancements submitted by the node.js community 90 | mime.load(path.join(__dirname, 'types/node.types')); 91 | 92 | // Set the default type 93 | mime.default_type = mime.types.bin; 94 | -------------------------------------------------------------------------------- /websvr/test/lib/test.js: -------------------------------------------------------------------------------- 1 | //Test method 2 | var os = require("os"); 3 | 4 | var test = module.exports = function(msg, func, repeat) { 5 | 6 | var t1 = new Date(), 7 | m1 = os.freemem(); 8 | 9 | repeat = repeat || 1000000; 10 | 11 | for (var i = 0; i < repeat; i++) { 12 | func(); 13 | } 14 | 15 | var t2 = new Date(), 16 | m2 = os.freemem(); 17 | 18 | console.log("\r\n" + msg); 19 | console.log(" time used:", t2 - t1); 20 | console.log(" memo used:", m1 - m2); 21 | 22 | }; -------------------------------------------------------------------------------- /websvr/test/lib/types.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Pickup mime type from mime.types files (node-mime); 4 | */ 5 | var fs = require("fs"), 6 | types = "./types/mime.types"; 7 | 8 | //remove the space/tab/comments 9 | var readLines = function(trunk) { 10 | var lines = []; 11 | 12 | //format the EOL, keep the same with (win & linux) 13 | var body = trunk.toString().replace('\r', ''); 14 | 15 | //remove comments: /* comthing */ 16 | //body = body.replace(/\/\*.*\*\//g, ''); 17 | body.split('\n').forEach(function(line) { 18 | lines.push(line.trim()); 19 | }); 20 | 21 | return lines; 22 | }; 23 | 24 | var pickup = function(lines) { 25 | 26 | var result = {}; 27 | 28 | lines.forEach(function(line, idx) { 29 | // 30 | if (line[0] != '#') { 31 | 32 | //pickup values in attribute 33 | var pair = line.split(/[ \t]+/), 34 | l = pair.length; 35 | 36 | if (l > 1 && pair[0]) { 37 | for (var i = 1; i < l; i++) { 38 | pair[i] && ( result[pair[i]] = pair[0] ); 39 | } 40 | }; 41 | } 42 | }); 43 | 44 | fs.writeFileSync("MIMETYPES.js", "var MIMETYPES = module.exports = " + JSON.stringify(result, null, ' ') + ';'); 45 | }; 46 | 47 | fs.readFile(types, function(err, trunk) { 48 | if (err) throw err; 49 | 50 | var lines = readLines(trunk); 51 | pickup(lines); 52 | }); -------------------------------------------------------------------------------- /websvr/test/lib/types/node.types: -------------------------------------------------------------------------------- 1 | # What: Google Chrome Extension 2 | # Why: To allow apps to (work) be served with the right content type header. 3 | # http://codereview.chromium.org/2830017 4 | # Added by: niftylettuce 5 | application/x-chrome-extension crx 6 | 7 | # What: OTF Message Silencer 8 | # Why: To silence the "Resource interpreted as font but transferred with MIME 9 | # type font/otf" message that occurs in Google Chrome 10 | # Added by: niftylettuce 11 | font/opentype otf 12 | 13 | # What: HTC support 14 | # Why: To properly render .htc files such as CSS3PIE 15 | # Added by: niftylettuce 16 | text/x-component htc 17 | 18 | # What: HTML5 application cache manifest 19 | # Why: De-facto standard. Required by Mozilla browser when serving HTML5 apps 20 | # per https://developer.mozilla.org/en/offline_resources_in_firefox 21 | # Added by: louisremi 22 | text/cache-manifest appcache manifest 23 | 24 | # What: node binary buffer format 25 | # Why: semi-standard extension w/in the node community 26 | # Added by: tootallnate 27 | application/octet-stream buffer 28 | 29 | # What: The "protected" MP-4 formats used by iTunes. 30 | # Why: Required for streaming music to browsers (?) 31 | # Added by: broofa 32 | application/mp4 m4p 33 | audio/mp4 m4a 34 | 35 | # What: Music playlist format (http://en.wikipedia.org/wiki/M3U) 36 | # Why: See https://github.com/bentomas/node-mime/pull/6 37 | # Added by: mjrusso 38 | application/x-mpegURL m3u8 39 | 40 | # What: Video format, Part of RFC1890 41 | # Why: See https://github.com/bentomas/node-mime/pull/6 42 | # Added by: mjrusso 43 | video/MP2T ts 44 | 45 | # What: The FLAC lossless codec format 46 | # Why: Streaming and serving FLAC audio 47 | # Added by: jacobrask 48 | audio/flac flac 49 | 50 | # What: EventSource mime type 51 | # Why: mime type of Server-Sent Events stream 52 | # http://www.w3.org/TR/eventsource/#text-event-stream 53 | # Added by: francois2metz 54 | text/event-stream event-stream 55 | -------------------------------------------------------------------------------- /websvr/test/lib/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.4.2 2 | // http://underscorejs.org 3 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 4 | // Underscore may be freely distributed under the MIT license. 5 | 6 | (function() { 7 | 8 | // Baseline setup 9 | // -------------- 10 | 11 | // Establish the root object, `window` in the browser, or `global` on the server. 12 | var root = this; 13 | 14 | // Save the previous value of the `_` variable. 15 | var previousUnderscore = root._; 16 | 17 | // Establish the object that gets returned to break out of a loop iteration. 18 | var breaker = {}; 19 | 20 | // Save bytes in the minified (but not gzipped) version: 21 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 22 | 23 | // Create quick reference variables for speed access to core prototypes. 24 | var push = ArrayProto.push, 25 | slice = ArrayProto.slice, 26 | concat = ArrayProto.concat, 27 | unshift = ArrayProto.unshift, 28 | toString = ObjProto.toString, 29 | hasOwnProperty = ObjProto.hasOwnProperty; 30 | 31 | // All **ECMAScript 5** native function implementations that we hope to use 32 | // are declared here. 33 | var 34 | nativeForEach = ArrayProto.forEach, 35 | nativeMap = ArrayProto.map, 36 | nativeReduce = ArrayProto.reduce, 37 | nativeReduceRight = ArrayProto.reduceRight, 38 | nativeFilter = ArrayProto.filter, 39 | nativeEvery = ArrayProto.every, 40 | nativeSome = ArrayProto.some, 41 | nativeIndexOf = ArrayProto.indexOf, 42 | nativeLastIndexOf = ArrayProto.lastIndexOf, 43 | nativeIsArray = Array.isArray, 44 | nativeKeys = Object.keys, 45 | nativeBind = FuncProto.bind; 46 | 47 | // Create a safe reference to the Underscore object for use below. 48 | var _ = function(obj) { 49 | if (obj instanceof _) return obj; 50 | if (!(this instanceof _)) return new _(obj); 51 | this._wrapped = obj; 52 | }; 53 | 54 | // Export the Underscore object for **Node.js**, with 55 | // backwards-compatibility for the old `require()` API. If we're in 56 | // the browser, add `_` as a global object via a string identifier, 57 | // for Closure Compiler "advanced" mode. 58 | if (typeof exports !== 'undefined') { 59 | if (typeof module !== 'undefined' && module.exports) { 60 | exports = module.exports = _; 61 | } 62 | exports._ = _; 63 | } else { 64 | root['_'] = _; 65 | } 66 | 67 | // Current version. 68 | _.VERSION = '1.4.2'; 69 | 70 | // Collection Functions 71 | // -------------------- 72 | 73 | // The cornerstone, an `each` implementation, aka `forEach`. 74 | // Handles objects with the built-in `forEach`, arrays, and raw objects. 75 | // Delegates to **ECMAScript 5**'s native `forEach` if available. 76 | var each = _.each = _.forEach = function(obj, iterator, context) { 77 | if (obj == null) return; 78 | if (nativeForEach && obj.forEach === nativeForEach) { 79 | obj.forEach(iterator, context); 80 | } else if (obj.length === +obj.length) { 81 | for (var i = 0, l = obj.length; i < l; i++) { 82 | if (iterator.call(context, obj[i], i, obj) === breaker) return; 83 | } 84 | } else { 85 | for (var key in obj) { 86 | if (_.has(obj, key)) { 87 | if (iterator.call(context, obj[key], key, obj) === breaker) return; 88 | } 89 | } 90 | } 91 | }; 92 | 93 | // Return the results of applying the iterator to each element. 94 | // Delegates to **ECMAScript 5**'s native `map` if available. 95 | _.map = _.collect = function(obj, iterator, context) { 96 | var results = []; 97 | if (obj == null) return results; 98 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 99 | each(obj, function(value, index, list) { 100 | results[results.length] = iterator.call(context, value, index, list); 101 | }); 102 | return results; 103 | }; 104 | 105 | // **Reduce** builds up a single result from a list of values, aka `inject`, 106 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 107 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 108 | var initial = arguments.length > 2; 109 | if (obj == null) obj = []; 110 | if (nativeReduce && obj.reduce === nativeReduce) { 111 | if (context) iterator = _.bind(iterator, context); 112 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 113 | } 114 | each(obj, function(value, index, list) { 115 | if (!initial) { 116 | memo = value; 117 | initial = true; 118 | } else { 119 | memo = iterator.call(context, memo, value, index, list); 120 | } 121 | }); 122 | if (!initial) throw new TypeError('Reduce of empty array with no initial value'); 123 | return memo; 124 | }; 125 | 126 | // The right-associative version of reduce, also known as `foldr`. 127 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 128 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 129 | var initial = arguments.length > 2; 130 | if (obj == null) obj = []; 131 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 132 | if (context) iterator = _.bind(iterator, context); 133 | return arguments.length > 2 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 134 | } 135 | var length = obj.length; 136 | if (length !== +length) { 137 | var keys = _.keys(obj); 138 | length = keys.length; 139 | } 140 | each(obj, function(value, index, list) { 141 | index = keys ? keys[--length] : --length; 142 | if (!initial) { 143 | memo = obj[index]; 144 | initial = true; 145 | } else { 146 | memo = iterator.call(context, memo, obj[index], index, list); 147 | } 148 | }); 149 | if (!initial) throw new TypeError('Reduce of empty array with no initial value'); 150 | return memo; 151 | }; 152 | 153 | // Return the first value which passes a truth test. Aliased as `detect`. 154 | _.find = _.detect = function(obj, iterator, context) { 155 | var result; 156 | any(obj, function(value, index, list) { 157 | if (iterator.call(context, value, index, list)) { 158 | result = value; 159 | return true; 160 | } 161 | }); 162 | return result; 163 | }; 164 | 165 | // Return all the elements that pass a truth test. 166 | // Delegates to **ECMAScript 5**'s native `filter` if available. 167 | // Aliased as `select`. 168 | _.filter = _.select = function(obj, iterator, context) { 169 | var results = []; 170 | if (obj == null) return results; 171 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 172 | each(obj, function(value, index, list) { 173 | if (iterator.call(context, value, index, list)) results[results.length] = value; 174 | }); 175 | return results; 176 | }; 177 | 178 | // Return all the elements for which a truth test fails. 179 | _.reject = function(obj, iterator, context) { 180 | var results = []; 181 | if (obj == null) return results; 182 | each(obj, function(value, index, list) { 183 | if (!iterator.call(context, value, index, list)) results[results.length] = value; 184 | }); 185 | return results; 186 | }; 187 | 188 | // Determine whether all of the elements match a truth test. 189 | // Delegates to **ECMAScript 5**'s native `every` if available. 190 | // Aliased as `all`. 191 | _.every = _.all = function(obj, iterator, context) { 192 | iterator || (iterator = _.identity); 193 | var result = true; 194 | if (obj == null) return result; 195 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 196 | each(obj, function(value, index, list) { 197 | if (!(result = result && iterator.call(context, value, index, list))) return breaker; 198 | }); 199 | return !!result; 200 | }; 201 | 202 | // Determine if at least one element in the object matches a truth test. 203 | // Delegates to **ECMAScript 5**'s native `some` if available. 204 | // Aliased as `any`. 205 | var any = _.some = _.any = function(obj, iterator, context) { 206 | iterator || (iterator = _.identity); 207 | var result = false; 208 | if (obj == null) return result; 209 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 210 | each(obj, function(value, index, list) { 211 | if (result || (result = iterator.call(context, value, index, list))) return breaker; 212 | }); 213 | return !!result; 214 | }; 215 | 216 | // Determine if the array or object contains a given value (using `===`). 217 | // Aliased as `include`. 218 | _.contains = _.include = function(obj, target) { 219 | var found = false; 220 | if (obj == null) return found; 221 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 222 | found = any(obj, function(value) { 223 | return value === target; 224 | }); 225 | return found; 226 | }; 227 | 228 | // Invoke a method (with arguments) on every item in a collection. 229 | _.invoke = function(obj, method) { 230 | var args = slice.call(arguments, 2); 231 | return _.map(obj, function(value) { 232 | return (_.isFunction(method) ? method : value[method]).apply(value, args); 233 | }); 234 | }; 235 | 236 | // Convenience version of a common use case of `map`: fetching a property. 237 | _.pluck = function(obj, key) { 238 | return _.map(obj, function(value){ return value[key]; }); 239 | }; 240 | 241 | // Convenience version of a common use case of `filter`: selecting only objects 242 | // with specific `key:value` pairs. 243 | _.where = function(obj, attrs) { 244 | if (_.isEmpty(attrs)) return []; 245 | return _.filter(obj, function(value) { 246 | for (var key in attrs) { 247 | if (attrs[key] !== value[key]) return false; 248 | } 249 | return true; 250 | }); 251 | }; 252 | 253 | // Return the maximum element or (element-based computation). 254 | // Can't optimize arrays of integers longer than 65,535 elements. 255 | // See: https://bugs.webkit.org/show_bug.cgi?id=80797 256 | _.max = function(obj, iterator, context) { 257 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 258 | return Math.max.apply(Math, obj); 259 | } 260 | if (!iterator && _.isEmpty(obj)) return -Infinity; 261 | var result = {computed : -Infinity}; 262 | each(obj, function(value, index, list) { 263 | var computed = iterator ? iterator.call(context, value, index, list) : value; 264 | computed >= result.computed && (result = {value : value, computed : computed}); 265 | }); 266 | return result.value; 267 | }; 268 | 269 | // Return the minimum element (or element-based computation). 270 | _.min = function(obj, iterator, context) { 271 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 272 | return Math.min.apply(Math, obj); 273 | } 274 | if (!iterator && _.isEmpty(obj)) return Infinity; 275 | var result = {computed : Infinity}; 276 | each(obj, function(value, index, list) { 277 | var computed = iterator ? iterator.call(context, value, index, list) : value; 278 | computed < result.computed && (result = {value : value, computed : computed}); 279 | }); 280 | return result.value; 281 | }; 282 | 283 | // Shuffle an array. 284 | _.shuffle = function(obj) { 285 | var rand; 286 | var index = 0; 287 | var shuffled = []; 288 | each(obj, function(value) { 289 | rand = _.random(index++); 290 | shuffled[index - 1] = shuffled[rand]; 291 | shuffled[rand] = value; 292 | }); 293 | return shuffled; 294 | }; 295 | 296 | // An internal function to generate lookup iterators. 297 | var lookupIterator = function(value) { 298 | return _.isFunction(value) ? value : function(obj){ return obj[value]; }; 299 | }; 300 | 301 | // Sort the object's values by a criterion produced by an iterator. 302 | _.sortBy = function(obj, value, context) { 303 | var iterator = lookupIterator(value); 304 | return _.pluck(_.map(obj, function(value, index, list) { 305 | return { 306 | value : value, 307 | index : index, 308 | criteria : iterator.call(context, value, index, list) 309 | }; 310 | }).sort(function(left, right) { 311 | var a = left.criteria; 312 | var b = right.criteria; 313 | if (a !== b) { 314 | if (a > b || a === void 0) return 1; 315 | if (a < b || b === void 0) return -1; 316 | } 317 | return left.index < right.index ? -1 : 1; 318 | }), 'value'); 319 | }; 320 | 321 | // An internal function used for aggregate "group by" operations. 322 | var group = function(obj, value, context, behavior) { 323 | var result = {}; 324 | var iterator = lookupIterator(value); 325 | each(obj, function(value, index) { 326 | var key = iterator.call(context, value, index, obj); 327 | behavior(result, key, value); 328 | }); 329 | return result; 330 | }; 331 | 332 | // Groups the object's values by a criterion. Pass either a string attribute 333 | // to group by, or a function that returns the criterion. 334 | _.groupBy = function(obj, value, context) { 335 | return group(obj, value, context, function(result, key, value) { 336 | (_.has(result, key) ? result[key] : (result[key] = [])).push(value); 337 | }); 338 | }; 339 | 340 | // Counts instances of an object that group by a certain criterion. Pass 341 | // either a string attribute to count by, or a function that returns the 342 | // criterion. 343 | _.countBy = function(obj, value, context) { 344 | return group(obj, value, context, function(result, key, value) { 345 | if (!_.has(result, key)) result[key] = 0; 346 | result[key]++; 347 | }); 348 | }; 349 | 350 | // Use a comparator function to figure out the smallest index at which 351 | // an object should be inserted so as to maintain order. Uses binary search. 352 | _.sortedIndex = function(array, obj, iterator, context) { 353 | iterator = iterator == null ? _.identity : lookupIterator(iterator); 354 | var value = iterator.call(context, obj); 355 | var low = 0, high = array.length; 356 | while (low < high) { 357 | var mid = (low + high) >>> 1; 358 | iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; 359 | } 360 | return low; 361 | }; 362 | 363 | // Safely convert anything iterable into a real, live array. 364 | _.toArray = function(obj) { 365 | if (!obj) return []; 366 | if (obj.length === +obj.length) return slice.call(obj); 367 | return _.values(obj); 368 | }; 369 | 370 | // Return the number of elements in an object. 371 | _.size = function(obj) { 372 | return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; 373 | }; 374 | 375 | // Array Functions 376 | // --------------- 377 | 378 | // Get the first element of an array. Passing **n** will return the first N 379 | // values in the array. Aliased as `head` and `take`. The **guard** check 380 | // allows it to work with `_.map`. 381 | _.first = _.head = _.take = function(array, n, guard) { 382 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; 383 | }; 384 | 385 | // Returns everything but the last entry of the array. Especially useful on 386 | // the arguments object. Passing **n** will return all the values in 387 | // the array, excluding the last N. The **guard** check allows it to work with 388 | // `_.map`. 389 | _.initial = function(array, n, guard) { 390 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 391 | }; 392 | 393 | // Get the last element of an array. Passing **n** will return the last N 394 | // values in the array. The **guard** check allows it to work with `_.map`. 395 | _.last = function(array, n, guard) { 396 | if ((n != null) && !guard) { 397 | return slice.call(array, Math.max(array.length - n, 0)); 398 | } else { 399 | return array[array.length - 1]; 400 | } 401 | }; 402 | 403 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. 404 | // Especially useful on the arguments object. Passing an **n** will return 405 | // the rest N values in the array. The **guard** 406 | // check allows it to work with `_.map`. 407 | _.rest = _.tail = _.drop = function(array, n, guard) { 408 | return slice.call(array, (n == null) || guard ? 1 : n); 409 | }; 410 | 411 | // Trim out all falsy values from an array. 412 | _.compact = function(array) { 413 | return _.filter(array, function(value){ return !!value; }); 414 | }; 415 | 416 | // Internal implementation of a recursive `flatten` function. 417 | var flatten = function(input, shallow, output) { 418 | each(input, function(value) { 419 | if (_.isArray(value)) { 420 | shallow ? push.apply(output, value) : flatten(value, shallow, output); 421 | } else { 422 | output.push(value); 423 | } 424 | }); 425 | return output; 426 | }; 427 | 428 | // Return a completely flattened version of an array. 429 | _.flatten = function(array, shallow) { 430 | return flatten(array, shallow, []); 431 | }; 432 | 433 | // Return a version of the array that does not contain the specified value(s). 434 | _.without = function(array) { 435 | return _.difference(array, slice.call(arguments, 1)); 436 | }; 437 | 438 | // Produce a duplicate-free version of the array. If the array has already 439 | // been sorted, you have the option of using a faster algorithm. 440 | // Aliased as `unique`. 441 | _.uniq = _.unique = function(array, isSorted, iterator, context) { 442 | var initial = iterator ? _.map(array, iterator, context) : array; 443 | var results = []; 444 | var seen = []; 445 | each(initial, function(value, index) { 446 | if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { 447 | seen.push(value); 448 | results.push(array[index]); 449 | } 450 | }); 451 | return results; 452 | }; 453 | 454 | // Produce an array that contains the union: each distinct element from all of 455 | // the passed-in arrays. 456 | _.union = function() { 457 | return _.uniq(concat.apply(ArrayProto, arguments)); 458 | }; 459 | 460 | // Produce an array that contains every item shared between all the 461 | // passed-in arrays. 462 | _.intersection = function(array) { 463 | var rest = slice.call(arguments, 1); 464 | return _.filter(_.uniq(array), function(item) { 465 | return _.every(rest, function(other) { 466 | return _.indexOf(other, item) >= 0; 467 | }); 468 | }); 469 | }; 470 | 471 | // Take the difference between one array and a number of other arrays. 472 | // Only the elements present in just the first array will remain. 473 | _.difference = function(array) { 474 | var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); 475 | return _.filter(array, function(value){ return !_.contains(rest, value); }); 476 | }; 477 | 478 | // Zip together multiple lists into a single array -- elements that share 479 | // an index go together. 480 | _.zip = function() { 481 | var args = slice.call(arguments); 482 | var length = _.max(_.pluck(args, 'length')); 483 | var results = new Array(length); 484 | for (var i = 0; i < length; i++) { 485 | results[i] = _.pluck(args, "" + i); 486 | } 487 | return results; 488 | }; 489 | 490 | // Converts lists into objects. Pass either a single array of `[key, value]` 491 | // pairs, or two parallel arrays of the same length -- one of keys, and one of 492 | // the corresponding values. 493 | _.object = function(list, values) { 494 | var result = {}; 495 | for (var i = 0, l = list.length; i < l; i++) { 496 | if (values) { 497 | result[list[i]] = values[i]; 498 | } else { 499 | result[list[i][0]] = list[i][1]; 500 | } 501 | } 502 | return result; 503 | }; 504 | 505 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 506 | // we need this function. Return the position of the first occurrence of an 507 | // item in an array, or -1 if the item is not included in the array. 508 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 509 | // If the array is large and already in sort order, pass `true` 510 | // for **isSorted** to use binary search. 511 | _.indexOf = function(array, item, isSorted) { 512 | if (array == null) return -1; 513 | var i = 0, l = array.length; 514 | if (isSorted) { 515 | if (typeof isSorted == 'number') { 516 | i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); 517 | } else { 518 | i = _.sortedIndex(array, item); 519 | return array[i] === item ? i : -1; 520 | } 521 | } 522 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); 523 | for (; i < l; i++) if (array[i] === item) return i; 524 | return -1; 525 | }; 526 | 527 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 528 | _.lastIndexOf = function(array, item, from) { 529 | if (array == null) return -1; 530 | var hasIndex = from != null; 531 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { 532 | return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); 533 | } 534 | var i = (hasIndex ? from : array.length); 535 | while (i--) if (array[i] === item) return i; 536 | return -1; 537 | }; 538 | 539 | // Generate an integer Array containing an arithmetic progression. A port of 540 | // the native Python `range()` function. See 541 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 542 | _.range = function(start, stop, step) { 543 | if (arguments.length <= 1) { 544 | stop = start || 0; 545 | start = 0; 546 | } 547 | step = arguments[2] || 1; 548 | 549 | var len = Math.max(Math.ceil((stop - start) / step), 0); 550 | var idx = 0; 551 | var range = new Array(len); 552 | 553 | while(idx < len) { 554 | range[idx++] = start; 555 | start += step; 556 | } 557 | 558 | return range; 559 | }; 560 | 561 | // Function (ahem) Functions 562 | // ------------------ 563 | 564 | // Reusable constructor function for prototype setting. 565 | var ctor = function(){}; 566 | 567 | // Create a function bound to a given object (assigning `this`, and arguments, 568 | // optionally). Binding with arguments is also known as `curry`. 569 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available. 570 | // We check for `func.bind` first, to fail fast when `func` is undefined. 571 | _.bind = function bind(func, context) { 572 | var bound, args; 573 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 574 | if (!_.isFunction(func)) throw new TypeError; 575 | args = slice.call(arguments, 2); 576 | return bound = function() { 577 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 578 | ctor.prototype = func.prototype; 579 | var self = new ctor; 580 | var result = func.apply(self, args.concat(slice.call(arguments))); 581 | if (Object(result) === result) return result; 582 | return self; 583 | }; 584 | }; 585 | 586 | // Bind all of an object's methods to that object. Useful for ensuring that 587 | // all callbacks defined on an object belong to it. 588 | _.bindAll = function(obj) { 589 | var funcs = slice.call(arguments, 1); 590 | if (funcs.length == 0) funcs = _.functions(obj); 591 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 592 | return obj; 593 | }; 594 | 595 | // Memoize an expensive function by storing its results. 596 | _.memoize = function(func, hasher) { 597 | var memo = {}; 598 | hasher || (hasher = _.identity); 599 | return function() { 600 | var key = hasher.apply(this, arguments); 601 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 602 | }; 603 | }; 604 | 605 | // Delays a function for the given number of milliseconds, and then calls 606 | // it with the arguments supplied. 607 | _.delay = function(func, wait) { 608 | var args = slice.call(arguments, 2); 609 | return setTimeout(function(){ return func.apply(null, args); }, wait); 610 | }; 611 | 612 | // Defers a function, scheduling it to run after the current call stack has 613 | // cleared. 614 | _.defer = function(func) { 615 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 616 | }; 617 | 618 | // Returns a function, that, when invoked, will only be triggered at most once 619 | // during a given window of time. 620 | _.throttle = function(func, wait) { 621 | var context, args, timeout, throttling, more, result; 622 | var whenDone = _.debounce(function(){ more = throttling = false; }, wait); 623 | return function() { 624 | context = this; args = arguments; 625 | var later = function() { 626 | timeout = null; 627 | if (more) { 628 | result = func.apply(context, args); 629 | } 630 | whenDone(); 631 | }; 632 | if (!timeout) timeout = setTimeout(later, wait); 633 | if (throttling) { 634 | more = true; 635 | } else { 636 | throttling = true; 637 | result = func.apply(context, args); 638 | } 639 | whenDone(); 640 | return result; 641 | }; 642 | }; 643 | 644 | // Returns a function, that, as long as it continues to be invoked, will not 645 | // be triggered. The function will be called after it stops being called for 646 | // N milliseconds. If `immediate` is passed, trigger the function on the 647 | // leading edge, instead of the trailing. 648 | _.debounce = function(func, wait, immediate) { 649 | var timeout, result; 650 | return function() { 651 | var context = this, args = arguments; 652 | var later = function() { 653 | timeout = null; 654 | if (!immediate) result = func.apply(context, args); 655 | }; 656 | var callNow = immediate && !timeout; 657 | clearTimeout(timeout); 658 | timeout = setTimeout(later, wait); 659 | if (callNow) result = func.apply(context, args); 660 | return result; 661 | }; 662 | }; 663 | 664 | // Returns a function that will be executed at most one time, no matter how 665 | // often you call it. Useful for lazy initialization. 666 | _.once = function(func) { 667 | var ran = false, memo; 668 | return function() { 669 | if (ran) return memo; 670 | ran = true; 671 | memo = func.apply(this, arguments); 672 | func = null; 673 | return memo; 674 | }; 675 | }; 676 | 677 | // Returns the first function passed as an argument to the second, 678 | // allowing you to adjust arguments, run code before and after, and 679 | // conditionally execute the original function. 680 | _.wrap = function(func, wrapper) { 681 | return function() { 682 | var args = [func]; 683 | push.apply(args, arguments); 684 | return wrapper.apply(this, args); 685 | }; 686 | }; 687 | 688 | // Returns a function that is the composition of a list of functions, each 689 | // consuming the return value of the function that follows. 690 | _.compose = function() { 691 | var funcs = arguments; 692 | return function() { 693 | var args = arguments; 694 | for (var i = funcs.length - 1; i >= 0; i--) { 695 | args = [funcs[i].apply(this, args)]; 696 | } 697 | return args[0]; 698 | }; 699 | }; 700 | 701 | // Returns a function that will only be executed after being called N times. 702 | _.after = function(times, func) { 703 | if (times <= 0) return func(); 704 | return function() { 705 | if (--times < 1) { 706 | return func.apply(this, arguments); 707 | } 708 | }; 709 | }; 710 | 711 | // Object Functions 712 | // ---------------- 713 | 714 | // Retrieve the names of an object's properties. 715 | // Delegates to **ECMAScript 5**'s native `Object.keys` 716 | _.keys = nativeKeys || function(obj) { 717 | if (obj !== Object(obj)) throw new TypeError('Invalid object'); 718 | var keys = []; 719 | for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; 720 | return keys; 721 | }; 722 | 723 | // Retrieve the values of an object's properties. 724 | _.values = function(obj) { 725 | var values = []; 726 | for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); 727 | return values; 728 | }; 729 | 730 | // Convert an object into a list of `[key, value]` pairs. 731 | _.pairs = function(obj) { 732 | var pairs = []; 733 | for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); 734 | return pairs; 735 | }; 736 | 737 | // Invert the keys and values of an object. The values must be serializable. 738 | _.invert = function(obj) { 739 | var result = {}; 740 | for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; 741 | return result; 742 | }; 743 | 744 | // Return a sorted list of the function names available on the object. 745 | // Aliased as `methods` 746 | _.functions = _.methods = function(obj) { 747 | var names = []; 748 | for (var key in obj) { 749 | if (_.isFunction(obj[key])) names.push(key); 750 | } 751 | return names.sort(); 752 | }; 753 | 754 | // Extend a given object with all the properties in passed-in object(s). 755 | _.extend = function(obj) { 756 | each(slice.call(arguments, 1), function(source) { 757 | for (var prop in source) { 758 | obj[prop] = source[prop]; 759 | } 760 | }); 761 | return obj; 762 | }; 763 | 764 | // Return a copy of the object only containing the whitelisted properties. 765 | _.pick = function(obj) { 766 | var copy = {}; 767 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 768 | each(keys, function(key) { 769 | if (key in obj) copy[key] = obj[key]; 770 | }); 771 | return copy; 772 | }; 773 | 774 | // Return a copy of the object without the blacklisted properties. 775 | _.omit = function(obj) { 776 | var copy = {}; 777 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 778 | for (var key in obj) { 779 | if (!_.contains(keys, key)) copy[key] = obj[key]; 780 | } 781 | return copy; 782 | }; 783 | 784 | // Fill in a given object with default properties. 785 | _.defaults = function(obj) { 786 | each(slice.call(arguments, 1), function(source) { 787 | for (var prop in source) { 788 | if (obj[prop] == null) obj[prop] = source[prop]; 789 | } 790 | }); 791 | return obj; 792 | }; 793 | 794 | // Create a (shallow-cloned) duplicate of an object. 795 | _.clone = function(obj) { 796 | if (!_.isObject(obj)) return obj; 797 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 798 | }; 799 | 800 | // Invokes interceptor with the obj, and then returns obj. 801 | // The primary purpose of this method is to "tap into" a method chain, in 802 | // order to perform operations on intermediate results within the chain. 803 | _.tap = function(obj, interceptor) { 804 | interceptor(obj); 805 | return obj; 806 | }; 807 | 808 | // Internal recursive comparison function for `isEqual`. 809 | var eq = function(a, b, aStack, bStack) { 810 | // Identical objects are equal. `0 === -0`, but they aren't identical. 811 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. 812 | if (a === b) return a !== 0 || 1 / a == 1 / b; 813 | // A strict comparison is necessary because `null == undefined`. 814 | if (a == null || b == null) return a === b; 815 | // Unwrap any wrapped objects. 816 | if (a instanceof _) a = a._wrapped; 817 | if (b instanceof _) b = b._wrapped; 818 | // Compare `[[Class]]` names. 819 | var className = toString.call(a); 820 | if (className != toString.call(b)) return false; 821 | switch (className) { 822 | // Strings, numbers, dates, and booleans are compared by value. 823 | case '[object String]': 824 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 825 | // equivalent to `new String("5")`. 826 | return a == String(b); 827 | case '[object Number]': 828 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for 829 | // other numeric values. 830 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); 831 | case '[object Date]': 832 | case '[object Boolean]': 833 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 834 | // millisecond representations. Note that invalid dates with millisecond representations 835 | // of `NaN` are not equivalent. 836 | return +a == +b; 837 | // RegExps are compared by their source patterns and flags. 838 | case '[object RegExp]': 839 | return a.source == b.source && 840 | a.global == b.global && 841 | a.multiline == b.multiline && 842 | a.ignoreCase == b.ignoreCase; 843 | } 844 | if (typeof a != 'object' || typeof b != 'object') return false; 845 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 846 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 847 | var length = aStack.length; 848 | while (length--) { 849 | // Linear search. Performance is inversely proportional to the number of 850 | // unique nested structures. 851 | if (aStack[length] == a) return bStack[length] == b; 852 | } 853 | // Add the first object to the stack of traversed objects. 854 | aStack.push(a); 855 | bStack.push(b); 856 | var size = 0, result = true; 857 | // Recursively compare objects and arrays. 858 | if (className == '[object Array]') { 859 | // Compare array lengths to determine if a deep comparison is necessary. 860 | size = a.length; 861 | result = size == b.length; 862 | if (result) { 863 | // Deep compare the contents, ignoring non-numeric properties. 864 | while (size--) { 865 | if (!(result = eq(a[size], b[size], aStack, bStack))) break; 866 | } 867 | } 868 | } else { 869 | // Objects with different constructors are not equivalent, but `Object`s 870 | // from different frames are. 871 | var aCtor = a.constructor, bCtor = b.constructor; 872 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && 873 | _.isFunction(bCtor) && (bCtor instanceof bCtor))) { 874 | return false; 875 | } 876 | // Deep compare objects. 877 | for (var key in a) { 878 | if (_.has(a, key)) { 879 | // Count the expected number of properties. 880 | size++; 881 | // Deep compare each member. 882 | if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; 883 | } 884 | } 885 | // Ensure that both objects contain the same number of properties. 886 | if (result) { 887 | for (key in b) { 888 | if (_.has(b, key) && !(size--)) break; 889 | } 890 | result = !size; 891 | } 892 | } 893 | // Remove the first object from the stack of traversed objects. 894 | aStack.pop(); 895 | bStack.pop(); 896 | return result; 897 | }; 898 | 899 | // Perform a deep comparison to check if two objects are equal. 900 | _.isEqual = function(a, b) { 901 | return eq(a, b, [], []); 902 | }; 903 | 904 | // Is a given array, string, or object empty? 905 | // An "empty" object has no enumerable own-properties. 906 | _.isEmpty = function(obj) { 907 | if (obj == null) return true; 908 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 909 | for (var key in obj) if (_.has(obj, key)) return false; 910 | return true; 911 | }; 912 | 913 | // Is a given value a DOM element? 914 | _.isElement = function(obj) { 915 | return !!(obj && obj.nodeType === 1); 916 | }; 917 | 918 | // Is a given value an array? 919 | // Delegates to ECMA5's native Array.isArray 920 | _.isArray = nativeIsArray || function(obj) { 921 | return toString.call(obj) == '[object Array]'; 922 | }; 923 | 924 | // Is a given variable an object? 925 | _.isObject = function(obj) { 926 | return obj === Object(obj); 927 | }; 928 | 929 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. 930 | each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { 931 | _['is' + name] = function(obj) { 932 | return toString.call(obj) == '[object ' + name + ']'; 933 | }; 934 | }); 935 | 936 | // Define a fallback version of the method in browsers (ahem, IE), where 937 | // there isn't any inspectable "Arguments" type. 938 | if (!_.isArguments(arguments)) { 939 | _.isArguments = function(obj) { 940 | return !!(obj && _.has(obj, 'callee')); 941 | }; 942 | } 943 | 944 | // Optimize `isFunction` if appropriate. 945 | if (typeof (/./) !== 'function') { 946 | _.isFunction = function(obj) { 947 | return typeof obj === 'function'; 948 | }; 949 | } 950 | 951 | // Is a given object a finite number? 952 | _.isFinite = function(obj) { 953 | return _.isNumber(obj) && isFinite(obj); 954 | }; 955 | 956 | // Is the given value `NaN`? (NaN is the only number which does not equal itself). 957 | _.isNaN = function(obj) { 958 | return _.isNumber(obj) && obj != +obj; 959 | }; 960 | 961 | // Is a given value a boolean? 962 | _.isBoolean = function(obj) { 963 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 964 | }; 965 | 966 | // Is a given value equal to null? 967 | _.isNull = function(obj) { 968 | return obj === null; 969 | }; 970 | 971 | // Is a given variable undefined? 972 | _.isUndefined = function(obj) { 973 | return obj === void 0; 974 | }; 975 | 976 | // Shortcut function for checking if an object has a given property directly 977 | // on itself (in other words, not on a prototype). 978 | _.has = function(obj, key) { 979 | return hasOwnProperty.call(obj, key); 980 | }; 981 | 982 | // Utility Functions 983 | // ----------------- 984 | 985 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 986 | // previous owner. Returns a reference to the Underscore object. 987 | _.noConflict = function() { 988 | root._ = previousUnderscore; 989 | return this; 990 | }; 991 | 992 | // Keep the identity function around for default iterators. 993 | _.identity = function(value) { 994 | return value; 995 | }; 996 | 997 | // Run a function **n** times. 998 | _.times = function(n, iterator, context) { 999 | for (var i = 0; i < n; i++) iterator.call(context, i); 1000 | }; 1001 | 1002 | // Return a random integer between min and max (inclusive). 1003 | _.random = function(min, max) { 1004 | if (max == null) { 1005 | max = min; 1006 | min = 0; 1007 | } 1008 | return min + (0 | Math.random() * (max - min + 1)); 1009 | }; 1010 | 1011 | // List of HTML entities for escaping. 1012 | var entityMap = { 1013 | escape: { 1014 | '&': '&', 1015 | '<': '<', 1016 | '>': '>', 1017 | '"': '"', 1018 | "'": ''', 1019 | '/': '/' 1020 | } 1021 | }; 1022 | entityMap.unescape = _.invert(entityMap.escape); 1023 | 1024 | // Regexes containing the keys and values listed immediately above. 1025 | var entityRegexes = { 1026 | escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), 1027 | unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') 1028 | }; 1029 | 1030 | // Functions for escaping and unescaping strings to/from HTML interpolation. 1031 | _.each(['escape', 'unescape'], function(method) { 1032 | _[method] = function(string) { 1033 | if (string == null) return ''; 1034 | return ('' + string).replace(entityRegexes[method], function(match) { 1035 | return entityMap[method][match]; 1036 | }); 1037 | }; 1038 | }); 1039 | 1040 | // If the value of the named property is a function then invoke it; 1041 | // otherwise, return it. 1042 | _.result = function(object, property) { 1043 | if (object == null) return null; 1044 | var value = object[property]; 1045 | return _.isFunction(value) ? value.call(object) : value; 1046 | }; 1047 | 1048 | // Add your own custom functions to the Underscore object. 1049 | _.mixin = function(obj) { 1050 | each(_.functions(obj), function(name){ 1051 | var func = _[name] = obj[name]; 1052 | _.prototype[name] = function() { 1053 | var args = [this._wrapped]; 1054 | push.apply(args, arguments); 1055 | return result.call(this, func.apply(_, args)); 1056 | }; 1057 | }); 1058 | }; 1059 | 1060 | // Generate a unique integer id (unique within the entire client session). 1061 | // Useful for temporary DOM ids. 1062 | var idCounter = 0; 1063 | _.uniqueId = function(prefix) { 1064 | var id = idCounter++; 1065 | return prefix ? prefix + id : id; 1066 | }; 1067 | 1068 | // By default, Underscore uses ERB-style template delimiters, change the 1069 | // following template settings to use alternative delimiters. 1070 | _.templateSettings = { 1071 | evaluate : /<%([\s\S]+?)%>/g, 1072 | interpolate : /<%=([\s\S]+?)%>/g, 1073 | escape : /<%-([\s\S]+?)%>/g 1074 | }; 1075 | 1076 | // When customizing `templateSettings`, if you don't want to define an 1077 | // interpolation, evaluation or escaping regex, we need one that is 1078 | // guaranteed not to match. 1079 | var noMatch = /(.)^/; 1080 | 1081 | // Certain characters need to be escaped so that they can be put into a 1082 | // string literal. 1083 | var escapes = { 1084 | "'": "'", 1085 | '\\': '\\', 1086 | '\r': 'r', 1087 | '\n': 'n', 1088 | '\t': 't', 1089 | '\u2028': 'u2028', 1090 | '\u2029': 'u2029' 1091 | }; 1092 | 1093 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 1094 | 1095 | // JavaScript micro-templating, similar to John Resig's implementation. 1096 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 1097 | // and correctly escapes quotes within interpolated code. 1098 | _.template = function(text, data, settings) { 1099 | settings = _.defaults({}, settings, _.templateSettings); 1100 | 1101 | // Combine delimiters into one regular expression via alternation. 1102 | var matcher = new RegExp([ 1103 | (settings.escape || noMatch).source, 1104 | (settings.interpolate || noMatch).source, 1105 | (settings.evaluate || noMatch).source 1106 | ].join('|') + '|$', 'g'); 1107 | 1108 | // Compile the template source, escaping string literals appropriately. 1109 | var index = 0; 1110 | var source = "__p+='"; 1111 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 1112 | source += text.slice(index, offset) 1113 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 1114 | source += 1115 | escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" : 1116 | interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" : 1117 | evaluate ? "';\n" + evaluate + "\n__p+='" : ''; 1118 | index = offset + match.length; 1119 | }); 1120 | source += "';\n"; 1121 | 1122 | // If a variable is not specified, place data values in local scope. 1123 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 1124 | 1125 | source = "var __t,__p='',__j=Array.prototype.join," + 1126 | "print=function(){__p+=__j.call(arguments,'');};\n" + 1127 | source + "return __p;\n"; 1128 | 1129 | try { 1130 | var render = new Function(settings.variable || 'obj', '_', source); 1131 | } catch (e) { 1132 | e.source = source; 1133 | throw e; 1134 | } 1135 | 1136 | if (data) return render(data, _); 1137 | var template = function(data) { 1138 | return render.call(this, data, _); 1139 | }; 1140 | 1141 | // Provide the compiled function source as a convenience for precompilation. 1142 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 1143 | 1144 | return template; 1145 | }; 1146 | 1147 | // Add a "chain" function, which will delegate to the wrapper. 1148 | _.chain = function(obj) { 1149 | return _(obj).chain(); 1150 | }; 1151 | 1152 | // OOP 1153 | // --------------- 1154 | // If Underscore is called as a function, it returns a wrapped object that 1155 | // can be used OO-style. This wrapper holds altered versions of all the 1156 | // underscore functions. Wrapped objects may be chained. 1157 | 1158 | // Helper function to continue chaining intermediate results. 1159 | var result = function(obj) { 1160 | return this._chain ? _(obj).chain() : obj; 1161 | }; 1162 | 1163 | // Add all of the Underscore functions to the wrapper object. 1164 | _.mixin(_); 1165 | 1166 | // Add all mutator Array functions to the wrapper. 1167 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1168 | var method = ArrayProto[name]; 1169 | _.prototype[name] = function() { 1170 | var obj = this._wrapped; 1171 | method.apply(obj, arguments); 1172 | if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; 1173 | return result.call(this, obj); 1174 | }; 1175 | }); 1176 | 1177 | // Add all accessor Array functions to the wrapper. 1178 | each(['concat', 'join', 'slice'], function(name) { 1179 | var method = ArrayProto[name]; 1180 | _.prototype[name] = function() { 1181 | return result.call(this, method.apply(this._wrapped, arguments)); 1182 | }; 1183 | }); 1184 | 1185 | _.extend(_.prototype, { 1186 | 1187 | // Start chaining a wrapped Underscore object. 1188 | chain: function() { 1189 | this._chain = true; 1190 | return this; 1191 | }, 1192 | 1193 | // Extracts the result from a wrapped and chained object. 1194 | value: function() { 1195 | return this._wrapped; 1196 | } 1197 | 1198 | }); 1199 | 1200 | }).call(this); 1201 | -------------------------------------------------------------------------------- /websvr/test/lib/uuid.js: -------------------------------------------------------------------------------- 1 | /* 2 | * UUID-js: A js library to generate and parse UUIDs, TimeUUIDs and generate 3 | * TimeUUID based on dates for range selections. 4 | * @see http://www.ietf.org/rfc/rfc4122.txt 5 | **/ 6 | 7 | function UUIDjs() { 8 | }; 9 | 10 | UUIDjs.maxFromBits = function(bits) { 11 | return Math.pow(2, bits); 12 | }; 13 | 14 | UUIDjs.limitUI04 = UUIDjs.maxFromBits(4); 15 | UUIDjs.limitUI06 = UUIDjs.maxFromBits(6); 16 | UUIDjs.limitUI08 = UUIDjs.maxFromBits(8); 17 | UUIDjs.limitUI12 = UUIDjs.maxFromBits(12); 18 | UUIDjs.limitUI14 = UUIDjs.maxFromBits(14); 19 | UUIDjs.limitUI16 = UUIDjs.maxFromBits(16); 20 | UUIDjs.limitUI32 = UUIDjs.maxFromBits(32); 21 | UUIDjs.limitUI40 = UUIDjs.maxFromBits(40); 22 | UUIDjs.limitUI48 = UUIDjs.maxFromBits(48); 23 | 24 | UUIDjs.randomUI04 = function() { 25 | return Math.round(Math.random() * UUIDjs.limitUI04); 26 | }; 27 | UUIDjs.randomUI06 = function() { 28 | return Math.round(Math.random() * UUIDjs.limitUI06); 29 | }; 30 | UUIDjs.randomUI08 = function() { 31 | return Math.round(Math.random() * UUIDjs.limitUI08); 32 | }; 33 | UUIDjs.randomUI12 = function() { 34 | return Math.round(Math.random() * UUIDjs.limitUI12); 35 | }; 36 | UUIDjs.randomUI14 = function() { 37 | return Math.round(Math.random() * UUIDjs.limitUI14); 38 | }; 39 | UUIDjs.randomUI16 = function() { 40 | return Math.round(Math.random() * UUIDjs.limitUI16); 41 | }; 42 | UUIDjs.randomUI32 = function() { 43 | return Math.round(Math.random() * UUIDjs.limitUI32); 44 | }; 45 | UUIDjs.randomUI40 = function() { 46 | return (0 | Math.random() * (1 << 30)) + (0 | Math.random() * (1 << 40 - 30)) * (1 << 30); 47 | }; 48 | UUIDjs.randomUI48 = function() { 49 | return (0 | Math.random() * (1 << 30)) + (0 | Math.random() * (1 << 48 - 30)) * (1 << 30); 50 | }; 51 | 52 | UUIDjs.paddedString = function(string, length, z) { 53 | string = String(string); 54 | z = (!z) ? '0' : z; 55 | var i = length - string.length; 56 | for (; i > 0; i >>>= 1, z += z) { 57 | if (i & 1) { 58 | string = z + string; 59 | } 60 | } 61 | return string; 62 | }; 63 | 64 | UUIDjs.prototype.fromParts = function(timeLow, timeMid, timeHiAndVersion, clockSeqHiAndReserved, clockSeqLow, node) { 65 | this.version = (timeHiAndVersion >> 12) & 0xF; 66 | this.hex = UUIDjs.paddedString(timeLow.toString(16), 8) 67 | + '-' 68 | + UUIDjs.paddedString(timeMid.toString(16), 4) 69 | + '-' 70 | + UUIDjs.paddedString(timeHiAndVersion.toString(16), 4) 71 | + '-' 72 | + UUIDjs.paddedString(clockSeqHiAndReserved.toString(16), 2) 73 | + UUIDjs.paddedString(clockSeqLow.toString(16), 2) 74 | + '-' 75 | + UUIDjs.paddedString(node.toString(16), 12); 76 | return this; 77 | }; 78 | 79 | UUIDjs.prototype.toString = function() { 80 | return this.hex; 81 | }; 82 | UUIDjs.prototype.toURN = function() { 83 | return 'urn:uuid:' + this.hex; 84 | }; 85 | 86 | UUIDjs.prototype.toBytes = function() { 87 | var parts = this.hex.split('-'); 88 | var ints = []; 89 | var intPos = 0; 90 | for (var i = 0; i < parts.length; i++) { 91 | for (var j = 0; j < parts[i].length; j+=2) { 92 | ints[intPos++] = parseInt(parts[i].substr(j, 2), 16); 93 | } 94 | } 95 | return ints; 96 | }; 97 | 98 | UUIDjs.prototype.equals = function(uuid) { 99 | if (!(uuid instanceof UUID)) { 100 | return false; 101 | } 102 | if (this.hex !== uuid.hex) { 103 | return false; 104 | } 105 | return true; 106 | }; 107 | 108 | UUIDjs.getTimeFieldValues = function(time) { 109 | var ts = time - Date.UTC(1582, 9, 15); 110 | var hm = ((ts / 0x100000000) * 10000) & 0xFFFFFFF; 111 | return { low: ((ts & 0xFFFFFFF) * 10000) % 0x100000000, 112 | mid: hm & 0xFFFF, hi: hm >>> 16, timestamp: ts }; 113 | }; 114 | 115 | UUIDjs._create4 = function() { 116 | return new UUIDjs().fromParts( 117 | UUIDjs.randomUI32(), 118 | UUIDjs.randomUI16(), 119 | 0x4000 | UUIDjs.randomUI12(), 120 | 0x80 | UUIDjs.randomUI06(), 121 | UUIDjs.randomUI08(), 122 | UUIDjs.randomUI48() 123 | ); 124 | }; 125 | 126 | UUIDjs._create1 = function() { 127 | var now = new Date().getTime(); 128 | var sequence = UUIDjs.randomUI14(); 129 | var node = (UUIDjs.randomUI08() | 1) * 0x10000000000 + UUIDjs.randomUI40(); 130 | var tick = UUIDjs.randomUI04(); 131 | var timestamp = 0; 132 | var timestampRatio = 1/4; 133 | 134 | if (now != timestamp) { 135 | if (now < timestamp) { 136 | sequence++; 137 | } 138 | timestamp = now; 139 | tick = UUIDjs.randomUI04(); 140 | } else if (Math.random() < timestampRatio && tick < 9984) { 141 | tick += 1 + UUIDjs.randomUI04(); 142 | } else { 143 | sequence++; 144 | } 145 | 146 | var tf = UUIDjs.getTimeFieldValues(timestamp); 147 | var tl = tf.low + tick; 148 | var thav = (tf.hi & 0xFFF) | 0x1000; 149 | 150 | sequence &= 0x3FFF; 151 | var cshar = (sequence >>> 8) | 0x80; 152 | var csl = sequence & 0xFF; 153 | 154 | return new UUIDjs().fromParts(tl, tf.mid, thav, cshar, csl, node); 155 | }; 156 | 157 | UUIDjs.create = function(version) { 158 | version = version || 4; 159 | return this['_create' + version](); 160 | }; 161 | 162 | UUIDjs.fromTime = function(time, last) { 163 | last = (!last) ? false : last; 164 | var tf = UUIDjs.getTimeFieldValues(time); 165 | var tl = tf.low; 166 | var thav = (tf.hi & 0xFFF) | 0x1000; // set version '0001' 167 | if (last === false) { 168 | return new UUIDjs().fromParts(tl, tf.mid, thav, 0, 0, 0); 169 | } else { 170 | return new UUIDjs().fromParts(tl, tf.mid, thav, 0x80 | UUIDjs.limitUI06, UUIDjs.limitUI08 - 1, UUIDjs.limitUI48 - 1); 171 | } 172 | }; 173 | 174 | UUIDjs.firstFromTime = function(time) { 175 | return UUIDjs.fromTime(time, false); 176 | }; 177 | UUIDjs.lastFromTime = function(time) { 178 | return UUIDjs.fromTime(time, true); 179 | }; 180 | 181 | UUIDjs.fromURN = function(strId) { 182 | var r, p = /^(?:urn:uuid:|\{)?([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{2})([0-9a-f]{2})-([0-9a-f]{12})(?:\})?$/i; 183 | if ((r = p.exec(strId))) { 184 | return new UUIDjs().fromParts(parseInt(r[1], 16), parseInt(r[2], 16), 185 | parseInt(r[3], 16), parseInt(r[4], 16), 186 | parseInt(r[5], 16), parseInt(r[6], 16)); 187 | } 188 | return null; 189 | }; 190 | 191 | UUIDjs.fromBytes = function(ints) { 192 | if (ints.length < 5) { 193 | return null; 194 | } 195 | var str = ''; 196 | var pos = 0; 197 | var parts = [4, 2, 2, 2, 6]; 198 | for (var i = 0; i < parts.length; i++) { 199 | for (var j = 0; j < parts[i]; j++) { 200 | var octet = ints[pos++].toString(16); 201 | if (octet.length == 1) { 202 | octet = '0' + octet; 203 | } 204 | str += octet; 205 | } 206 | if (parts[i] !== 6) { 207 | str += '-'; 208 | } 209 | } 210 | return UUIDjs.fromURN(str); 211 | }; 212 | 213 | UUIDjs.fromBinary = function(binary) { 214 | var ints = []; 215 | for (var i = 0; i < binary.length; i++) { 216 | ints[i] = binary.charCodeAt(i); 217 | if (ints[i] > 255 || ints[i] < 0) { 218 | throw new Error('Unexpected byte in binary data.'); 219 | } 220 | } 221 | return UUIDjs.fromBytes(ints); 222 | }; 223 | 224 | // Aliases to support legacy code. Do not use these when writing new code as 225 | // they may be removed in future versions! 226 | UUIDjs.new = function() { 227 | return this.create(4); 228 | }; 229 | UUIDjs.newTS = function() { 230 | return this.create(1); 231 | }; 232 | 233 | module.exports = UUIDjs; 234 | -------------------------------------------------------------------------------- /websvr/test/testExtend.js: -------------------------------------------------------------------------------- 1 | var test = require("./lib/test"); 2 | 3 | var extend = function(tar, obj) { 4 | if (!obj) return; 5 | 6 | for (var key in obj) { 7 | tar[key] = obj[key]; 8 | } 9 | 10 | return tar; 11 | }; 12 | 13 | var _ = require("./lib/underscore"); 14 | 15 | //test method 16 | test("Extend by _.extend:", function() { 17 | var tar = { 18 | a: 1, b: "2", c: "3", 19 | e: { i: "5", j: "6" }, 20 | f: { x: {}, y: [] } 21 | }; 22 | 23 | var obj = { 24 | b: 123, 25 | f: { x: [1]} 26 | }; 27 | 28 | _.extend(tar, obj); 29 | //console.log(tar, obj); 30 | }); 31 | 32 | test("Extend by simple func:", function() { 33 | var tar = { 34 | a: 1, b: "2", c: "3", 35 | e: { i: "5", j: "6" }, 36 | f: { x: {}, y: [] } 37 | }; 38 | 39 | var obj = { 40 | b: 123, 41 | f: { x: [1]} 42 | }; 43 | 44 | extend(tar, obj); 45 | //console.log(tar, obj); 46 | }); 47 | 48 | test("String operate by []:", function() { 49 | var dirPath = 'www/abc/login/'; 50 | dirPath[dirPath.length - 1] == '/'; 51 | }); 52 | 53 | test("String operate by charAt:", function() { 54 | var dirPath = 'www/abc/login/'; 55 | dirPath.charAt(dirPath.length - 1) == '/'; 56 | }); -------------------------------------------------------------------------------- /websvr/test/testFormat.js: -------------------------------------------------------------------------------- 1 | var test = require("./lib/test"); 2 | 3 | var qs = require("querystring"); 4 | 5 | var testQuery = "a=link&b=1&c=abc&f=efg" 6 | var testString = '{a: "link", b: 1, c: "abc", f: "efg"}'; 7 | var testObject = {a: "link", b: 1, c: "abc", f: "efg"}; 8 | 9 | test("Test parse/stringify querystring", function() { 10 | var string = qs.stringify(testObject); 11 | var obj = qs.parse(string); 12 | //console.log(string, obj); 13 | }); 14 | 15 | test("Test parse/stringify json", function() { 16 | var string = JSON.stringify(testString); 17 | var obj = JSON.parse(string); 18 | //console.log(string, obj); 19 | }); -------------------------------------------------------------------------------- /websvr/test/testMIME.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var test = require("./lib/test"), 3 | mime = require("./lib/mime"); 4 | MIMETYPES = require('./lib/MIMETYPES.js'); 5 | 6 | var urls = [ 7 | "http://www.github.com/appmobi/jq.html", 8 | "stringify/appmobi.lok.mp3", 9 | "good/soha.shtml", 10 | "abcdefg/appmobi.look/abcdefgg.good" 11 | ]; 12 | 13 | /* 14 | It'll be sure that: 15 | 1) The url must indicate to a file. 16 | 2) There will be no query string. 17 | 3) Length of extension will be less than 10. 18 | */ 19 | var lookup = function(url) { 20 | var idx = url.lastIndexOf('.') + 1, 21 | type; 22 | 23 | (idx > 0 ) && (type = MIMETYPES[url.substr(idx, 10)]); 24 | 25 | return type || "application/octet-stream"; 26 | }; 27 | 28 | test("Test mime.lookup == mime.custom", function() { 29 | urls.forEach(function(url) { 30 | console.log(mime.lookup(url) == lookup(url), lookup(url), mime.lookup(url), url); 31 | }); 32 | }, 1); 33 | 34 | test("Test mime.lookup", function() { 35 | urls.forEach(function(url) { 36 | mime.lookup(url); 37 | }); 38 | }); 39 | 40 | test("Test mime.custom", function() { 41 | urls.forEach(function(url) { 42 | lookup(url); 43 | }); 44 | }); -------------------------------------------------------------------------------- /websvr/test/testSession.js: -------------------------------------------------------------------------------- 1 | var test = require("./lib/test"); 2 | 3 | var create = function() { 4 | //Time stamp, change interval is 18.641 hours, higher 6 bits will be kept, this is used for delete the old sessions 5 | var uuid 6 | = ((+new Date()) / 60000 | 0) //Time stamp, change interval is 1 min, 8 chars 7 | + '-' 8 | + ((Math.random() * 0x4000000 | 0)) //Random 1: Used for distinguish the session, max 9 chars 9 | + ((Math.random() * 0x4000000 | 0)); //Random 2: Used for distinguish the session, max 9 chars 10 | 11 | //fix the length to 25 12 | //uuid += '0000000000'.substr(0, 25 - uuid.length); 13 | 14 | return uuid; 15 | }; 16 | 17 | test("SessionManager.create: test length", function() { 18 | 19 | var max = 0, min = 1000; 20 | for (var i = 0; i < 1000000; i ++) { 21 | var len = create().length; 22 | 23 | (len > max) && (max = len); 24 | (len < min) && (min = len); 25 | } 26 | 27 | console.log("min:", min, "max:", max); 28 | 29 | }, 1); 30 | 31 | test("Test parse Int string: | 0", function() { 32 | var rnd = "18999334.3415921676"; 33 | rnd = rnd | 0; 34 | }); 35 | 36 | test("Test parse Int string: parseInt", function() { 37 | var rnd = "18999334.3415921676"; 38 | rnd = parseInt(rnd); 39 | }); 40 | 41 | test("Test parse Int float: | 0", function() { 42 | var rnd = 18999334.3415921676; 43 | rnd = rnd | 0; 44 | }); 45 | 46 | test("Test parse Int float: parseInt", function() { 47 | var rnd = 18999334.3415921676; 48 | rnd = parseInt(rnd); 49 | }); -------------------------------------------------------------------------------- /websvr/test/testString.js: -------------------------------------------------------------------------------- 1 | var test = require("./lib/test"); 2 | 3 | var testString = "/a=link&b=1&c=abc&f=efg" 4 | 5 | test("Test string chartAt", function() { 6 | testString.charAt(0) == '/' 7 | }); 8 | 9 | test("Test string array", function() { 10 | testString[0] == '/' 11 | }); -------------------------------------------------------------------------------- /websvr/test/testUUID.js: -------------------------------------------------------------------------------- 1 | var UUIDjs = require("./lib/uuid"), 2 | test = require("./lib/test"); 3 | 4 | var websvr = require('../websvr')().stop(); 5 | 6 | (function() { 7 | // Private array of chars to use 8 | var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 9 | 10 | Math.uuid = function (len, radix) { 11 | var chars = CHARS, uuid = [], i; 12 | radix = radix || chars.length; 13 | 14 | if (len) { 15 | // Compact form 16 | for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix]; 17 | } else { 18 | // rfc4122, version 4 form 19 | var r; 20 | 21 | // rfc4122 requires these characters 22 | uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; 23 | uuid[14] = '4'; 24 | 25 | // Fill in random data. At i==19 set the high bits of clock sequence as 26 | // per rfc4122, sec. 4.1.5 27 | for (i = 0; i < 36; i++) { 28 | if (!uuid[i]) { 29 | r = 0 | Math.random()*16; 30 | uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 31 | } 32 | } 33 | } 34 | 35 | return uuid.join(''); 36 | }; 37 | 38 | // A more performant, but slightly bulkier, RFC4122v4 solution. We boost performance 39 | // by minimizing calls to random() 40 | Math.uuidFast = function() { 41 | var chars = CHARS, uuid = new Array(36), rnd=0, r; 42 | for (var i = 0; i < 36; i++) { 43 | if (i==8 || i==13 || i==18 || i==23) { 44 | uuid[i] = '-'; 45 | } else if (i==14) { 46 | uuid[i] = '4'; 47 | } else { 48 | if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0; 49 | r = rnd & 0xf; 50 | rnd = rnd >> 4; 51 | uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 52 | } 53 | } 54 | return uuid.join(''); 55 | }; 56 | 57 | // A more compact, but less performant, RFC4122v4 solution: 58 | Math.uuidCompact = function() { 59 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 60 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 61 | return v.toString(16); 62 | }); 63 | }; 64 | })(); 65 | 66 | //Conver to array will make it slower; 67 | var CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 68 | var CHARSArr = CHARS.split(''); 69 | 70 | var SCHARS = "0123456789ABCDEF"; 71 | var SCHARSArr = SCHARS.split(''); 72 | 73 | console.log("Testing long chars:"); 74 | 75 | test("Math.uuid(32)", function() { 76 | Math.uuid(32); 77 | }); 78 | 79 | test("Math.uuidFast", function() { 80 | Math.uuidFast(); 81 | }); 82 | 83 | test("UUIDjs.create", function() { 84 | UUIDjs.create(); 85 | }); 86 | 87 | var createUUID = function() { 88 | var create = function() { 89 | return (Math.random() * 0x10000 | 0).toString(16); 90 | }; 91 | 92 | var uuid = new Array(8); 93 | for (var i = 0; i < 8; i++) { 94 | uuid[i] = (Math.random() * 0x10000 | 0).toString(16); 95 | } 96 | }; 97 | 98 | test("createUUID", function() { 99 | createUUID(); 100 | }); 101 | 102 | test("UUID fixed array", function() { 103 | //Using fixed size array will be more faster than "new Array()" 104 | var uuid = new Array(32); 105 | for (var i = 0; i < 32; i++) { 106 | uuid[i] = CHARS[0 | Math.random() * 36]; 107 | } 108 | uuid = uuid.join(); 109 | }); 110 | 111 | test("UUID string plus", function() { 112 | var uuid = ""; 113 | for (var i = 0; i < 32; i++) { 114 | uuid += CHARS[0 | Math.random() * 36]; 115 | } 116 | }); 117 | 118 | test("UUIDArr string plus", function() { 119 | var uuid = ""; 120 | for (var i = 0; i < 32; i++) { 121 | uuid += CHARSArr[0 | Math.random() * 36]; 122 | } 123 | }); 124 | 125 | test("UUIDArr fixed array", function() { 126 | //Using fixed size array will be more faster than "new Array()" 127 | var uuid = new Array(32); 128 | for (var i = 0; i < 32; i++) { 129 | uuid[i] = CHARSArr[0 | Math.random() * 36]; 130 | } 131 | uuid = uuid.join(); 132 | }); 133 | 134 | console.log("\r\n\r\nTesting short chars:"); 135 | 136 | test("UUID fixed array", function() { 137 | //Using fixed size array will be more faster than "new Array()" 138 | var uuid = new Array(32); 139 | for (var i = 0; i < 32; i++) { 140 | uuid[i] = SCHARS[0 | Math.random() * 16]; 141 | } 142 | uuid = uuid.join(); 143 | }); 144 | 145 | test("UUID string plus", function() { 146 | var uuid = ""; 147 | for (var i = 0; i < 32; i++) { 148 | uuid += SCHARS[0 | Math.random() * 16]; 149 | } 150 | }); 151 | 152 | test("UUIDArr string plus", function() { 153 | var uuid = ""; 154 | for (var i = 0; i < 32; i++) { 155 | uuid += SCHARSArr[0 | Math.random() * 16]; 156 | } 157 | }); 158 | 159 | test("UUIDArr fixed array", function() { 160 | //Using fixed size array will be more faster than "new Array()" 161 | var uuid = new Array(32); 162 | for (var i = 0; i < 32; i++) { 163 | uuid[i] = SCHARSArr[0 | Math.random() * 16]; 164 | } 165 | uuid = uuid.join(); 166 | }); 167 | 168 | //interval is 4 hours 169 | test("UUIDArr fixed array final", function() { 170 | var suuid = (+new Date()) / 14400000; 171 | 172 | var uuid = new Array(32); 173 | for (var i = 0; i < 32; i++) { 174 | uuid[i] = SCHARSArr[0 | Math.random() * 16]; 175 | } 176 | 177 | suuid += "-" + uuid.join(''); 178 | }); 179 | 180 | test("Custom session id, with full Date", function() { 181 | var uuid 182 | = (+new Date()) //Time stamp, change interval is 0.583 hours, higher 11 bits will be kept 183 | + '-' 184 | + ((Math.random() * 0x40000000 | 0)) //Random 1: Used for distinguish the session 185 | + ((Math.random() * 0x40000000 | 0)); //Random 2: Used for distinguish the session 186 | 187 | uuid += '0000000000'.substr(0, 25 - uuid.length); 188 | }); 189 | 190 | test("Custom session id, with short Date", function() { 191 | var uuid 192 | = ((+new Date()) / 60000 | 0) //Time stamp, change interval is 0.583 hours, higher 11 bits will be kept 193 | + '-' 194 | + ((Math.random() * 0x40000000 | 0)) //Random 1: Used for distinguish the session 195 | + ((Math.random() * 0x40000000 | 0)); //Random 2: Used for distinguish the session 196 | 197 | uuid += '0000000000'.substr(0, 25 - uuid.length); 198 | }); 199 | 200 | test("Test default Session ID", function() { 201 | var newID = websvr.newID(); 202 | }); 203 | 204 | test("Test Session ID with appended chars", function() { 205 | var newID = websvr.newID(3); 206 | }); -------------------------------------------------------------------------------- /websvr/websvr.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Description: websvr 3 | * Author: Kris Zhang 4 | * Licenses: MIT 5 | * Project url: https://github.com/newghost/websvr 6 | */ 7 | 8 | "use strict"; 9 | 10 | //Node libraries 11 | var fs = require("fs"); 12 | var path = require("path"); 13 | var qs = require("querystring"); 14 | var os = require("os"); 15 | 16 | var http = require("http"); 17 | var https = require("https"); 18 | 19 | //Open source libraries 20 | var mime = require("mime"); 21 | var formidable = require("formidable"); 22 | 23 | /* 24 | * Utility 25 | */ 26 | var _ = { 27 | //extend object to target 28 | extend: function(tar, obj) { 29 | if (!obj) return tar; 30 | 31 | for (var key in obj) { 32 | tar[key] = obj[key]; 33 | } 34 | 35 | return tar; 36 | } 37 | }; 38 | 39 | //Shortcuts 40 | var define = Object.defineProperty; 41 | 42 | //Mapping 43 | var CHARS = '0123456789abcdefghijklmnopqrstuvwxyz'.split(''); 44 | 45 | /* 46 | * Define and Export WebSvr 47 | */ 48 | var WebSvr = module.exports = function(options) { 49 | 50 | var self = {}; 51 | 52 | var SessionStore; 53 | 54 | /*****************Web module definitions*************/ 55 | /* 56 | Configurations 57 | */ 58 | var Settings = { 59 | //root folder of server 60 | root: process.cwd() 61 | 62 | //home folder of web 63 | , home: './' 64 | 65 | //http start 66 | //default port of http 67 | , port: 8054 68 | 69 | //default port of https 70 | , httpsPort: 8443 71 | , httpsKey: "" 72 | , httpsCert: "" 73 | 74 | //list files in directory 75 | , listDir: false 76 | //enable client-side cache(304) 77 | , cache: true 78 | //enable debug information output 79 | , debug: true 80 | //enable cache of template/include file (when enabled templates will not be refreshed before restart) 81 | , templateCache: true 82 | //show errors to user(displayed in response) 83 | , showError: true 84 | 85 | //default pages, only one is supported 86 | , defaultPage: "index.html" 87 | , 404: "" 88 | 89 | /* 90 | Session timeout, in milliseconds. 91 | */ 92 | , sessionTimeout: 1440000 93 | 94 | //session file stored here 95 | , sessionDir : '' 96 | 97 | //session domain 98 | , sessionDomain: '' 99 | 100 | , sessionLength: 36 101 | 102 | //tempary upload file stored here 103 | , uploadDir: os.tmpDir() 104 | }; 105 | 106 | /* 107 | Logger: log sth 108 | */ 109 | var Logger = (function() { 110 | /* 111 | Turn off debug when Settings.debug = false 112 | */ 113 | var debug = function() { 114 | //diable console.log information 115 | if (!Settings.debug) { 116 | return; 117 | } 118 | 119 | var d = new Date().toISOString(); 120 | Array.prototype.splice.call(arguments, 0, 0, d); 121 | console.log.apply(console, arguments); 122 | }; 123 | 124 | var error = function() { 125 | var d = new Date().toISOString(); 126 | Array.prototype.splice.call(arguments, 0, 0, d); 127 | console.error.apply(console, arguments); 128 | }; 129 | 130 | return { debug: debug, error: error }; 131 | })(); 132 | 133 | /* 134 | Body parser, parse the data in request body 135 | when parse complete, execute the callback with response data; 136 | */ 137 | var BodyParser = function(req, res, callback) { 138 | 139 | var receives = []; 140 | 141 | req.on('data', function(chunk) { 142 | receives.push(chunk); 143 | }); 144 | 145 | req.on('end', function() { 146 | callback(Buffer.concat(receives).toString()); 147 | }); 148 | }; 149 | 150 | /* 151 | Parse request with session support 152 | */ 153 | var SessionParser = function() { 154 | var self = this; 155 | 156 | //session id 157 | self.sid = null; 158 | self.val = null; 159 | }; 160 | 161 | SessionParser.prototype = { 162 | init: function(req, res, cb) { 163 | var self = this 164 | , sidKey = "_wsid" 165 | , sidVal 166 | , sidStr 167 | ; 168 | 169 | //Get or Create sid, sid exist in the cookie, read it 170 | var sidVal = req.cookies[sidKey]; 171 | 172 | //Does session expired? 173 | var getSession = function(session) { 174 | var isValid = session && session.__lastAccessTime && (+new Date() - session.__lastAccessTime <= Settings.sessionTimeout); 175 | 176 | if (isValid) { 177 | self.sid = sidVal; 178 | self.val = session; 179 | self.val.__lastAccessTime = +new Date(); 180 | cb && cb(); 181 | } else { 182 | SessionStore.del(sidVal); 183 | setSession(); 184 | } 185 | }; 186 | 187 | var setSession = function() { 188 | self.create(); 189 | res.cookie(sidKey, self.sid, { domain: Settings.sessionDomain, path: '/', httponly: true }); 190 | cb && cb(); 191 | }; 192 | 193 | //Sid doesn't exist, create it 194 | if (!sidVal || sidVal.length != Settings.sessionLength) { 195 | setSession(); 196 | } else { 197 | SessionStore.get(sidVal, getSession); 198 | } 199 | } 200 | 201 | /* 202 | * newId() : [Time Stamp]-[serverID][Random Chars] //for sessionid, fixed length 203 | * newID(n) : [Time Stamp][serverID][Random Chars(n)] //for userid 204 | */ 205 | , newID: function(appendLen) { 206 | var len = CHARS.length; 207 | var sid = (+new Date()).toString(len); 208 | 209 | if (appendLen) { 210 | sid += Settings.serverID || ''; 211 | for (var i = 0; i < appendLen; i++) { 212 | sid += CHARS[Math.random() * len | 0]; 213 | } 214 | } else { 215 | sid = sid + '-' + (Settings.serverID || ''); 216 | for (var i = sid.length; i < Settings.sessionLength; i++ ) { 217 | sid += CHARS[Math.random() * len | 0]; 218 | } 219 | } 220 | 221 | return sid; 222 | } 223 | 224 | //Binding new sid to this session 225 | , create: function() { 226 | var self = this; 227 | self.sid = self.newID(); 228 | self.val = { __lastAccessTime: +new Date() }; 229 | return self; 230 | } 231 | 232 | , update: function() { 233 | var self = this; 234 | SessionStore.set(self.sid, self.val); 235 | } 236 | 237 | //Set an key/value pair in session object 238 | , set: function(key, val) { 239 | var session = this.val; 240 | session.__lastAccessTime = +new Date(); 241 | session[key] = val; 242 | return session; 243 | } 244 | 245 | //Get value from session file 246 | , get: function(key) { 247 | var session = this.val; 248 | session.__lastAccessTime = +new Date(); 249 | return key ? session[key] : session; 250 | } 251 | }; 252 | 253 | 254 | var errorRender = function(err, customErrorMsg, outFn) { 255 | var errorMsg 256 | = (customErrorMsg || '') 257 | + '\n' 258 | + err.stack || err.message || 'unknow error' 259 | + '\n' 260 | ; 261 | 262 | console.error(errorMsg); 263 | 264 | if (outFn) { 265 | Settings.showError 266 | ? outFn('
' + errorMsg.replace(//g, '>') + '
') 267 | : outFn(); 268 | } 269 | } 270 | 271 | /* 272 | Parser: Functions that Filter and Handler will be called 273 | */ 274 | var Parser = function(req, res, mapper) { 275 | 276 | var handle = function() { 277 | try { 278 | mapper.handler(req, res); 279 | } catch(err) { 280 | errorRender(err, 'Error ' + new Date().toISOString() + ' ' + req.url, res.end) 281 | } 282 | }; 283 | 284 | //add sesion support 285 | var parseSession = function() { 286 | //add sesion support 287 | if (mapper.session && typeof req.session == "undefined") { 288 | req.session = new SessionParser(); 289 | req.session.init(req, res, handle); 290 | } else { 291 | handle(); 292 | } 293 | }; 294 | 295 | /* 296 | parse data in request 297 | */ 298 | var parseBody = function() { 299 | //need to parse the request? 300 | if (mapper.post && typeof req.body == "undefined") { 301 | //Must parser the request first, or the post data will lost; 302 | BodyParser(req, res, function(data) { 303 | var body = data; 304 | 305 | //handle exception 306 | try { 307 | mapper.post == "json" 308 | && (body = JSON.parse(data || "{}")); 309 | 310 | mapper.post == "qs" 311 | && (body = qs.parse(data || "")); 312 | } catch(e) { 313 | body = {}; 314 | } 315 | 316 | req.body = body; 317 | parseSession(); 318 | }); 319 | } else { 320 | parseSession(); 321 | } 322 | }; 323 | 324 | /* 325 | parse file in request, this should be at the top of the list 326 | */ 327 | var parseFile = function() { 328 | if (mapper._before && !mapper._before(req, res)) { 329 | Logger.debug('"before" function does not return true, request ended.'); 330 | res.end('This is not a valid request'); 331 | return 332 | } 333 | 334 | //Need to parse the file in request? 335 | if (mapper.file && typeof req.body == "undefined") { 336 | //Must parser the request first, or the post data maybe lost; 337 | var form = new formidable.IncomingForm(); 338 | 339 | form.uploadDir = Settings.uploadDir; 340 | 341 | form.parse(req, function(err, fields, files) { 342 | if (err) { 343 | Logger.debug(err); 344 | return; 345 | }; 346 | 347 | //attach the parameters and files 348 | req.body = fields; 349 | req.files = files; 350 | 351 | //in fact request will not be parsed again, because body is not undefined 352 | parseBody(); 353 | }); 354 | } else { 355 | parseBody(); 356 | }; 357 | }; 358 | 359 | /* 360 | parse cookie in request 361 | */ 362 | var parseCookies = function() { 363 | var cookie = req.headers.cookie 364 | , cookies = {} 365 | ; 366 | 367 | if (cookie) { 368 | var cookieArr = cookie.split(';'); 369 | 370 | for (var i = 0; i < cookieArr.length; i++) { 371 | var strCookie = cookieArr[i] 372 | , idx = strCookie.indexOf('=') 373 | ; 374 | idx > 0 && (cookies[strCookie.substr(0, idx).trim()] = strCookie.substr(idx + 1).trim()); 375 | } 376 | } 377 | 378 | req.cookies = cookies; 379 | parseFile(); 380 | }; 381 | 382 | parseCookies(); 383 | }; 384 | 385 | 386 | /* 387 | set: res.cookie(name, value, options) 388 | del: res.cookie(name, null); 389 | */ 390 | var Cookie = function(name, value, options) { 391 | if (arguments.length < 2) { 392 | return Logger.debug('cookie setter ignored', name); 393 | } 394 | 395 | var self = this 396 | , cookies = self.cookies = self.cookies || [] 397 | , setStr = name + '=' + (value || '') 398 | ; 399 | 400 | options = options || {}; 401 | 402 | if (value === null) { 403 | setStr += '; expires=Thu, 01 Jan 1970 00:00:00 GMT'; 404 | } else if (options.expires) { 405 | setStr += '; expires=' + (new Date(options.expires)).toGMTString(); 406 | } 407 | 408 | options.path && (setStr += '; path=' + options.path); 409 | options.domain && (setStr += '; domain=' + options.domain); 410 | options.secure && (setStr += '; secure'); 411 | options.httponly && (setStr += '; httponly'); 412 | 413 | cookies.push(setStr); 414 | }; 415 | 416 | 417 | /* 418 | SessionStore Interface (MemoryStore) 419 | - get : (sid, callback:session) 420 | - set : (sid, session) 421 | - del : (sid) 422 | session object: { 423 | sid: { 424 | .... 425 | __lastAccessTime: dateObject 426 | } 427 | } 428 | */ 429 | var MemoryStore = (function() { 430 | 431 | var list; 432 | 433 | //force update session in list, convert to big int 434 | //get session in list, if undefined create new one 435 | var get = function(sid, cb) { 436 | !list && init(); 437 | !list[sid] && (list[sid] = {}); 438 | cb && cb(list[sid]); 439 | }; 440 | 441 | var set = function(sid, session) { 442 | !list && init(); 443 | list[sid] = session; 444 | }; 445 | 446 | //remove a sesson from list 447 | var del = function(sid) { 448 | delete list[sid]; 449 | }; 450 | 451 | /* 452 | Session clear handler 453 | */ 454 | var clearHandler = function() { 455 | for (var sid in list) { 456 | var session = list[sid]; 457 | var isValid = session.__lastAccessTime && ((new Date() - session.__lastAccessTime) || 0 <= Settings.sessionTimeout * 2); 458 | !isValid && del(sid); 459 | } 460 | }; 461 | 462 | var init = function() { 463 | list = {}; 464 | setInterval(clearHandler, Settings.sessionTimeout * 4); 465 | }; 466 | 467 | return { 468 | get : get 469 | , set : set 470 | , del : del 471 | } 472 | 473 | })(); 474 | 475 | var FileStore = (function() { 476 | 477 | var getPath = function(sid) { 478 | return path.join(Settings.sessionDir, sid); 479 | }; 480 | 481 | var del = function(sid) { 482 | fs.unlink(getPath(sid), function(err) { 483 | Logger.debug("unlink session file err", err); 484 | }); 485 | }; 486 | 487 | var set = function(sid, session) { 488 | fs.writeFile(getPath(sid), JSON.stringify(session), function(err) { 489 | if (err) { 490 | Logger.error(err); 491 | } 492 | }); 493 | }; 494 | 495 | var get = function(sid, cb) { 496 | var session = {}; 497 | fs.readFile(getPath(sid), function(err, data) { 498 | if (err) { 499 | Logger.debug(err); 500 | cb && cb(session); 501 | return; 502 | } 503 | 504 | try { 505 | session = JSON.parse(data); 506 | } catch (e) { 507 | Logger.debug(e); 508 | } 509 | cb && cb(session); 510 | }); 511 | }; 512 | 513 | /* 514 | Clear the sessions, you should do it manually somewhere, etc: 515 | setInterval(websvr.SessionStore.clear, 200 * 60 * 1000) 516 | */ 517 | var clear = function() { 518 | fs.readdir(Settings.sessionDir, function(err, files) { 519 | if (err) return Logger.debug(err); 520 | 521 | //Delete these sessions that created very very long ago 522 | var expire = +new Date() - Settings.sessionTimeout * 24; 523 | 524 | for (var i = 0; i < files.length; i++) { 525 | var file = files[i] 526 | , idx = file.indexOf('-') 527 | ; 528 | 529 | if (file.length == Settings.sessionLength && idx > 0) { 530 | var stamp = parseInt(file.substr(0, idx), CHARS.length); 531 | //remove the expired session 532 | stamp && stamp < expire && del(file); 533 | } 534 | } 535 | }); 536 | }; 537 | 538 | return { 539 | get : get 540 | , set : set 541 | , del : del 542 | , clear : clear 543 | } 544 | 545 | })(); 546 | 547 | /* 548 | Mapper: Used for Filter & Handler, 549 | expression: required parameter 550 | handler: required parameter 551 | options: optional parameters 552 | */ 553 | var Mapper = function(expression, handler, options) { 554 | var self = this; 555 | 556 | self.expression = expression; 557 | self.handler = handler; 558 | 559 | typeof options == 'object' 560 | ? self.extend(options) 561 | : (self.post = options); 562 | }; 563 | 564 | Mapper.prototype = { 565 | /* 566 | Does this mapper matched this request? 567 | Filter and Handler doesn't have the same matched rules when you passing a string 568 | Filter : Match any section of the request url, e.g., websvr.filter(".svr", cb); 569 | Handler : Match from the begining but it can bypass '/', e.g., websvr.handle("home/login", cb) or websvr.handle("/home/login") 570 | */ 571 | match: function(req, isHandler) { 572 | var self = this 573 | , reqUrl = req.url 574 | , expression = self.expression 575 | ; 576 | 577 | //No expression? It's a general filter mapper 578 | if (!expression) return true; 579 | 580 | switch (expression.constructor) { 581 | //String handler must start with home path, but it can bypass '/' 582 | case String: 583 | return self.matchString(req, isHandler, expression); 584 | case RegExp: return expression.test(reqUrl); 585 | case Array: 586 | for (var i = 0, l = expression.length; i < l; i++) { 587 | if (self.matchString(req, isHandler, expression[i])) { 588 | return true; 589 | } 590 | } 591 | return false; 592 | } 593 | 594 | return false; 595 | }, 596 | 597 | /* 598 | Handle string expression like: /login/:username or /userinfo/ 599 | */ 600 | matchString: function(req, isHandler, expression) { 601 | var reqUrl = req.url; 602 | 603 | //Pure string without params 604 | if (expression.indexOf('/:') < 0) { 605 | var idx = reqUrl.indexOf(expression); 606 | 607 | /* 608 | fix: url(['/m'], cb) & url(['/more'], cb) will match the same handler 609 | */ 610 | if (isHandler) { 611 | if ((idx == 0 || idx == 1)) { 612 | var lastChar = reqUrl.charAt(idx + expression.length) 613 | return lastChar == '' || lastChar == '/' || lastChar == '?' 614 | } 615 | } else { 616 | return idx > -1; 617 | } 618 | 619 | return false; 620 | //Handle and pickup params 621 | } else { 622 | var params = this.parseUrl(expression, reqUrl); 623 | params && _.extend(req.params, params); 624 | return params; 625 | } 626 | }, 627 | 628 | /* 629 | * Pickup the params in the request url 630 | * expression = /home/:key/:pager 631 | * /home/JavaScript => { id: 'JavaScript', pager: '' } 632 | * /key/JavaScript => false 633 | */ 634 | parseUrl: function(expression, reqUrl) { 635 | //Remove the params in querystring 636 | var idx = reqUrl.indexOf('?'); 637 | idx > 0 && (reqUrl = reqUrl.substr(0, idx)); 638 | 639 | var parts = expression.split('/') 640 | , start = expression.charAt(0) === '/' ? 0 : 1 641 | , urls = reqUrl.split('/') 642 | , params = {} 643 | ; 644 | 645 | for (var i = 0, l = parts.length; i < l; i++) { 646 | var part = parts[i] 647 | , param = urls[i + start] 648 | ; 649 | 650 | if (part.charAt(0) === ':') { 651 | var paramName = part.substr(1); 652 | try { 653 | params[paramName] = decodeURIComponent(param || ''); 654 | } catch(err) { 655 | params[paramName] = param; 656 | } 657 | } else if (part != param) { 658 | return false; 659 | } 660 | } 661 | 662 | return params; 663 | }, 664 | 665 | /* 666 | Add optional parameters on current mapper 667 | i.e: 668 | session: boolean 669 | file: boolean 670 | parse: boolean 671 | */ 672 | extend: function(options) { 673 | for(var key in options) { 674 | this[key] = options[key] 675 | } 676 | }, 677 | 678 | /* 679 | Something need to be done first: i.e: 680 | check the file size and extension before uploading files; 681 | check the content-length before receiving a post json 682 | */ 683 | before: function(func) { 684 | func && (this._before = func) 685 | } 686 | }; 687 | 688 | /* 689 | Http Filter: Execute all the rules that matched, 690 | Filter will be always called before a handler. 691 | */ 692 | var Filter = { 693 | //filter list 694 | filters: [] 695 | 696 | /* 697 | filter: add a new filter 698 | expression: string/regexp [optional] 699 | handler: function [required] 700 | options: object [optional] 701 | */ 702 | , filter: function(expression, handler, options) { 703 | //The first parameter is Function => (handler, options) 704 | if (expression.constructor == Function) { 705 | options = handler; 706 | handler = expression; 707 | expression = null; 708 | } 709 | 710 | var mapper = new Mapper(expression, handler, options); 711 | Filter.filters.push(mapper); 712 | 713 | return mapper; 714 | } 715 | 716 | //Session: parse the session 717 | , session: function(expression, handler, options) { 718 | var mapper = this.filter(expression, handler, options); 719 | mapper.session = true; 720 | return mapper; 721 | } 722 | 723 | /* 724 | file receiver: it's a specfic filter, 725 | this filter should be always at the top of the filter list 726 | */ 727 | , file: function(expression, handler, options) { 728 | var mapper = new Mapper(expression, handler, options); 729 | mapper.file = true; 730 | //insert at the top of the filter list 731 | Filter.filters.splice(0, 0, mapper); 732 | 733 | return mapper; 734 | } 735 | }; 736 | 737 | /* 738 | Filter Chain 739 | */ 740 | var FilterChain = function(cb, req, res) { 741 | var self = this; 742 | 743 | self.idx = 0; 744 | self.cb = cb; 745 | 746 | self.req = req; 747 | self.res = res; 748 | }; 749 | 750 | FilterChain.prototype = { 751 | next: function() { 752 | var self = this 753 | , req = self.req 754 | , res = self.res 755 | ; 756 | 757 | var mapper = Filter.filters[self.idx++]; 758 | 759 | //filter is complete, execute callback; 760 | if (!mapper) return self.cb && self.cb(); 761 | 762 | /* 763 | If not Matched go to next filter 764 | If matched need to execute the req.next() in callback handler, 765 | e.g: 766 | webSvr.filter(/expression/, function(req, res) { 767 | //filter actions 768 | req.next(req, res); 769 | }, options); 770 | */ 771 | if (mapper.match(req)) { 772 | Logger.debug("filter matched", self.idx, mapper.expression, req.url); 773 | 774 | //filter matched, parse the request and then execute it 775 | Parser(req, res, mapper); 776 | } else { 777 | //filter not matched, validate next filter 778 | self.next(); 779 | } 780 | } 781 | }; 782 | 783 | /* 784 | Http Handler: Execute and returned when when first matched; 785 | At the same time only one Handler will be called; 786 | */ 787 | var Handler = { 788 | handlers: [] 789 | /* 790 | url: add a new handler 791 | expression: string/regexp [required] 792 | handler: [many types] [required] 793 | options: object [optional] 794 | */ 795 | , url: function(expression, handler, options) { 796 | if (!expression) { 797 | Logger.debug('url expression ignored'); 798 | } else { 799 | var mapper = new Mapper(expression, handler, options); 800 | Handler.handlers.push(mapper); 801 | } 802 | return self; 803 | } 804 | 805 | , post: function(expression, handler, options) { 806 | if (expression && handler) { 807 | return this.url(expression, handler, options || 'qs'); 808 | } 809 | return self; 810 | } 811 | 812 | , handle: function(req, res) { 813 | //flag: is matched? 814 | for(var i = 0, len = Handler.handlers.length; i < len ; i++) { 815 | 816 | var mapper = Handler.handlers[i]; 817 | //This is handler match 818 | if (mapper.match(req, true)) { 819 | 820 | Logger.debug("handler matched", i, mapper.expression, req.url); 821 | 822 | var handler = mapper.handler, 823 | type = handler.constructor.name; 824 | 825 | switch(type) { 826 | //function: treated it as custom function handler 827 | case "Function": 828 | Parser(req, res, mapper); 829 | break; 830 | 831 | //string: treated it as content 832 | case "String": 833 | res.writeHead(200, { "Content-Type": "text/html" }); 834 | res.end(handler); 835 | break; 836 | 837 | //array: treated it as a file. 838 | case "Array": 839 | res.sendFile(handler[0]); 840 | break; 841 | } 842 | return true; 843 | } 844 | } 845 | 846 | return false; 847 | 848 | } //end of handle 849 | }; 850 | 851 | /* 852 | ListDir: List all the files in a directory 853 | */ 854 | var ListDir = (function() { 855 | 856 | var urlFormat = function(url) { 857 | url = url.replace(/\\/g,'/'); 858 | url = url.replace(/ /g,'%20'); 859 | return url; 860 | }; 861 | 862 | //Align to right 863 | var date = function(date) { 864 | var d = date.getFullYear() 865 | + '-' + (date.getMonth() + 1) 866 | + '-' + (date.getDay() + 1) 867 | + " " + date.toLocaleTimeString(); 868 | return " ".substring(0, 20 - d.length) + d; 869 | }; 870 | 871 | //Align to left 872 | var size = function(num) { 873 | return num + " ".substring(0, 12 - String(num).length); 874 | }; 875 | 876 | //Create an anchor 877 | var anchor = function(txt, url) { 878 | url = url ? url : "/"; 879 | return '' + txt + ""; 880 | }; 881 | 882 | var listDir = { 883 | //List all the files in a directory 884 | list: function(req, res, dir) { 885 | var url = req.url, 886 | cur = 0, 887 | len = 0; 888 | 889 | var listBegin = function() { 890 | res.writeHead(200, {"Content-Type": "text/html"}); 891 | res.write("

http://" + req.headers.host + url + "


"); 892 | res.write("
");
 893 |           res.write(anchor("[To Parent Directory]", url.substr(0, url.lastIndexOf('/'))) + "\r\n\r\n");
 894 |         };
 895 | 
 896 |         var listEnd = function() {
 897 |           res.write("

"); 898 | res.end("
Count: " + len + "
"); 899 | }; 900 | 901 | listBegin(); 902 | 903 | fs.readdir(dir, function(err, files) { 904 | if (err) { 905 | listEnd(); 906 | Logger.debug(err); 907 | return; 908 | } 909 | 910 | len = files.length; 911 | 912 | for(var idx = 0; idx < len; idx++) { 913 | //Persistent the idx before make the sync process 914 | (function(idx) { 915 | var filePath = path.join(dir, files[idx]), 916 | fileUrl = urlFormat(path.join(url, files[idx])); 917 | 918 | fs.stat(filePath, function(err, stat) { 919 | cur++; 920 | 921 | if (err) { 922 | Logger.debug(err); 923 | }else{ 924 | res.write( 925 | date(stat.mtime) 926 | + "\t" + size(stat.size) 927 | + anchor(files[idx], fileUrl) 928 | + "\r\n" 929 | ); 930 | } 931 | 932 | (cur == len) && listEnd(); 933 | }); 934 | })(idx); 935 | } 936 | 937 | (len == 0) && listEnd(); 938 | }); 939 | } 940 | }; 941 | 942 | return listDir; 943 | }()); 944 | 945 | /* 946 | * Template Engine 947 | */ 948 | var Template = (function() { 949 | 950 | //Caching of template files. 951 | var templatePool = {} 952 | , includeString = '/g 954 | , includeBeginLen = 14 955 | , includeAfterLen = 4 956 | ; 957 | 958 | //default engine and defaultModel (e.g., define global footer/header in model) 959 | var engineFunc = require("dot").compile 960 | , defaultModel = null 961 | ; 962 | 963 | //get a file 964 | var getFile = function(filename, cb) { 965 | //if template cache enabled, get from cache pool directly 966 | var cachedTemplate = templatePool[filename] 967 | 968 | var updateCache = function(err, tmpl) { 969 | if (err) { 970 | Logger.debug(err); 971 | cb && cb(""); 972 | } else { 973 | tmpl = getInclude(tmpl.toString(), cb) 974 | templatePool[filename] = tmpl; 975 | Logger.debug('update template cache', filename); 976 | } 977 | }; 978 | 979 | if (Settings.templateCache && cachedTemplate) { 980 | if (cachedTemplate.indexOf(includeString) > -1) { 981 | updateCache(null, cachedTemplate); 982 | } else { 983 | cb && cb(templatePool[filename]); 984 | } 985 | } else { 986 | /* 987 | * webSvr.render('/home.tmpl', model) : means related to Setting.root 988 | * webSvr.render('home.tmpl', model) : means related to Setting.home 989 | */ 990 | var firstChar = filename && filename.charAt(0) 991 | , fullpath = path.join(firstChar == '/' ? Settings.root : Settings.home, filename) 992 | ; 993 | 994 | fs.readFile(fullpath, updateCache); 995 | } 996 | }; 997 | 998 | var getInclude = function(tmpl, cb) { 999 | /* 1000 | find and update all the include files, 1001 | will get templates from cache for making the process easier 1002 | include file will be enabled at the next refresh 1003 | */ 1004 | tmpl = (tmpl || '').replace(includeRegExp, function(fileStr) { 1005 | Logger.debug('Include File:', fileStr); 1006 | var includeFile = fileStr.substring(includeBeginLen, fileStr.length - includeAfterLen); 1007 | getFile(includeFile); 1008 | return templatePool[includeFile] || fileStr; 1009 | }); 1010 | 1011 | cb && cb (tmpl) 1012 | return tmpl 1013 | }; 1014 | 1015 | return { 1016 | //render templates 1017 | render: function(tmplUrl, _model, outFn, isRawTmpl) { 1018 | var res = this 1019 | , end = outFn || res.end 1020 | , len = arguments.length 1021 | ; 1022 | 1023 | /* 1024 | format 1025 | */ 1026 | len < 1 && (tmplUrl = {}); 1027 | 1028 | if (len < 2) { 1029 | if (typeof tmplUrl == 'object') { 1030 | _model = tmplUrl; 1031 | /* 1032 | * remove the first '/' make it as related path 1033 | */ 1034 | tmplUrl = res.req.url.substr(1); 1035 | 1036 | var idx = tmplUrl.indexOf('?'); 1037 | idx > -1 && (tmplUrl = tmplUrl.substr(0, idx)); 1038 | } else { 1039 | _model = {}; 1040 | } 1041 | } 1042 | 1043 | /* 1044 | merge model 1045 | */ 1046 | var model = defaultModel 1047 | ? Object.create(defaultModel) 1048 | : {} 1049 | ; 1050 | 1051 | _.extend(model, res.model); 1052 | _.extend(model, _model); 1053 | 1054 | 1055 | /* 1056 | response 1057 | */ 1058 | var render = function(chrunk) { 1059 | var tmplFn; 1060 | 1061 | try { 1062 | tmplFn = engineFunc(chrunk); 1063 | end(tmplFn(model)); 1064 | } catch(err) { 1065 | /* 1066 | refer: https://code.google.com/p/v8/issues/detail?id=1914 1067 | at eval (eval at (unknown source), :2:8) 1068 | */ 1069 | var errStack = err.stack || '' 1070 | , lineStr = ':' 1071 | , idx = errStack.indexOf(lineStr) 1072 | , errmsg = res.req.url + '@' + tmplUrl.substr(0, 400) 1073 | 1074 | if (idx > 0) { 1075 | errStack = errStack.substr(idx) 1076 | errStack = errStack.substr(0, errStack.indexOf(')')) 1077 | var pos = errStack.substring(lineStr.length) 1078 | pos = pos.substring(pos.indexOf(':') + 1, errStack.length - 1) 1079 | pos = parseInt(pos) 1080 | 1081 | if (pos) { 1082 | var strFn = (tmplFn || '').toString() 1083 | var errStr = strFn.substring(pos - 10, pos + 100) 1084 | errmsg += ':' + errStack + '\n ' + errStr 1085 | } 1086 | } 1087 | 1088 | errorRender(err, errmsg, end) 1089 | } 1090 | }; 1091 | 1092 | isRawTmpl 1093 | ? getInclude(tmplUrl, render) 1094 | : getFile(tmplUrl, render) 1095 | } 1096 | , renderRaw: function(rawTmpl, model, outFn) { 1097 | Template.render(rawTmpl, model, outFn, true) 1098 | } 1099 | , engine: function(_engineFunc) { 1100 | engineFunc = _engineFunc; 1101 | } 1102 | , model: function(_model) { 1103 | defaultModel = _model; 1104 | } 1105 | , clear: function() { 1106 | for (var tmpl in templatePool) { 1107 | delete templatePool[tmpl] 1108 | } 1109 | } 1110 | } 1111 | }()); 1112 | 1113 | 1114 | /*****************Web initial codes*************/ 1115 | var fileHandler = function(req, res) { 1116 | 1117 | var url = req.url 1118 | , hasQuery = url.indexOf("?") 1119 | ; 1120 | 1121 | //fs.stat can't recognize the file name with querystring; 1122 | url = hasQuery > 0 ? url.substring(0, hasQuery) : url; 1123 | 1124 | var fullPath = path.join(Settings.home, url); 1125 | 1126 | //Handle path 1127 | var handlePath = function(phyPath) { 1128 | fs.stat(phyPath, function(err, stat) { 1129 | 1130 | //Consider as file not found 1131 | if (err) return self.write404(res); 1132 | 1133 | //Is file? Open this file and send to client. 1134 | if (stat.isFile()) { 1135 | // "If-modified-since" undefined, mark it as 1970-01-01 0:0:0 1136 | var cacheTime = new Date(req.headers["if-modified-since"] || 1); 1137 | 1138 | // The file is modified 1139 | if (Settings.cache && stat.mtime <= cacheTime) { 1140 | res.writeHead(304); 1141 | res.end(); 1142 | 1143 | // Else send "not modifed" 1144 | } else { 1145 | res.setHeader("Last-Modified", stat.mtime.toUTCString()); 1146 | writeFile(res, phyPath); 1147 | } 1148 | } 1149 | 1150 | //Is Directory? 1151 | else if (stat.isDirectory()) { 1152 | handleDefault(phyPath); 1153 | } 1154 | 1155 | //Or write the 404 pages 1156 | else { 1157 | self.write404(res); 1158 | } 1159 | 1160 | }); 1161 | }; 1162 | 1163 | //List all the files and folders. 1164 | var handleDir = function(dirPath) { 1165 | Settings.listDir 1166 | ? ListDir.list(req, res, dirPath) 1167 | : self.write403(res); 1168 | }; 1169 | 1170 | //Handle default page 1171 | var handleDefault = function(dirPath) { 1172 | var defaultPage = Settings.defaultPage; 1173 | 1174 | if (defaultPage) { 1175 | var defaultPath = path.join(dirPath, defaultPage); 1176 | 1177 | fs.exists(defaultPath, function(exists) { 1178 | //If page exists hanle it again 1179 | if (exists) { 1180 | //In order to make it as a dir path for loading static resources 1181 | if (url[url.length - 1] != '/') { 1182 | return res.redirect(url + '/'); 1183 | } 1184 | 1185 | handlePath(defaultPath); 1186 | //If page doesn't exist hanlde the dir again 1187 | } else { 1188 | handleDir(dirPath); 1189 | } 1190 | }); 1191 | } else { 1192 | handleDir(dirPath); 1193 | } 1194 | }; 1195 | 1196 | handlePath(fullPath); 1197 | }; 1198 | 1199 | var requestHandler = function(req, res) { 1200 | //Make request accessible in response object 1201 | res.req = req; 1202 | 1203 | //Response may be shutdown when do the filter, in order not to cause exception, 1204 | //Rewrite the write/writeHead functionalities of current response object 1205 | var endFn = res.end; 1206 | res.end = function() { 1207 | 1208 | //If Content-Type is undefined, using text/html as default 1209 | if (!res.headersSent) { 1210 | !res.getHeader('Content-Type') && res.setHeader('Content-Type', 'text/html; charset=' + (res.charset || 'utf-8')); 1211 | res.cookies && res.cookies.length && res.setHeader('Set-Cookie', res.cookies); 1212 | } 1213 | 1214 | //Execute old end 1215 | endFn.apply(res, arguments); 1216 | //Rewirte write/writeHead on response object 1217 | res.write = res.writeHead = res.setHeader = function() { 1218 | Logger.debug("response is already end, response.write ignored!") 1219 | }; 1220 | 1221 | //Update session when resonse.end is executed 1222 | req.session && req.session.update(); 1223 | }; 1224 | 1225 | res.sendRootFile = function(filePath) { 1226 | writeFile(res, path.join(Settings.root, filePath)); 1227 | }; 1228 | 1229 | res.sendHomeFile = res.sendFile = function(filePath) { 1230 | writeFile(res, path.join(Settings.home, filePath)); 1231 | }; 1232 | 1233 | //301/302 : move permanently 1234 | res.redirect = function(url, status) { 1235 | res.statusCode = status || 302; 1236 | res.setHeader('Location', url); 1237 | res.end(); 1238 | }; 1239 | 1240 | //set content-type 1241 | res.type = function(type) { 1242 | if(type && !res.headersSent) { 1243 | res.getHeader('Content-Type') && res.removeHeader("Content-Type"); 1244 | res.setHeader('Content-Type', (mime.lookup(type) || 'text/plain') + '; charset=' + (res.charset || 'utf-8')); 1245 | } 1246 | }; 1247 | 1248 | //Send sth 1249 | res.send = function(type, content) { 1250 | if (arguments.length < 2) { 1251 | content = type; 1252 | type = null; 1253 | } 1254 | 1255 | if (typeof content == 'object') { 1256 | content = JSON.stringify(content); 1257 | type = type || 'json'; 1258 | } 1259 | 1260 | if (type) { 1261 | typeof type == 'number' 1262 | ? (res.statusCode = 404) 1263 | : res.type(type); 1264 | } 1265 | res.end(content || ''); 1266 | }; 1267 | 1268 | res.cookie = Cookie; 1269 | 1270 | //render template objects 1271 | res.render = Template.render; 1272 | res.renderRaw = Template.renderRaw; 1273 | 1274 | //params in the matched url 1275 | req.params = {}; 1276 | //default model 1277 | res.model = {}; 1278 | 1279 | //initial httprequest 1280 | var filterChain = new FilterChain(function() { 1281 | 1282 | //if handler not match, send the request 1283 | !Handler.handle(req, res) && fileHandler(req, res); 1284 | 1285 | }, req, res); 1286 | 1287 | //Hook FilterChain object on the request 1288 | req.filter = filterChain; 1289 | 1290 | //Handle the first filter 1291 | req.filter.next(); 1292 | }; 1293 | 1294 | var writeFile = function(res, fullPath) { 1295 | fs.readFile(fullPath, function(err, data) { 1296 | if (err) { 1297 | Logger.debug(err); 1298 | return; 1299 | } 1300 | 1301 | res.type(fullPath); 1302 | res.writeHead(200); 1303 | res.end(data, "binary"); 1304 | }); 1305 | }; 1306 | 1307 | //API have function chain 1308 | //Mapper 1309 | self.parseUrl = Mapper.prototype.parseUrl; 1310 | 1311 | //Server ID 1312 | self.newID = SessionParser.prototype.newID; 1313 | 1314 | //Filter 1315 | self.use = Filter.filter; 1316 | self.filter = Filter.filter; 1317 | self.session = Filter.session; 1318 | self.file = Filter.file; 1319 | 1320 | //Handler 1321 | self.get = Handler.url; 1322 | self.url = Handler.url; 1323 | self.handle = Handler.url; 1324 | self.handler = Handler.url; 1325 | self.post = Handler.post; 1326 | self.settings = Settings; 1327 | 1328 | //Template 1329 | self.render = Template.render; 1330 | self.engine = Template.engine; 1331 | self.model = Template.model; 1332 | self.clear = Template.clear; 1333 | 1334 | //Get a full path of a request 1335 | self.getFullPath = function(filePath) { 1336 | return path.join(Settings.home, filePath); 1337 | }; 1338 | 1339 | self.write403 = function(res) { 1340 | res.writeHead(403, {"Content-Type": "text/html"}); 1341 | res.end("Access forbidden!"); 1342 | 1343 | return self; 1344 | }; 1345 | 1346 | self.write404 = function(res) { 1347 | var tmpl404 = Settings["404"]; 1348 | 1349 | res.writeHead(404, {"Content-Type": "text/html"}); 1350 | 1351 | tmpl404 1352 | ? res.render(tmpl404, null) 1353 | : res.end("File not found!"); 1354 | 1355 | return self; 1356 | }; 1357 | 1358 | self.running = false; 1359 | 1360 | //start http server 1361 | self.start = function() { 1362 | 1363 | if (self.running) { 1364 | console.log('Already running, ignored'); 1365 | return self; 1366 | } 1367 | 1368 | //Create http server: Enable by default 1369 | if (Settings.port) { 1370 | var port = Settings.port; 1371 | 1372 | var httpSvr = self.httpSvr || http.createServer(requestHandler); 1373 | httpSvr.listen(port); 1374 | 1375 | console.log("Http server running at" 1376 | ,"home:", Settings.home 1377 | ,"port:", port 1378 | ); 1379 | 1380 | self.httpSvr = httpSvr; 1381 | } 1382 | 1383 | //Create https server: Disable by default 1384 | if ( Settings.httpsPort 1385 | && Settings.httpsKey 1386 | && Settings.httpsCert) { 1387 | 1388 | var httpsPort = Settings.httpsPort; 1389 | 1390 | var httpsSvr = self.httpsSvr || https.createServer({ 1391 | key: Settings.httpsKey, 1392 | cert: Settings.httpsCert 1393 | }, requestHandler); 1394 | 1395 | httpsSvr.listen(httpsPort); 1396 | 1397 | console.log("Https server running at" 1398 | ,"home:", Settings.home 1399 | ,"port:", httpsPort 1400 | ); 1401 | 1402 | self.httpsSvr = httpsSvr; 1403 | } 1404 | 1405 | self.running = true; 1406 | 1407 | return self; 1408 | }; 1409 | 1410 | //stop http server 1411 | self.stop = function() { 1412 | self.httpSvr && self.httpSvr.close(); 1413 | self.httpsSvr && self.httpsSvr.close(); 1414 | self.running = false; 1415 | 1416 | return self; 1417 | }; 1418 | 1419 | //init 1420 | self.init = function() { 1421 | //Update the default value of Settings 1422 | _.extend(Settings, options); 1423 | 1424 | SessionStore = Settings.sessionDir ? FileStore : MemoryStore; 1425 | 1426 | //Start by default 1427 | self.start(); 1428 | 1429 | return self; 1430 | }; 1431 | 1432 | //property: filters & handlers 1433 | define(self, 'filters', { 1434 | get: function() { 1435 | return Filter.filters 1436 | }, 1437 | set: function(filters) { 1438 | Filter.filters = filters; 1439 | } 1440 | }); 1441 | 1442 | define(self, 'handlers', { 1443 | get: function() { 1444 | return Handler.handlers; 1445 | }, 1446 | set: function(handlers) { 1447 | Handler.handlers = handlers; 1448 | } 1449 | }); 1450 | 1451 | define(self, 'sessionStore', { 1452 | get: function() { 1453 | return SessionStore; 1454 | }, 1455 | set: function(sessionStore) { 1456 | if (sessionStore && sessionStore.get && sessionStore.set && sessionStore.del) { 1457 | SessionStore = sessionStore; 1458 | } else { 1459 | Logger.debug('Your session storage do not have interface: get/set/del'); 1460 | } 1461 | } 1462 | }); 1463 | 1464 | //init 1465 | self.init(); 1466 | 1467 | return self; 1468 | 1469 | }; --------------------------------------------------------------------------------