Compare commits
222 Commits
Author | SHA1 | Date |
---|---|---|
|
be5c886c97 | 3 years ago |
|
787d839086 | 3 years ago |
|
9e921d5909 | 3 years ago |
|
68f6fe2b96 | 3 years ago |
|
7286385833 | 3 years ago |
|
3dcc43578b | 3 years ago |
|
1ea6b6e99d | 3 years ago |
|
7c613bc788 | 3 years ago |
|
cb4809195b | 3 years ago |
|
00d84614c2 | 3 years ago |
|
52e7cef7ef | 3 years ago |
|
fbff1bc201 | 3 years ago |
|
7af15cc32d | 4 years ago |
|
7f397ce753 | 4 years ago |
|
8f8b039f65 | 4 years ago |
|
eeaf2d7b18 | 4 years ago |
|
db0b7d6444 | 4 years ago |
|
db6e7603f9 | 4 years ago |
|
ad5d7549d7 | 4 years ago |
|
5d2965ffc5 | 5 years ago |
|
f255928af7 | 5 years ago |
|
a108dbadc5 | 5 years ago |
|
c409aca080 | 5 years ago |
|
219424550b | 5 years ago |
|
f147acb51c | 5 years ago |
|
9a692ed652 | 5 years ago |
|
3a17c86a0f | 5 years ago |
|
677a22987a | 5 years ago |
|
89d912c6ff | 5 years ago |
|
4cac6713ef | 5 years ago |
|
f3b0de745b | 5 years ago |
|
cc8a99752f | 5 years ago |
|
6853d077e7 | 5 years ago |
|
80a2b6f0dd | 5 years ago |
|
4f68b3d7d6 | 5 years ago |
|
ef0ca40533 | 5 years ago |
|
f372ef18de | 5 years ago |
|
181a3a2bfa | 5 years ago |
|
61d08afb3b | 5 years ago |
|
1ba025328d | 5 years ago |
|
a79fb39f54 | 5 years ago |
|
3a72d74537 | 5 years ago |
|
e9ae74b7a9 | 5 years ago |
|
c305e9a83d | 5 years ago |
|
16bce4c83d | 5 years ago |
|
661997cd73 | 5 years ago |
|
159f989d08 | 5 years ago |
|
139df62ec4 | 5 years ago |
|
bae6387bb7 | 5 years ago |
|
bb7b9571a7 | 5 years ago |
|
a4dc29fb2b | 5 years ago |
|
342f56ce1a | 5 years ago |
|
05ecc90764 | 5 years ago |
|
69cf505a90 | 5 years ago |
|
9f41993566 | 5 years ago |
|
5c9311fb85 | 5 years ago |
|
5a8d52a5e3 | 5 years ago |
|
0f145b4444 | 5 years ago |
|
aef4bb5edb | 5 years ago |
|
36c854ef1b | 5 years ago |
|
edd428ff37 | 5 years ago |
|
0612ba001e | 5 years ago |
|
064680003d | 5 years ago |
|
655f2af45a | 5 years ago |
|
ce03749c2f | 5 years ago |
|
f6084b4339 | 5 years ago |
|
9b0a5ff0a3 | 5 years ago |
|
1fff48568f | 6 years ago |
|
b4c666fbcf | 6 years ago |
|
b866c33c93 | 6 years ago |
|
035cf0e91e | 6 years ago |
|
f3838ab4a8 | 7 years ago |
|
bf2b1c957a | 7 years ago |
|
86bbc1899d | 7 years ago |
|
d41d7491d4 | 7 years ago |
|
5fb43eb67c | 7 years ago |
|
1eeef4ede4 | 7 years ago |
|
ebc749c5e0 | 7 years ago |
|
b0bbb72f35 | 7 years ago |
|
2213c3874a | 7 years ago |
|
6ebd72a86c | 7 years ago |
|
b6814a1445 | 7 years ago |
|
e3d18efdc6 | 7 years ago |
|
869fb65738 | 7 years ago |
|
56b939124e | 7 years ago |
|
ee1c1c0856 | 7 years ago |
|
b087ac8dd1 | 7 years ago |
|
d922667f56 | 7 years ago |
|
5f6fefa7a6 | 7 years ago |
|
faa7e679ca | 7 years ago |
|
cd3bf26dbe | 7 years ago |
|
830dc1bc43 | 7 years ago |
|
dc0f151a7f | 7 years ago |
|
7f625e22f7 | 7 years ago |
|
528b7b07a8 | 7 years ago |
|
2b81e67ce7 | 7 years ago |
|
827e7b51b5 | 7 years ago |
|
16d529e935 | 7 years ago |
|
ad7702aaf4 | 7 years ago |
|
f5fbc8d19e | 7 years ago |
|
0a8923bf12 | 7 years ago |
|
4d572a2ec0 | 7 years ago |
|
d9a53d3e6e | 7 years ago |
|
8da37ea5de | 8 years ago |
|
ff0fccd6c2 | 8 years ago |
|
63c4576633 | 8 years ago |
|
b31d143bcd | 8 years ago |
|
0d8aec8d61 | 8 years ago |
|
1f9fdd205d | 8 years ago |
|
cdd0cf3739 | 8 years ago |
|
ba5c6b8d16 | 8 years ago |
|
cfef588283 | 8 years ago |
|
318c5f7ba6 | 8 years ago |
|
ee03e7cd78 | 8 years ago |
|
3b6934e348 | 8 years ago |
|
f161cc33b4 | 8 years ago |
|
40f1f2588e | 8 years ago |
|
e4e025f67e | 8 years ago |
|
e12805a8aa | 8 years ago |
|
e76c845f16 | 8 years ago |
|
072418695e | 8 years ago |
|
584b66bc66 | 8 years ago |
|
f8db455f74 | 8 years ago |
|
f19c5d1049 | 8 years ago |
|
c5b859ec98 | 8 years ago |
|
2ee93a7409 | 8 years ago |
|
bf1dbb68b8 | 8 years ago |
|
cf28e23d8e | 8 years ago |
|
5939dec185 | 8 years ago |
|
3ed1d775ac | 8 years ago |
|
87b1c76aaf | 8 years ago |
|
4599203bdf | 8 years ago |
|
d66bc9a6c4 | 8 years ago |
|
80f0618736 | 8 years ago |
|
ac2bceefbb | 8 years ago |
|
dbf4f6b5dd | 8 years ago |
|
8e9205cecc | 8 years ago |
|
e54a860172 | 8 years ago |
|
5a8697cdd8 | 8 years ago |
|
091ea973a8 | 8 years ago |
|
939b7221ab | 8 years ago |
|
934aaf7f51 | 8 years ago |
|
930e21ccb7 | 9 years ago |
|
eb5c8eef6a | 9 years ago |
|
03dd611a86 | 9 years ago |
|
f24376b192 | 9 years ago |
|
eea359d0ec | 9 years ago |
|
af9a71549b | 9 years ago |
|
3178676fba | 9 years ago |
|
3bdfab8219 | 9 years ago |
|
a3a24d9765 | 9 years ago |
|
8afb53e77e | 9 years ago |
|
d6d9cf40f9 | 9 years ago |
|
1010a142e2 | 9 years ago |
|
d3db5e2a5d | 9 years ago |
|
0209375865 | 9 years ago |
|
00a9d9c312 | 9 years ago |
|
6835eef468 | 9 years ago |
|
4626fd9c8d | 9 years ago |
|
fbb6e63c37 | 9 years ago |
|
84c909a5db | 9 years ago |
|
45e19bc7cc | 10 years ago |
|
233bc6ff16 | 10 years ago |
|
360b325ced | 11 years ago |
|
e93f98112b | 11 years ago |
|
05cb051bc8 | 11 years ago |
|
031cdd738a | 11 years ago |
|
c92ab077c0 | 11 years ago |
|
6c31389327 | 11 years ago |
|
a8d4f3c300 | 11 years ago |
|
ab029eae2f | 11 years ago |
|
447d0aae76 | 11 years ago |
|
4870158430 | 11 years ago |
|
0471b059a0 | 11 years ago |
|
5bbe50b481 | 11 years ago |
|
bda2749879 | 11 years ago |
|
028aa96b13 | 11 years ago |
|
2deda5b68a | 11 years ago |
|
ee7098457e | 11 years ago |
|
7a08960414 | 12 years ago |
|
89909747f1 | 12 years ago |
|
202e695e07 | 12 years ago |
|
48e8e79659 | 12 years ago |
|
abb49f2cf3 | 12 years ago |
|
d1cd2a5213 | 13 years ago |
|
27317844e0 | 13 years ago |
|
ee74e2fa90 | 13 years ago |
|
5d8bd2e6f8 | 13 years ago |
|
cd4c7aeab8 | 13 years ago |
|
e37c3cf1b9 | 13 years ago |
|
8858bab985 | 13 years ago |
|
afb0c332cc | 13 years ago |
|
82c58c5c0c | 13 years ago |
|
46bdd27431 | 13 years ago |
|
1adfba1a37 | 13 years ago |
|
54e55b1b0d | 13 years ago |
|
08d37cc7f7 | 13 years ago |
|
aa781957e8 | 13 years ago |
|
c00477c93c | 13 years ago |
|
035f09ac05 | 13 years ago |
|
36e00bb29e | 13 years ago |
|
10623873e8 | 13 years ago |
|
e536ba1019 | 13 years ago |
|
85fc36d710 | 13 years ago |
|
5d5ae164f3 | 13 years ago |
|
79309c75df | 13 years ago |
|
4b58c8d356 | 13 years ago |
|
8f0d6260b0 | 13 years ago |
|
93a83a35da | 13 years ago |
|
4efc5d47d9 | 13 years ago |
|
ff8ef54e34 | 13 years ago |
|
814a49812a | 13 years ago |
|
e0610bc1be | 13 years ago |
|
962976c204 | 13 years ago |
|
16080bdc16 | 13 years ago |
|
20ce741341 | 13 years ago |
|
13bb094fb3 | 13 years ago |
|
b43a55ffda | 13 years ago |
|
45cbdcce70 | 13 years ago |
|
1950cc8db0 | 13 years ago |
|
90cfe0ec57 | 13 years ago |
|
87e28548b9 | 13 years ago |
@ -0,0 +1,8 @@ |
|||||||
|
Dockerfile |
||||||
|
.git |
||||||
|
npm-debug.log |
||||||
|
node_modules |
||||||
|
*.swp |
||||||
|
*.swo |
||||||
|
data |
||||||
|
*.DS_Store |
@ -0,0 +1,2 @@ |
|||||||
|
**/*.min.js |
||||||
|
config.js |
@ -0,0 +1,25 @@ |
|||||||
|
{ |
||||||
|
"env": { |
||||||
|
"es6": true, |
||||||
|
"node": true |
||||||
|
}, |
||||||
|
"extends": "eslint:recommended", |
||||||
|
"rules": { |
||||||
|
"indent": [ |
||||||
|
"error", |
||||||
|
2 |
||||||
|
], |
||||||
|
"linebreak-style": [ |
||||||
|
"error", |
||||||
|
"unix" |
||||||
|
], |
||||||
|
"quotes": [ |
||||||
|
"error", |
||||||
|
"single" |
||||||
|
], |
||||||
|
"semi": [ |
||||||
|
"error", |
||||||
|
"always" |
||||||
|
] |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
* @toptal/site-acquisition-eng |
@ -0,0 +1,30 @@ |
|||||||
|
name: Close inactive issues and PRs |
||||||
|
on: |
||||||
|
workflow_dispatch: |
||||||
|
schedule: |
||||||
|
- cron: "30 1 * * *" |
||||||
|
|
||||||
|
jobs: |
||||||
|
close-stale: |
||||||
|
runs-on: ubuntu-latest |
||||||
|
permissions: |
||||||
|
issues: write |
||||||
|
pull-requests: write |
||||||
|
steps: |
||||||
|
- uses: actions/stale@v3 |
||||||
|
with: |
||||||
|
days-before-stale: 30 |
||||||
|
days-before-close: 14 |
||||||
|
stale-issue-label: "stale" |
||||||
|
stale-pr-label: "stale" |
||||||
|
|
||||||
|
exempt-issue-labels: backlog,triage,nostale |
||||||
|
exempt-pr-labels: backlog,triage,nostale |
||||||
|
|
||||||
|
stale-pr-message: "This PR is stale because it has been open for 30 days with no activity." |
||||||
|
close-pr-message: "This PR was closed because it has been inactive for 14 days since being marked as stale." |
||||||
|
|
||||||
|
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." |
||||||
|
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." |
||||||
|
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }} |
@ -1,5 +1,7 @@ |
|||||||
npm-debug.log |
npm-debug.log |
||||||
|
node_modules |
||||||
*.swp |
*.swp |
||||||
*.swo |
*.swo |
||||||
data |
data |
||||||
*.DS_Store |
*.DS_Store |
||||||
|
docker-compose.override.yml |
||||||
|
@ -0,0 +1,68 @@ |
|||||||
|
FROM node:14.8.0-stretch |
||||||
|
|
||||||
|
RUN mkdir -p /usr/src/app && \ |
||||||
|
chown node:node /usr/src/app |
||||||
|
|
||||||
|
USER node:node |
||||||
|
|
||||||
|
WORKDIR /usr/src/app |
||||||
|
|
||||||
|
COPY --chown=node:node . . |
||||||
|
|
||||||
|
RUN npm install && \ |
||||||
|
npm install redis@0.8.1 && \ |
||||||
|
npm install pg@4.1.1 && \ |
||||||
|
npm install memcached@2.2.2 && \ |
||||||
|
npm install aws-sdk@2.738.0 && \ |
||||||
|
npm install rethinkdbdash@2.3.31 |
||||||
|
|
||||||
|
ENV STORAGE_TYPE=memcached \ |
||||||
|
STORAGE_HOST=127.0.0.1 \ |
||||||
|
STORAGE_PORT=11211\ |
||||||
|
STORAGE_EXPIRE_SECONDS=2592000\ |
||||||
|
STORAGE_DB=2 \ |
||||||
|
STORAGE_AWS_BUCKET= \ |
||||||
|
STORAGE_AWS_REGION= \ |
||||||
|
STORAGE_USENAME= \ |
||||||
|
STORAGE_PASSWORD= \ |
||||||
|
STORAGE_FILEPATH= |
||||||
|
|
||||||
|
ENV LOGGING_LEVEL=verbose \ |
||||||
|
LOGGING_TYPE=Console \ |
||||||
|
LOGGING_COLORIZE=true |
||||||
|
|
||||||
|
ENV HOST=0.0.0.0\ |
||||||
|
PORT=7777\ |
||||||
|
KEY_LENGTH=10\ |
||||||
|
MAX_LENGTH=400000\ |
||||||
|
STATIC_MAX_AGE=86400\ |
||||||
|
RECOMPRESS_STATIC_ASSETS=true |
||||||
|
|
||||||
|
ENV KEYGENERATOR_TYPE=phonetic \ |
||||||
|
KEYGENERATOR_KEYSPACE= |
||||||
|
|
||||||
|
ENV RATELIMITS_NORMAL_TOTAL_REQUESTS=500\ |
||||||
|
RATELIMITS_NORMAL_EVERY_MILLISECONDS=60000 \ |
||||||
|
RATELIMITS_WHITELIST_TOTAL_REQUESTS= \ |
||||||
|
RATELIMITS_WHITELIST_EVERY_MILLISECONDS= \ |
||||||
|
# comma separated list for the whitelisted \ |
||||||
|
RATELIMITS_WHITELIST=example1.whitelist,example2.whitelist \ |
||||||
|
\ |
||||||
|
RATELIMITS_BLACKLIST_TOTAL_REQUESTS= \ |
||||||
|
RATELIMITS_BLACKLIST_EVERY_MILLISECONDS= \ |
||||||
|
# comma separated list for the blacklisted \ |
||||||
|
RATELIMITS_BLACKLIST=example1.blacklist,example2.blacklist |
||||||
|
ENV DOCUMENTS=about=./about.md |
||||||
|
|
||||||
|
EXPOSE ${PORT} |
||||||
|
STOPSIGNAL SIGINT |
||||||
|
ENTRYPOINT [ "bash", "docker-entrypoint.sh" ] |
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s \ |
||||||
|
--retries=3 CMD [ "sh", "-c", "echo -n 'curl localhost:7777... '; \ |
||||||
|
(\ |
||||||
|
curl -sf localhost:7777 > /dev/null\ |
||||||
|
) && echo OK || (\ |
||||||
|
echo Fail && exit 2\ |
||||||
|
)"] |
||||||
|
CMD ["npm", "start"] |
@ -0,0 +1,12 @@ |
|||||||
|
version: '3.0' |
||||||
|
services: |
||||||
|
haste-server: |
||||||
|
build: . |
||||||
|
environment: |
||||||
|
- STORAGE_TYPE=memcached |
||||||
|
- STORAGE_HOST=memcached |
||||||
|
- STORAGE_PORT=11211 |
||||||
|
ports: |
||||||
|
- 7777:7777 |
||||||
|
memcached: |
||||||
|
image: memcached:latest |
@ -0,0 +1,108 @@ |
|||||||
|
const { |
||||||
|
HOST, |
||||||
|
PORT, |
||||||
|
KEY_LENGTH, |
||||||
|
MAX_LENGTH, |
||||||
|
STATIC_MAX_AGE, |
||||||
|
RECOMPRESS_STATIC_ASSETS, |
||||||
|
STORAGE_TYPE, |
||||||
|
STORAGE_HOST, |
||||||
|
STORAGE_PORT, |
||||||
|
STORAGE_EXPIRE_SECONDS, |
||||||
|
STORAGE_DB, |
||||||
|
STORAGE_AWS_BUCKET, |
||||||
|
STORAGE_AWS_REGION, |
||||||
|
STORAGE_PASSWORD, |
||||||
|
STORAGE_USERNAME, |
||||||
|
STORAGE_FILEPATH, |
||||||
|
LOGGING_LEVEL, |
||||||
|
LOGGING_TYPE, |
||||||
|
LOGGING_COLORIZE, |
||||||
|
KEYGENERATOR_TYPE, |
||||||
|
KEY_GENERATOR_KEYSPACE, |
||||||
|
RATE_LIMITS_NORMAL_TOTAL_REQUESTS, |
||||||
|
RATE_LIMITS_NORMAL_EVERY_MILLISECONDS, |
||||||
|
RATE_LIMITS_WHITELIST_TOTAL_REQUESTS, |
||||||
|
RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS, |
||||||
|
RATE_LIMITS_WHITELIST, |
||||||
|
RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS, |
||||||
|
RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS, |
||||||
|
RATE_LIMITS_BLACKLIST, |
||||||
|
DOCUMENTS, |
||||||
|
} = process.env; |
||||||
|
|
||||||
|
const config = { |
||||||
|
host: HOST, |
||||||
|
port: Number(PORT), |
||||||
|
|
||||||
|
keyLength: Number(KEY_LENGTH), |
||||||
|
|
||||||
|
maxLength: Number(MAX_LENGTH), |
||||||
|
|
||||||
|
staticMaxAge: Number(STATIC_MAX_AGE), |
||||||
|
|
||||||
|
recompressStaticAssets: RECOMPRESS_STATIC_ASSETS, |
||||||
|
|
||||||
|
logging: [ |
||||||
|
{ |
||||||
|
level: LOGGING_LEVEL, |
||||||
|
type: LOGGING_TYPE, |
||||||
|
colorize: LOGGING_COLORIZE, |
||||||
|
}, |
||||||
|
], |
||||||
|
|
||||||
|
keyGenerator: { |
||||||
|
type: KEYGENERATOR_TYPE, |
||||||
|
keyspace: KEY_GENERATOR_KEYSPACE, |
||||||
|
}, |
||||||
|
|
||||||
|
rateLimits: { |
||||||
|
whitelist: RATE_LIMITS_WHITELIST ? RATE_LIMITS_WHITELIST.split(",") : [], |
||||||
|
blacklist: RATE_LIMITS_BLACKLIST ? RATE_LIMITS_BLACKLIST.split(",") : [], |
||||||
|
categories: { |
||||||
|
normal: { |
||||||
|
totalRequests: RATE_LIMITS_NORMAL_TOTAL_REQUESTS, |
||||||
|
every: RATE_LIMITS_NORMAL_EVERY_MILLISECONDS, |
||||||
|
}, |
||||||
|
whitelist: |
||||||
|
RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS || |
||||||
|
RATE_LIMITS_WHITELIST_TOTAL_REQUESTS |
||||||
|
? { |
||||||
|
totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS, |
||||||
|
every: RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS, |
||||||
|
} |
||||||
|
: null, |
||||||
|
blacklist: |
||||||
|
RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS || |
||||||
|
RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS |
||||||
|
? { |
||||||
|
totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS, |
||||||
|
every: RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS, |
||||||
|
} |
||||||
|
: null, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
storage: { |
||||||
|
type: STORAGE_TYPE, |
||||||
|
host: STORAGE_HOST, |
||||||
|
port: Number(STORAGE_PORT), |
||||||
|
expire: Number(STORAGE_EXPIRE_SECONDS), |
||||||
|
bucket: STORAGE_AWS_BUCKET, |
||||||
|
region: STORAGE_AWS_REGION, |
||||||
|
connectionUrl: `postgres://${STORAGE_USERNAME}:${STORAGE_PASSWORD}@${STORAGE_HOST}:${STORAGE_PORT}/${STORAGE_DB}`, |
||||||
|
db: STORAGE_DB, |
||||||
|
user: STORAGE_USERNAME, |
||||||
|
password: STORAGE_PASSWORD, |
||||||
|
path: STORAGE_FILEPATH, |
||||||
|
}, |
||||||
|
|
||||||
|
documents: DOCUMENTS |
||||||
|
? DOCUMENTS.split(",").reduce((acc, item) => { |
||||||
|
const keyAndValueArray = item.replace(/\s/g, "").split("="); |
||||||
|
return { ...acc, [keyAndValueArray[0]]: keyAndValueArray[1] }; |
||||||
|
}, {}) |
||||||
|
: null, |
||||||
|
}; |
||||||
|
|
||||||
|
console.log(JSON.stringify(config)); |
@ -0,0 +1,9 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
# We use this file to translate environmental variables to .env files used by the application |
||||||
|
|
||||||
|
set -e |
||||||
|
|
||||||
|
node ./docker-entrypoint.js > ./config.js |
||||||
|
|
||||||
|
exec "$@" |
@ -0,0 +1,56 @@ |
|||||||
|
/*global require,module,process*/ |
||||||
|
|
||||||
|
var AWS = require('aws-sdk'); |
||||||
|
var winston = require('winston'); |
||||||
|
|
||||||
|
var AmazonS3DocumentStore = function(options) { |
||||||
|
this.expire = options.expire; |
||||||
|
this.bucket = options.bucket; |
||||||
|
this.client = new AWS.S3({region: options.region}); |
||||||
|
}; |
||||||
|
|
||||||
|
AmazonS3DocumentStore.prototype.get = function(key, callback, skipExpire) { |
||||||
|
var _this = this; |
||||||
|
|
||||||
|
var req = { |
||||||
|
Bucket: _this.bucket, |
||||||
|
Key: key |
||||||
|
}; |
||||||
|
|
||||||
|
_this.client.getObject(req, function(err, data) { |
||||||
|
if(err) { |
||||||
|
callback(false); |
||||||
|
} |
||||||
|
else { |
||||||
|
callback(data.Body.toString('utf-8')); |
||||||
|
if (_this.expire && !skipExpire) { |
||||||
|
winston.warn('amazon s3 store cannot set expirations on keys'); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
AmazonS3DocumentStore.prototype.set = function(key, data, callback, skipExpire) { |
||||||
|
var _this = this; |
||||||
|
|
||||||
|
var req = { |
||||||
|
Bucket: _this.bucket, |
||||||
|
Key: key, |
||||||
|
Body: data, |
||||||
|
ContentType: 'text/plain' |
||||||
|
}; |
||||||
|
|
||||||
|
_this.client.putObject(req, function(err, data) { |
||||||
|
if (err) { |
||||||
|
callback(false); |
||||||
|
} |
||||||
|
else { |
||||||
|
callback(true); |
||||||
|
if (_this.expire && !skipExpire) { |
||||||
|
winston.warn('amazon s3 store cannot set expirations on keys'); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = AmazonS3DocumentStore; |
@ -0,0 +1,89 @@ |
|||||||
|
/*global require,module,process*/ |
||||||
|
|
||||||
|
const Datastore = require('@google-cloud/datastore'); |
||||||
|
const winston = require('winston'); |
||||||
|
|
||||||
|
class GoogleDatastoreDocumentStore { |
||||||
|
|
||||||
|
// Create a new store with options
|
||||||
|
constructor(options) { |
||||||
|
this.kind = "Haste"; |
||||||
|
this.expire = options.expire; |
||||||
|
this.datastore = new Datastore(); |
||||||
|
} |
||||||
|
|
||||||
|
// Save file in a key
|
||||||
|
set(key, data, callback, skipExpire) { |
||||||
|
var expireTime = (skipExpire || this.expire === undefined) ? null : new Date(Date.now() + this.expire * 1000); |
||||||
|
|
||||||
|
var taskKey = this.datastore.key([this.kind, key]) |
||||||
|
var task = { |
||||||
|
key: taskKey, |
||||||
|
data: [ |
||||||
|
{ |
||||||
|
name: 'value', |
||||||
|
value: data, |
||||||
|
excludeFromIndexes: true |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'expiration', |
||||||
|
value: expireTime |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
this.datastore.insert(task).then(() => { |
||||||
|
callback(true); |
||||||
|
}) |
||||||
|
.catch(err => { |
||||||
|
callback(false); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// Get a file from a key
|
||||||
|
get(key, callback, skipExpire) { |
||||||
|
var taskKey = this.datastore.key([this.kind, key]) |
||||||
|
|
||||||
|
this.datastore.get(taskKey).then((entity) => { |
||||||
|
if (skipExpire || entity[0]["expiration"] == null) { |
||||||
|
callback(entity[0]["value"]); |
||||||
|
} |
||||||
|
else { |
||||||
|
// check for expiry
|
||||||
|
if (entity[0]["expiration"] < new Date()) { |
||||||
|
winston.info("document expired", {key: key, expiration: entity[0]["expiration"], check: new Date(null)}); |
||||||
|
callback(false); |
||||||
|
} |
||||||
|
else { |
||||||
|
// update expiry
|
||||||
|
var task = { |
||||||
|
key: taskKey, |
||||||
|
data: [ |
||||||
|
{ |
||||||
|
name: 'value', |
||||||
|
value: entity[0]["value"], |
||||||
|
excludeFromIndexes: true |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'expiration', |
||||||
|
value: new Date(Date.now() + this.expire * 1000) |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
this.datastore.update(task).then(() => { |
||||||
|
}) |
||||||
|
.catch(err => { |
||||||
|
winston.error("failed to update expiration", {error: err}); |
||||||
|
}); |
||||||
|
callback(entity[0]["value"]); |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
.catch(err => { |
||||||
|
winston.error("Error retrieving value from Google Datastore", {error: err}); |
||||||
|
callback(false); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = GoogleDatastoreDocumentStore; |
@ -1,45 +1,54 @@ |
|||||||
var memcached = require('memcache'); |
const memcached = require('memcached'); |
||||||
var winston = require('winston'); |
const winston = require('winston'); |
||||||
|
|
||||||
|
class MemcachedDocumentStore { |
||||||
|
|
||||||
// Create a new store with options
|
// Create a new store with options
|
||||||
var MemcachedDocumentStore = function(options) { |
constructor(options) { |
||||||
this.expire = options.expire; |
this.expire = options.expire; |
||||||
if (!MemcachedDocumentStore.client) { |
|
||||||
MemcachedDocumentStore.connect(options); |
const host = options.host || '127.0.0.1'; |
||||||
|
const port = options.port || 11211; |
||||||
|
const url = `${host}:${port}`; |
||||||
|
this.connect(url); |
||||||
} |
} |
||||||
}; |
|
||||||
|
|
||||||
// Create a connection
|
// Create a connection
|
||||||
MemcachedDocumentStore.connect = function(options) { |
connect(url) { |
||||||
var host = options.host || '127.0.0.1'; |
this.client = new memcached(url); |
||||||
var port = options.port || 11211; |
|
||||||
this.client = new memcached.Client(port, host); |
winston.info(`connecting to memcached on ${url}`); |
||||||
this.client.connect(); |
|
||||||
this.client.on('connect', function() { |
this.client.on('failure', function(error) { |
||||||
winston.info('connected to memcached on ' + host + ':' + port); |
winston.info('error connecting to memcached', {error}); |
||||||
}); |
|
||||||
this.client.on('error', function(e) { |
|
||||||
winston.info('error connecting to memcached', { error: e }); |
|
||||||
}); |
}); |
||||||
}; |
} |
||||||
|
|
||||||
// Save file in a key
|
// Save file in a key
|
||||||
MemcachedDocumentStore.prototype.set = |
set(key, data, callback, skipExpire) { |
||||||
function(key, data, callback, skipExpire) { |
this.client.set(key, data, skipExpire ? 0 : this.expire || 0, (error) => { |
||||||
MemcachedDocumentStore.client.set(key, data, function(err, reply) { |
callback(!error); |
||||||
err ? callback(false) : callback(true); |
}); |
||||||
}, skipExpire ? 0 : this.expire); |
} |
||||||
}; |
|
||||||
|
|
||||||
// Get a file from a key
|
// Get a file from a key
|
||||||
MemcachedDocumentStore.prototype.get = function(key, callback, skipExpire) { |
get(key, callback, skipExpire) { |
||||||
var _this = this; |
this.client.get(key, (error, data) => { |
||||||
MemcachedDocumentStore.client.get(key, function(err, reply) { |
const value = error ? false : data; |
||||||
callback(err ? false : reply); |
|
||||||
if (_this.expire && !skipExpire) { |
callback(value); |
||||||
winston.warn('store does not currently push forward expirations on GET'); |
|
||||||
|
// Update the key so that the expiration is pushed forward
|
||||||
|
if (value && !skipExpire) { |
||||||
|
this.set(key, data, (updateSucceeded) => { |
||||||
|
if (!updateSucceeded) { |
||||||
|
winston.error('failed to update expiration on GET', {key}); |
||||||
|
} |
||||||
|
}, skipExpire); |
||||||
} |
} |
||||||
}); |
}); |
||||||
}; |
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
module.exports = MemcachedDocumentStore; |
module.exports = MemcachedDocumentStore; |
||||||
|
@ -0,0 +1,88 @@ |
|||||||
|
|
||||||
|
|
||||||
|
var MongoClient = require('mongodb').MongoClient, |
||||||
|
winston = require('winston'); |
||||||
|
|
||||||
|
var MongoDocumentStore = function (options) { |
||||||
|
this.expire = options.expire; |
||||||
|
this.connectionUrl = process.env.DATABASE_URl || options.connectionUrl; |
||||||
|
}; |
||||||
|
|
||||||
|
MongoDocumentStore.prototype.set = function (key, data, callback, skipExpire) { |
||||||
|
var now = Math.floor(new Date().getTime() / 1000), |
||||||
|
that = this; |
||||||
|
|
||||||
|
this.safeConnect(function (err, db) { |
||||||
|
if (err) |
||||||
|
return callback(false); |
||||||
|
|
||||||
|
db.collection('entries').update({ |
||||||
|
'entry_id': key, |
||||||
|
$or: [ |
||||||
|
{ expiration: -1 }, |
||||||
|
{ expiration: { $gt: now } } |
||||||
|
] |
||||||
|
}, { |
||||||
|
'entry_id': key, |
||||||
|
'value': data, |
||||||
|
'expiration': that.expire && !skipExpire ? that.expire + now : -1 |
||||||
|
}, { |
||||||
|
upsert: true |
||||||
|
}, function (err, existing) { |
||||||
|
if (err) { |
||||||
|
winston.error('error persisting value to mongodb', { error: err }); |
||||||
|
return callback(false); |
||||||
|
} |
||||||
|
|
||||||
|
callback(true); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
MongoDocumentStore.prototype.get = function (key, callback, skipExpire) { |
||||||
|
var now = Math.floor(new Date().getTime() / 1000), |
||||||
|
that = this; |
||||||
|
|
||||||
|
this.safeConnect(function (err, db) { |
||||||
|
if (err) |
||||||
|
return callback(false); |
||||||
|
|
||||||
|
db.collection('entries').findOne({ |
||||||
|
'entry_id': key, |
||||||
|
$or: [ |
||||||
|
{ expiration: -1 }, |
||||||
|
{ expiration: { $gt: now } } |
||||||
|
] |
||||||
|
}, function (err, entry) { |
||||||
|
if (err) { |
||||||
|
winston.error('error persisting value to mongodb', { error: err }); |
||||||
|
return callback(false); |
||||||
|
} |
||||||
|
|
||||||
|
callback(entry === null ? false : entry.value); |
||||||
|
|
||||||
|
if (entry !== null && entry.expiration !== -1 && that.expire && !skipExpire) { |
||||||
|
db.collection('entries').update({ |
||||||
|
'entry_id': key |
||||||
|
}, { |
||||||
|
$set: { |
||||||
|
'expiration': that.expire + now |
||||||
|
} |
||||||
|
}, function (err, result) { }); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
MongoDocumentStore.prototype.safeConnect = function (callback) { |
||||||
|
MongoClient.connect(this.connectionUrl, function (err, db) { |
||||||
|
if (err) { |
||||||
|
winston.error('error connecting to mongodb', { error: err }); |
||||||
|
callback(err); |
||||||
|
} else { |
||||||
|
callback(undefined, db); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports = MongoDocumentStore; |
@ -0,0 +1,80 @@ |
|||||||
|
/*global require,module,process*/ |
||||||
|
|
||||||
|
var winston = require('winston'); |
||||||
|
const {Pool} = require('pg'); |
||||||
|
|
||||||
|
// create table entries (id serial primary key, key varchar(255) not null, value text not null, expiration int, unique(key));
|
||||||
|
|
||||||
|
// A postgres document store
|
||||||
|
var PostgresDocumentStore = function (options) { |
||||||
|
this.expireJS = parseInt(options.expire, 10); |
||||||
|
|
||||||
|
const connectionString = process.env.DATABASE_URL || options.connectionUrl; |
||||||
|
this.pool = new Pool({connectionString}); |
||||||
|
}; |
||||||
|
|
||||||
|
PostgresDocumentStore.prototype = { |
||||||
|
|
||||||
|
// Set a given key
|
||||||
|
set: function (key, data, callback, skipExpire) { |
||||||
|
var now = Math.floor(new Date().getTime() / 1000); |
||||||
|
var that = this; |
||||||
|
this.safeConnect(function (err, client, done) { |
||||||
|
if (err) { return callback(false); } |
||||||
|
client.query('INSERT INTO entries (key, value, expiration) VALUES ($1, $2, $3)', [ |
||||||
|
key, |
||||||
|
data, |
||||||
|
that.expireJS && !skipExpire ? that.expireJS + now : null |
||||||
|
], function (err) { |
||||||
|
if (err) { |
||||||
|
winston.error('error persisting value to postgres', { error: err }); |
||||||
|
return callback(false); |
||||||
|
} |
||||||
|
callback(true); |
||||||
|
done(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
// Get a given key's data
|
||||||
|
get: function (key, callback, skipExpire) { |
||||||
|
var now = Math.floor(new Date().getTime() / 1000); |
||||||
|
var that = this; |
||||||
|
this.safeConnect(function (err, client, done) { |
||||||
|
if (err) { return callback(false); } |
||||||
|
client.query('SELECT id,value,expiration from entries where KEY = $1 and (expiration IS NULL or expiration > $2)', [key, now], function (err, result) { |
||||||
|
if (err) { |
||||||
|
winston.error('error retrieving value from postgres', { error: err }); |
||||||
|
return callback(false); |
||||||
|
} |
||||||
|
callback(result.rows.length ? result.rows[0].value : false); |
||||||
|
if (result.rows.length && that.expireJS && !skipExpire) { |
||||||
|
client.query('UPDATE entries SET expiration = $1 WHERE ID = $2', [ |
||||||
|
that.expireJS + now, |
||||||
|
result.rows[0].id |
||||||
|
], function (err) { |
||||||
|
if (!err) { |
||||||
|
done(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} else { |
||||||
|
done(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
// A connection wrapper
|
||||||
|
safeConnect: function (callback) { |
||||||
|
this.pool.connect((error, client, done) => { |
||||||
|
if (error) { |
||||||
|
winston.error('error connecting to postgres', {error}); |
||||||
|
callback(error); |
||||||
|
} else { |
||||||
|
callback(undefined, client, done); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports = PostgresDocumentStore; |
@ -0,0 +1,46 @@ |
|||||||
|
const crypto = require('crypto'); |
||||||
|
const rethink = require('rethinkdbdash'); |
||||||
|
const winston = require('winston'); |
||||||
|
|
||||||
|
const md5 = (str) => { |
||||||
|
const md5sum = crypto.createHash('md5'); |
||||||
|
md5sum.update(str); |
||||||
|
return md5sum.digest('hex'); |
||||||
|
}; |
||||||
|
|
||||||
|
class RethinkDBStore { |
||||||
|
constructor(options) { |
||||||
|
this.client = rethink({ |
||||||
|
silent: true, |
||||||
|
host: options.host || '127.0.0.1', |
||||||
|
port: options.port || 28015, |
||||||
|
db: options.db || 'haste', |
||||||
|
user: options.user || 'admin', |
||||||
|
password: options.password || '' |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
set(key, data, callback) { |
||||||
|
this.client.table('uploads').insert({ id: md5(key), data: data }).run((error) => { |
||||||
|
if (error) { |
||||||
|
callback(false); |
||||||
|
winston.error('failed to insert to table', error); |
||||||
|
return; |
||||||
|
} |
||||||
|
callback(true); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
get(key, callback) { |
||||||
|
this.client.table('uploads').get(md5(key)).run((error, result) => { |
||||||
|
if (error || !result) { |
||||||
|
callback(false); |
||||||
|
if (error) winston.error('failed to insert to table', error); |
||||||
|
return; |
||||||
|
} |
||||||
|
callback(result.data); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = RethinkDBStore; |
@ -0,0 +1,32 @@ |
|||||||
|
const fs = require('fs'); |
||||||
|
|
||||||
|
module.exports = class DictionaryGenerator { |
||||||
|
|
||||||
|
constructor(options, readyCallback) { |
||||||
|
// Check options format
|
||||||
|
if (!options) throw Error('No options passed to generator'); |
||||||
|
if (!options.path) throw Error('No dictionary path specified in options'); |
||||||
|
|
||||||
|
// Load dictionary
|
||||||
|
fs.readFile(options.path, 'utf8', (err, data) => { |
||||||
|
if (err) throw err; |
||||||
|
|
||||||
|
this.dictionary = data.split(/[\n\r]+/); |
||||||
|
|
||||||
|
if (readyCallback) readyCallback(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// Generates a dictionary-based key, of keyLength words
|
||||||
|
createKey(keyLength) { |
||||||
|
let text = ''; |
||||||
|
|
||||||
|
for (let i = 0; i < keyLength; i++) { |
||||||
|
const index = Math.floor(Math.random() * this.dictionary.length); |
||||||
|
text += this.dictionary[index]; |
||||||
|
} |
||||||
|
|
||||||
|
return text; |
||||||
|
} |
||||||
|
|
||||||
|
}; |
@ -1,32 +1,27 @@ |
|||||||
// Draws inspiration from pwgen and http://tools.arantius.com/password
|
// Draws inspiration from pwgen and http://tools.arantius.com/password
|
||||||
var PhoneticKeyGenerator = function(options) { |
|
||||||
// No options
|
|
||||||
}; |
|
||||||
|
|
||||||
// Generate a phonetic key
|
const randOf = (collection) => { |
||||||
PhoneticKeyGenerator.prototype.createKey = function(keyLength) { |
return () => { |
||||||
var text = ''; |
return collection[Math.floor(Math.random() * collection.length)]; |
||||||
for (var i = 0; i < keyLength; i++) { |
}; |
||||||
text += (i % 2 == 0) ? this.randConsonant() : this.randVowel(); |
|
||||||
} |
|
||||||
return text; |
|
||||||
}; |
}; |
||||||
|
|
||||||
PhoneticKeyGenerator.consonants = 'bcdfghjklmnpqrstvwxy'; |
// Helper methods to get an random vowel or consonant
|
||||||
PhoneticKeyGenerator.vowels = 'aeiou'; |
const randVowel = randOf('aeiou'); |
||||||
|
const randConsonant = randOf('bcdfghjklmnpqrstvwxyz'); |
||||||
|
|
||||||
// Get an random vowel
|
module.exports = class PhoneticKeyGenerator { |
||||||
PhoneticKeyGenerator.prototype.randVowel = function() { |
|
||||||
return PhoneticKeyGenerator.vowels[ |
|
||||||
Math.floor(Math.random() * PhoneticKeyGenerator.vowels.length) |
|
||||||
]; |
|
||||||
}; |
|
||||||
|
|
||||||
// Get an random consonant
|
// Generate a phonetic key of alternating consonant & vowel
|
||||||
PhoneticKeyGenerator.prototype.randConsonant = function() { |
createKey(keyLength) { |
||||||
return PhoneticKeyGenerator.consonants[ |
let text = ''; |
||||||
Math.floor(Math.random() * PhoneticKeyGenerator.consonants.length) |
const start = Math.round(Math.random()); |
||||||
]; |
|
||||||
}; |
|
||||||
|
|
||||||
module.exports = PhoneticKeyGenerator; |
for (let i = 0; i < keyLength; i++) { |
||||||
|
text += (i % 2 == start) ? randConsonant() : randVowel(); |
||||||
|
} |
||||||
|
|
||||||
|
return text; |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
@ -1,19 +1,20 @@ |
|||||||
var RandomKeyGenerator = function(options) { |
module.exports = class RandomKeyGenerator { |
||||||
if (!options) { |
|
||||||
options = {}; |
// Initialize a new generator with the given keySpace
|
||||||
} |
constructor(options = {}) { |
||||||
this.keyspace = options.keyspace || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
this.keyspace = options.keyspace || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
||||||
}; |
} |
||||||
|
|
||||||
// Generate a random key
|
// Generate a key of the given length
|
||||||
RandomKeyGenerator.prototype.createKey = function(keyLength) { |
createKey(keyLength) { |
||||||
var text = ''; |
var text = ''; |
||||||
var index; |
|
||||||
for (var i = 0; i < keyLength; i++) { |
for (var i = 0; i < keyLength; i++) { |
||||||
index = Math.floor(Math.random() * this.keyspace.length); |
const index = Math.floor(Math.random() * this.keyspace.length); |
||||||
text += this.keyspace.charAt(index); |
text += this.keyspace.charAt(index); |
||||||
} |
} |
||||||
|
|
||||||
return text; |
return text; |
||||||
}; |
} |
||||||
|
|
||||||
module.exports = RandomKeyGenerator; |
}; |
||||||
|
@ -1 +0,0 @@ |
|||||||
../mocha/bin/_mocha |
|
@ -1 +0,0 @@ |
|||||||
../mocha/bin/mocha |
|
@ -1 +0,0 @@ |
|||||||
../uglify-js/bin/uglifyjs |
|
@ -1,11 +0,0 @@ |
|||||||
*.markdown |
|
||||||
*.md |
|
||||||
.git* |
|
||||||
Makefile |
|
||||||
benchmarks/ |
|
||||||
docs/ |
|
||||||
examples/ |
|
||||||
install.sh |
|
||||||
support/ |
|
||||||
test/ |
|
||||||
.DS_Store |
|
@ -1,24 +0,0 @@ |
|||||||
(The MIT License) |
|
||||||
|
|
||||||
Copyright (c) 2010 Sencha Inc. |
|
||||||
Copyright (c) 2011 LearnBoost |
|
||||||
Copyright (c) 2011 TJ Holowaychuk |
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining |
|
||||||
a copy of this software and associated documentation files (the |
|
||||||
'Software'), to deal in the Software without restriction, including |
|
||||||
without limitation the rights to use, copy, modify, merge, publish, |
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to |
|
||||||
permit persons to whom the Software is furnished to do so, subject to |
|
||||||
the following conditions: |
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be |
|
||||||
included in all copies or substantial portions of the Software. |
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, |
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
@ -1,2 +0,0 @@ |
|||||||
|
|
||||||
module.exports = require('./lib/connect'); |
|
@ -1,81 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - Cache |
|
||||||
* Copyright(c) 2011 Sencha Inc. |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Expose `Cache`. |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = Cache; |
|
||||||
|
|
||||||
/** |
|
||||||
* LRU cache store. |
|
||||||
* |
|
||||||
* @param {Number} limit |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function Cache(limit) { |
|
||||||
this.store = {}; |
|
||||||
this.keys = []; |
|
||||||
this.limit = limit; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Touch `key`, promoting the object. |
|
||||||
* |
|
||||||
* @param {String} key |
|
||||||
* @param {Number} i |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
Cache.prototype.touch = function(key, i){ |
|
||||||
this.keys.splice(i,1); |
|
||||||
this.keys.push(key); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Remove `key`. |
|
||||||
* |
|
||||||
* @param {String} key |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
Cache.prototype.remove = function(key){ |
|
||||||
delete this.store[key]; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Get the object stored for `key`. |
|
||||||
* |
|
||||||
* @param {String} key |
|
||||||
* @return {Array} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
Cache.prototype.get = function(key){ |
|
||||||
return this.store[key]; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Add a cache `key`. |
|
||||||
* |
|
||||||
* @param {String} key |
|
||||||
* @return {Array} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
Cache.prototype.add = function(key){ |
|
||||||
// initialize store
|
|
||||||
var len = this.keys.push(key); |
|
||||||
|
|
||||||
// limit reached, invalid LRU
|
|
||||||
if (len > this.limit) this.remove(this.keys.shift()); |
|
||||||
|
|
||||||
var arr = this.store[key] = []; |
|
||||||
arr.createdAt = new Date; |
|
||||||
return arr; |
|
||||||
}; |
|
@ -1,106 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var HTTPServer = require('./http').Server |
|
||||||
, HTTPSServer = require('./https').Server |
|
||||||
, fs = require('fs'); |
|
||||||
|
|
||||||
// node patches
|
|
||||||
|
|
||||||
require('./patch'); |
|
||||||
|
|
||||||
// expose createServer() as the module
|
|
||||||
|
|
||||||
exports = module.exports = createServer; |
|
||||||
|
|
||||||
/** |
|
||||||
* Framework version. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.version = '1.8.5'; |
|
||||||
|
|
||||||
/** |
|
||||||
* Initialize a new `connect.HTTPServer` with the middleware |
|
||||||
* passed to this function. When an object is passed _first_, |
|
||||||
* we assume these are the tls options, and return a `connect.HTTPSServer`. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* An example HTTP server, accepting several middleware. |
|
||||||
* |
|
||||||
* var server = connect.createServer( |
|
||||||
* connect.logger() |
|
||||||
* , connect.static(__dirname + '/public') |
|
||||||
* ); |
|
||||||
* |
|
||||||
* An HTTPS server, utilizing the same middleware as above. |
|
||||||
* |
|
||||||
* var server = connect.createServer( |
|
||||||
* { key: key, cert: cert } |
|
||||||
* , connect.logger() |
|
||||||
* , connect.static(__dirname + '/public') |
|
||||||
* ); |
|
||||||
* |
|
||||||
* Alternatively with connect 1.0 we may omit `createServer()`. |
|
||||||
* |
|
||||||
* connect( |
|
||||||
* connect.logger() |
|
||||||
* , connect.static(__dirname + '/public') |
|
||||||
* ).listen(3000); |
|
||||||
* |
|
||||||
* @param {Object|Function} ... |
|
||||||
* @return {Server} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
function createServer() { |
|
||||||
if ('object' == typeof arguments[0]) { |
|
||||||
return new HTTPSServer(arguments[0], Array.prototype.slice.call(arguments, 1)); |
|
||||||
} else { |
|
||||||
return new HTTPServer(Array.prototype.slice.call(arguments)); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// support connect.createServer()
|
|
||||||
|
|
||||||
exports.createServer = createServer; |
|
||||||
|
|
||||||
// auto-load getters
|
|
||||||
|
|
||||||
exports.middleware = {}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Auto-load bundled middleware with getters. |
|
||||||
*/ |
|
||||||
|
|
||||||
fs.readdirSync(__dirname + '/middleware').forEach(function(filename){ |
|
||||||
if (/\.js$/.test(filename)) { |
|
||||||
var name = filename.substr(0, filename.lastIndexOf('.')); |
|
||||||
exports.middleware.__defineGetter__(name, function(){ |
|
||||||
return require('./middleware/' + name); |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
// expose utils
|
|
||||||
|
|
||||||
exports.utils = require('./utils'); |
|
||||||
|
|
||||||
// expose getters as first-class exports
|
|
||||||
|
|
||||||
exports.utils.merge(exports, exports.middleware); |
|
||||||
|
|
||||||
// expose constructors
|
|
||||||
|
|
||||||
exports.HTTPServer = HTTPServer; |
|
||||||
exports.HTTPSServer = HTTPSServer; |
|
||||||
|
|
@ -1,217 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - HTTPServer |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var http = require('http') |
|
||||||
, parse = require('url').parse |
|
||||||
, assert = require('assert'); |
|
||||||
|
|
||||||
// environment
|
|
||||||
|
|
||||||
var env = process.env.NODE_ENV || 'development'; |
|
||||||
|
|
||||||
/** |
|
||||||
* Initialize a new `Server` with the given `middleware`. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* var server = connect.createServer( |
|
||||||
* connect.favicon() |
|
||||||
* , connect.logger() |
|
||||||
* , connect.static(__dirname + '/public') |
|
||||||
* ); |
|
||||||
* |
|
||||||
* @params {Array} middleware
|
|
||||||
* @return {Server} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
var Server = exports.Server = function HTTPServer(middleware) { |
|
||||||
this.stack = []; |
|
||||||
middleware.forEach(function(fn){ |
|
||||||
this.use(fn); |
|
||||||
}, this); |
|
||||||
http.Server.call(this, this.handle); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Inherit from `http.Server.prototype`. |
|
||||||
*/ |
|
||||||
|
|
||||||
Server.prototype.__proto__ = http.Server.prototype; |
|
||||||
|
|
||||||
/** |
|
||||||
* Utilize the given middleware `handle` to the given `route`, |
|
||||||
* defaulting to _/_. This "route" is the mount-point for the |
|
||||||
* middleware, when given a value other than _/_ the middleware |
|
||||||
* is only effective when that segment is present in the request's |
|
||||||
* pathname. |
|
||||||
* |
|
||||||
* For example if we were to mount a function at _/admin_, it would |
|
||||||
* be invoked on _/admin_, and _/admin/settings_, however it would |
|
||||||
* not be invoked for _/_, or _/posts_. |
|
||||||
* |
|
||||||
* This is effectively the same as passing middleware to `connect.createServer()`, |
|
||||||
* however provides a progressive api. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* var server = connect.createServer(); |
|
||||||
* server.use(connect.favicon()); |
|
||||||
* server.use(connect.logger()); |
|
||||||
* server.use(connect.static(__dirname + '/public')); |
|
||||||
* |
|
||||||
* If we wanted to prefix static files with _/public_, we could |
|
||||||
* "mount" the `static()` middleware: |
|
||||||
* |
|
||||||
* server.use('/public', connect.static(__dirname + '/public')); |
|
||||||
* |
|
||||||
* This api is chainable, meaning the following is valid: |
|
||||||
* |
|
||||||
* connect.createServer() |
|
||||||
* .use(connect.favicon()) |
|
||||||
* .use(connect.logger()) |
|
||||||
* .use(connect.static(__dirname + '/public')) |
|
||||||
* .listen(3000); |
|
||||||
* |
|
||||||
* @param {String|Function} route or handle |
|
||||||
* @param {Function} handle |
|
||||||
* @return {Server} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
Server.prototype.use = function(route, handle){ |
|
||||||
this.route = '/'; |
|
||||||
|
|
||||||
// default route to '/'
|
|
||||||
if ('string' != typeof route) { |
|
||||||
handle = route; |
|
||||||
route = '/'; |
|
||||||
} |
|
||||||
|
|
||||||
// wrap sub-apps
|
|
||||||
if ('function' == typeof handle.handle) { |
|
||||||
var server = handle; |
|
||||||
server.route = route; |
|
||||||
handle = function(req, res, next) { |
|
||||||
server.handle(req, res, next); |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
// wrap vanilla http.Servers
|
|
||||||
if (handle instanceof http.Server) { |
|
||||||
handle = handle.listeners('request')[0]; |
|
||||||
} |
|
||||||
|
|
||||||
// normalize route to not trail with slash
|
|
||||||
if ('/' == route[route.length - 1]) { |
|
||||||
route = route.substr(0, route.length - 1); |
|
||||||
} |
|
||||||
|
|
||||||
// add the middleware
|
|
||||||
this.stack.push({ route: route, handle: handle }); |
|
||||||
|
|
||||||
// allow chaining
|
|
||||||
return this; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Handle server requests, punting them down |
|
||||||
* the middleware stack. |
|
||||||
* |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
Server.prototype.handle = function(req, res, out) { |
|
||||||
var writeHead = res.writeHead |
|
||||||
, stack = this.stack |
|
||||||
, removed = '' |
|
||||||
, index = 0; |
|
||||||
|
|
||||||
function next(err) { |
|
||||||
var layer, path, c; |
|
||||||
req.url = removed + req.url; |
|
||||||
req.originalUrl = req.originalUrl || req.url; |
|
||||||
removed = ''; |
|
||||||
|
|
||||||
layer = stack[index++]; |
|
||||||
|
|
||||||
// all done
|
|
||||||
if (!layer || res.headerSent) { |
|
||||||
// but wait! we have a parent
|
|
||||||
if (out) return out(err); |
|
||||||
|
|
||||||
// error
|
|
||||||
if (err) { |
|
||||||
var msg = 'production' == env |
|
||||||
? 'Internal Server Error' |
|
||||||
: err.stack || err.toString(); |
|
||||||
|
|
||||||
// output to stderr in a non-test env
|
|
||||||
if ('test' != env) console.error(err.stack || err.toString()); |
|
||||||
|
|
||||||
// unable to respond
|
|
||||||
if (res.headerSent) return req.socket.destroy(); |
|
||||||
|
|
||||||
res.statusCode = 500; |
|
||||||
res.setHeader('Content-Type', 'text/plain'); |
|
||||||
if ('HEAD' == req.method) return res.end(); |
|
||||||
res.end(msg); |
|
||||||
} else { |
|
||||||
res.statusCode = 404; |
|
||||||
res.setHeader('Content-Type', 'text/plain'); |
|
||||||
if ('HEAD' == req.method) return res.end(); |
|
||||||
res.end('Cannot ' + req.method + ' ' + req.url); |
|
||||||
} |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
try { |
|
||||||
path = parse(req.url).pathname; |
|
||||||
if (undefined == path) path = '/'; |
|
||||||
|
|
||||||
// skip this layer if the route doesn't match.
|
|
||||||
if (0 != path.indexOf(layer.route)) return next(err); |
|
||||||
|
|
||||||
c = path[layer.route.length]; |
|
||||||
if (c && '/' != c && '.' != c) return next(err); |
|
||||||
|
|
||||||
// Call the layer handler
|
|
||||||
// Trim off the part of the url that matches the route
|
|
||||||
removed = layer.route; |
|
||||||
req.url = req.url.substr(removed.length); |
|
||||||
|
|
||||||
// Ensure leading slash
|
|
||||||
if ('/' != req.url[0]) req.url = '/' + req.url; |
|
||||||
|
|
||||||
var arity = layer.handle.length; |
|
||||||
if (err) { |
|
||||||
if (arity === 4) { |
|
||||||
layer.handle(err, req, res, next); |
|
||||||
} else { |
|
||||||
next(err); |
|
||||||
} |
|
||||||
} else if (arity < 4) { |
|
||||||
layer.handle(req, res, next); |
|
||||||
} else { |
|
||||||
next(); |
|
||||||
} |
|
||||||
} catch (e) { |
|
||||||
if (e instanceof assert.AssertionError) { |
|
||||||
console.error(e.stack + '\n'); |
|
||||||
next(e); |
|
||||||
} else { |
|
||||||
next(e); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
next(); |
|
||||||
}; |
|
@ -1,47 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - HTTPServer |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var HTTPServer = require('./http').Server |
|
||||||
, https = require('https'); |
|
||||||
|
|
||||||
/** |
|
||||||
* Initialize a new `Server` with the given |
|
||||||
*`options` and `middleware`. The HTTPS api |
|
||||||
* is identical to the [HTTP](http.html) server, |
|
||||||
* however TLS `options` must be provided before |
|
||||||
* passing in the optional middleware. |
|
||||||
* |
|
||||||
* @params {Object} options |
|
||||||
* @params {Array} middleawre |
|
||||||
* @return {Server} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
var Server = exports.Server = function HTTPSServer(options, middleware) { |
|
||||||
this.stack = []; |
|
||||||
middleware.forEach(function(fn){ |
|
||||||
this.use(fn); |
|
||||||
}, this); |
|
||||||
https.Server.call(this, options, this.handle); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Inherit from `http.Server.prototype`. |
|
||||||
*/ |
|
||||||
|
|
||||||
Server.prototype.__proto__ = https.Server.prototype; |
|
||||||
|
|
||||||
// mixin HTTPServer methods
|
|
||||||
|
|
||||||
Object.keys(HTTPServer.prototype).forEach(function(method){ |
|
||||||
Server.prototype[method] = HTTPServer.prototype[method]; |
|
||||||
}); |
|
@ -1,46 +0,0 @@ |
|||||||
|
|
||||||
/** |
|
||||||
* # Connect |
|
||||||
* |
|
||||||
* Connect is a middleware framework for node, |
|
||||||
* shipping with over 11 bundled middleware and a rich choice of |
|
||||||
* [3rd-party middleware](https://github.com/senchalabs/connect/wiki).
|
|
||||||
* |
|
||||||
* Installation: |
|
||||||
*
|
|
||||||
* $ npm install connect |
|
||||||
*
|
|
||||||
* API: |
|
||||||
* |
|
||||||
* - [connect](connect.html) general |
|
||||||
* - [http](http.html) http server |
|
||||||
* - [https](https.html) https server |
|
||||||
* |
|
||||||
* Middleware: |
|
||||||
* |
|
||||||
* - [logger](middleware-logger.html) request logger with custom format support |
|
||||||
* - [csrf](middleware-csrf.html) Cross-site request forgery protection |
|
||||||
* - [basicAuth](middleware-basicAuth.html) basic http authentication |
|
||||||
* - [bodyParser](middleware-bodyParser.html) extensible request body parser |
|
||||||
* - [cookieParser](middleware-cookieParser.html) cookie parser |
|
||||||
* - [session](middleware-session.html) session management support with bundled [MemoryStore](middleware-session-memory.html) |
|
||||||
* - [compiler](middleware-compiler.html) static asset compiler (sass, less, coffee-script, etc) |
|
||||||
* - [methodOverride](middleware-methodOverride.html) faux HTTP method support |
|
||||||
* - [responseTime](middleware-responseTime.html) calculates response-time and exposes via X-Response-Time |
|
||||||
* - [router](middleware-router.html) provides rich Sinatra / Express-like routing |
|
||||||
* - [staticCache](middleware-staticCache.html) memory cache layer for the static() middleware |
|
||||||
* - [static](middleware-static.html) streaming static file server supporting `Range` and more |
|
||||||
* - [directory](middleware-directory.html) directory listing middleware |
|
||||||
* - [vhost](middleware-vhost.html) virtual host sub-domain mapping middleware |
|
||||||
* - [favicon](middleware-favicon.html) efficient favicon server (with default icon) |
|
||||||
* - [limit](middleware-limit.html) limit the bytesize of request bodies |
|
||||||
* - [profiler](middleware-profiler.html) request profiler reporting response-time, memory usage, etc |
|
||||||
* - [query](middleware-query.html) automatic querystring parser, populating `req.query` |
|
||||||
* - [errorHandler](middleware-errorHandler.html) flexible error handler |
|
||||||
* |
|
||||||
* Internals: |
|
||||||
* |
|
||||||
* - connect [utilities](utils.html) |
|
||||||
* - node monkey [patches](patch.html) |
|
||||||
* |
|
||||||
*/ |
|
@ -1,93 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - basicAuth |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var utils = require('../utils') |
|
||||||
, unauthorized = utils.unauthorized |
|
||||||
, badRequest = utils.badRequest; |
|
||||||
|
|
||||||
/** |
|
||||||
* Enfore basic authentication by providing a `callback(user, pass)`, |
|
||||||
* which must return `true` in order to gain access. Alternatively an async |
|
||||||
* method is provided as well, invoking `callback(user, pass, callback)`. Populates |
|
||||||
* `req.remoteUser`. The final alternative is simply passing username / password |
|
||||||
* strings. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* connect(connect.basicAuth('username', 'password')); |
|
||||||
* |
|
||||||
* connect( |
|
||||||
* connect.basicAuth(function(user, pass){ |
|
||||||
* return 'tj' == user & 'wahoo' == pass; |
|
||||||
* }) |
|
||||||
* ); |
|
||||||
* |
|
||||||
* connect( |
|
||||||
* connect.basicAuth(function(user, pass, fn){ |
|
||||||
* User.authenticate({ user: user, pass: pass }, fn); |
|
||||||
* }) |
|
||||||
* ); |
|
||||||
* |
|
||||||
* @param {Function|String} callback or username |
|
||||||
* @param {String} realm |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = function basicAuth(callback, realm) { |
|
||||||
var username, password; |
|
||||||
|
|
||||||
// user / pass strings
|
|
||||||
if ('string' == typeof callback) { |
|
||||||
username = callback; |
|
||||||
password = realm; |
|
||||||
if ('string' != typeof password) throw new Error('password argument required'); |
|
||||||
realm = arguments[2]; |
|
||||||
callback = function(user, pass){ |
|
||||||
return user == username && pass == password; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
realm = realm || 'Authorization Required'; |
|
||||||
|
|
||||||
return function(req, res, next) { |
|
||||||
var authorization = req.headers.authorization; |
|
||||||
|
|
||||||
if (req.remoteUser) return next(); |
|
||||||
if (!authorization) return unauthorized(res, realm); |
|
||||||
|
|
||||||
var parts = authorization.split(' ') |
|
||||||
, scheme = parts[0] |
|
||||||
, credentials = new Buffer(parts[1], 'base64').toString().split(':'); |
|
||||||
|
|
||||||
if ('Basic' != scheme) return badRequest(res); |
|
||||||
|
|
||||||
// async
|
|
||||||
if (callback.length >= 3) { |
|
||||||
var pause = utils.pause(req); |
|
||||||
callback(credentials[0], credentials[1], function(err, user){ |
|
||||||
if (err || !user) return unauthorized(res, realm); |
|
||||||
req.remoteUser = user; |
|
||||||
next(); |
|
||||||
pause.resume(); |
|
||||||
}); |
|
||||||
// sync
|
|
||||||
} else { |
|
||||||
if (callback(credentials[0], credentials[1])) { |
|
||||||
req.remoteUser = credentials[0]; |
|
||||||
next(); |
|
||||||
} else { |
|
||||||
unauthorized(res, realm); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
@ -1,196 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - bodyParser |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var qs = require('qs') |
|
||||||
, formidable = require('formidable'); |
|
||||||
|
|
||||||
/** |
|
||||||
* Extract the mime type from the given request's |
|
||||||
* _Content-Type_ header. |
|
||||||
* |
|
||||||
* @param {IncomingMessage} req |
|
||||||
* @return {String} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function mime(req) { |
|
||||||
var str = req.headers['content-type'] || ''; |
|
||||||
return str.split(';')[0]; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Parse request bodies. |
|
||||||
* |
|
||||||
* By default _application/json_, _application/x-www-form-urlencoded_, |
|
||||||
* and _multipart/form-data_ are supported, however you may map `connect.bodyParser.parse[contentType]` |
|
||||||
* to a function receiving `(req, options, callback)`. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* connect.createServer( |
|
||||||
* connect.bodyParser() |
|
||||||
* , function(req, res) { |
|
||||||
* res.end('viewing user ' + req.body.user.name); |
|
||||||
* } |
|
||||||
* ); |
|
||||||
* |
|
||||||
* $ curl -d 'user[name]=tj' http://localhost/
|
|
||||||
* $ curl -d '{"user":{"name":"tj"}}' -H "Content-Type: application/json" http://localhost/
|
|
||||||
* |
|
||||||
* Multipart req.files: |
|
||||||
* |
|
||||||
* As a security measure files are stored in a separate object, stored |
|
||||||
* as `req.files`. This prevents attacks that may potentially alter |
|
||||||
* filenames, and depending on the application gain access to restricted files. |
|
||||||
* |
|
||||||
* Multipart configuration: |
|
||||||
* |
|
||||||
* The `options` passed are provided to each parser function. |
|
||||||
* The _multipart/form-data_ parser merges these with formidable's |
|
||||||
* IncomingForm object, allowing you to tweak the upload directory, |
|
||||||
* size limits, etc. For example you may wish to retain the file extension |
|
||||||
* and change the upload directory: |
|
||||||
* |
|
||||||
* server.use(bodyParser({ uploadDir: '/www/mysite.com/uploads' })); |
|
||||||
* |
|
||||||
* View [node-formidable](https://github.com/felixge/node-formidable) for more information.
|
|
||||||
* |
|
||||||
* If you wish to use formidable directly within your app, and do not |
|
||||||
* desire this behaviour for multipart requests simply remove the |
|
||||||
* parser:
|
|
||||||
* |
|
||||||
* delete connect.bodyParser.parse['multipart/form-data']; |
|
||||||
* |
|
||||||
* Or |
|
||||||
* |
|
||||||
* delete express.bodyParser.parse['multipart/form-data']; |
|
||||||
* |
|
||||||
* @param {Object} options |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
exports = module.exports = function bodyParser(options){ |
|
||||||
options = options || {}; |
|
||||||
return function bodyParser(req, res, next) { |
|
||||||
if (req.body) return next(); |
|
||||||
req.body = {}; |
|
||||||
|
|
||||||
if ('GET' == req.method || 'HEAD' == req.method) return next(); |
|
||||||
var parser = exports.parse[mime(req)]; |
|
||||||
if (parser) { |
|
||||||
parser(req, options, next); |
|
||||||
} else { |
|
||||||
next(); |
|
||||||
} |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Parsers. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.parse = {}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Parse application/x-www-form-urlencoded. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.parse['application/x-www-form-urlencoded'] = function(req, options, fn){ |
|
||||||
var buf = ''; |
|
||||||
req.setEncoding('utf8'); |
|
||||||
req.on('data', function(chunk){ buf += chunk }); |
|
||||||
req.on('end', function(){ |
|
||||||
try { |
|
||||||
req.body = buf.length |
|
||||||
? qs.parse(buf) |
|
||||||
: {}; |
|
||||||
fn(); |
|
||||||
} catch (err){ |
|
||||||
fn(err); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Parse application/json. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.parse['application/json'] = function(req, options, fn){ |
|
||||||
var buf = ''; |
|
||||||
req.setEncoding('utf8'); |
|
||||||
req.on('data', function(chunk){ buf += chunk }); |
|
||||||
req.on('end', function(){ |
|
||||||
try { |
|
||||||
req.body = buf.length |
|
||||||
? JSON.parse(buf) |
|
||||||
: {}; |
|
||||||
fn(); |
|
||||||
} catch (err){ |
|
||||||
fn(err); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Parse multipart/form-data. |
|
||||||
* |
|
||||||
* TODO: make multiple support optional |
|
||||||
* TODO: revisit "error" flag if it's a formidable bug |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.parse['multipart/form-data'] = function(req, options, fn){ |
|
||||||
var form = new formidable.IncomingForm |
|
||||||
, data = {} |
|
||||||
, files = {} |
|
||||||
, done; |
|
||||||
|
|
||||||
Object.keys(options).forEach(function(key){ |
|
||||||
form[key] = options[key]; |
|
||||||
}); |
|
||||||
|
|
||||||
function ondata(name, val, data){ |
|
||||||
if (Array.isArray(data[name])) { |
|
||||||
data[name].push(val); |
|
||||||
} else if (data[name]) { |
|
||||||
data[name] = [data[name], val]; |
|
||||||
} else { |
|
||||||
data[name] = val; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
form.on('field', function(name, val){ |
|
||||||
ondata(name, val, data); |
|
||||||
}); |
|
||||||
|
|
||||||
form.on('file', function(name, val){ |
|
||||||
ondata(name, val, files); |
|
||||||
}); |
|
||||||
|
|
||||||
form.on('error', function(err){ |
|
||||||
fn(err); |
|
||||||
done = true; |
|
||||||
}); |
|
||||||
|
|
||||||
form.on('end', function(){ |
|
||||||
if (done) return; |
|
||||||
try { |
|
||||||
req.body = qs.parse(data); |
|
||||||
req.files = qs.parse(files); |
|
||||||
fn(); |
|
||||||
} catch (err) { |
|
||||||
fn(err); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
form.parse(req); |
|
||||||
}; |
|
@ -1,163 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - compiler |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var fs = require('fs') |
|
||||||
, path = require('path') |
|
||||||
, parse = require('url').parse; |
|
||||||
|
|
||||||
/** |
|
||||||
* Require cache. |
|
||||||
*/ |
|
||||||
|
|
||||||
var cache = {}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Setup compiler. |
|
||||||
* |
|
||||||
* Options: |
|
||||||
* |
|
||||||
* - `src` Source directory, defaults to **CWD**. |
|
||||||
* - `dest` Destination directory, defaults `src`. |
|
||||||
* - `enable` Array of enabled compilers. |
|
||||||
* |
|
||||||
* Compilers: |
|
||||||
* |
|
||||||
* - `sass` Compiles sass to css |
|
||||||
* - `less` Compiles less to css |
|
||||||
* - `coffeescript` Compiles coffee to js |
|
||||||
* |
|
||||||
* @param {Object} options |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
exports = module.exports = function compiler(options){ |
|
||||||
options = options || {}; |
|
||||||
|
|
||||||
var srcDir = options.src || process.cwd() |
|
||||||
, destDir = options.dest || srcDir |
|
||||||
, enable = options.enable; |
|
||||||
|
|
||||||
if (!enable || enable.length === 0) { |
|
||||||
throw new Error('compiler\'s "enable" option is not set, nothing will be compiled.'); |
|
||||||
} |
|
||||||
|
|
||||||
return function compiler(req, res, next){ |
|
||||||
if ('GET' != req.method) return next(); |
|
||||||
var pathname = parse(req.url).pathname; |
|
||||||
for (var i = 0, len = enable.length; i < len; ++i) { |
|
||||||
var name = enable[i] |
|
||||||
, compiler = compilers[name]; |
|
||||||
if (compiler.match.test(pathname)) { |
|
||||||
var src = (srcDir + pathname).replace(compiler.match, compiler.ext) |
|
||||||
, dest = destDir + pathname; |
|
||||||
|
|
||||||
// Compare mtimes
|
|
||||||
fs.stat(src, function(err, srcStats){ |
|
||||||
if (err) { |
|
||||||
if ('ENOENT' == err.code) { |
|
||||||
next(); |
|
||||||
} else { |
|
||||||
next(err); |
|
||||||
} |
|
||||||
} else { |
|
||||||
fs.stat(dest, function(err, destStats){ |
|
||||||
if (err) { |
|
||||||
// Oh snap! it does not exist, compile it
|
|
||||||
if ('ENOENT' == err.code) { |
|
||||||
compile(); |
|
||||||
} else { |
|
||||||
next(err); |
|
||||||
} |
|
||||||
} else { |
|
||||||
// Source has changed, compile it
|
|
||||||
if (srcStats.mtime > destStats.mtime) { |
|
||||||
compile(); |
|
||||||
} else { |
|
||||||
// Defer file serving
|
|
||||||
next(); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
// Compile to the destination
|
|
||||||
function compile() { |
|
||||||
fs.readFile(src, 'utf8', function(err, str){ |
|
||||||
if (err) { |
|
||||||
next(err); |
|
||||||
} else { |
|
||||||
compiler.compile(str, function(err, str){ |
|
||||||
if (err) { |
|
||||||
next(err); |
|
||||||
} else { |
|
||||||
fs.writeFile(dest, str, 'utf8', function(err){ |
|
||||||
next(err); |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
next(); |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Bundled compilers: |
|
||||||
* |
|
||||||
* - [sass](http://github.com/visionmedia/sass.js) to _css_
|
|
||||||
* - [less](http://github.com/cloudhead/less.js) to _css_
|
|
||||||
* - [coffee](http://github.com/jashkenas/coffee-script) to _js_
|
|
||||||
*/ |
|
||||||
|
|
||||||
var compilers = exports.compilers = { |
|
||||||
sass: { |
|
||||||
match: /\.css$/, |
|
||||||
ext: '.sass', |
|
||||||
compile: function(str, fn){ |
|
||||||
var sass = cache.sass || (cache.sass = require('sass')); |
|
||||||
try { |
|
||||||
fn(null, sass.render(str)); |
|
||||||
} catch (err) { |
|
||||||
fn(err); |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
less: { |
|
||||||
match: /\.css$/, |
|
||||||
ext: '.less', |
|
||||||
compile: function(str, fn){ |
|
||||||
var less = cache.less || (cache.less = require('less')); |
|
||||||
try { |
|
||||||
less.render(str, fn); |
|
||||||
} catch (err) { |
|
||||||
fn(err); |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
coffeescript: { |
|
||||||
match: /\.js$/, |
|
||||||
ext: '.coffee', |
|
||||||
compile: function(str, fn){ |
|
||||||
var coffee = cache.coffee || (cache.coffee = require('coffee-script')); |
|
||||||
try { |
|
||||||
fn(null, coffee.compile(str)); |
|
||||||
} catch (err) { |
|
||||||
fn(err); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}; |
|
@ -1,46 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - cookieParser |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var utils = require('./../utils'); |
|
||||||
|
|
||||||
/** |
|
||||||
* Parse _Cookie_ header and populate `req.cookies` |
|
||||||
* with an object keyed by the cookie names. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* connect.createServer( |
|
||||||
* connect.cookieParser() |
|
||||||
* , function(req, res, next){ |
|
||||||
* res.end(JSON.stringify(req.cookies)); |
|
||||||
* } |
|
||||||
* ); |
|
||||||
* |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = function cookieParser(){ |
|
||||||
return function cookieParser(req, res, next) { |
|
||||||
var cookie = req.headers.cookie; |
|
||||||
if (req.cookies) return next(); |
|
||||||
req.cookies = {}; |
|
||||||
if (cookie) { |
|
||||||
try { |
|
||||||
req.cookies = utils.parseCookie(cookie); |
|
||||||
} catch (err) { |
|
||||||
return next(err); |
|
||||||
} |
|
||||||
} |
|
||||||
next(); |
|
||||||
}; |
|
||||||
}; |
|
@ -1,105 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - csrf |
|
||||||
* Copyright(c) 2011 Sencha Inc. |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var utils = require('../utils') |
|
||||||
, crypto = require('crypto'); |
|
||||||
|
|
||||||
/** |
|
||||||
* CRSF protection middleware. |
|
||||||
* |
|
||||||
* By default this middleware generates a token named "_csrf" |
|
||||||
* which should be added to requests which mutate |
|
||||||
* state, within a hidden form field, query-string etc. This |
|
||||||
* token is validated against the visitor's `req.session._csrf` |
|
||||||
* property which is re-generated per request. |
|
||||||
* |
|
||||||
* The default `value` function checks `req.body` generated |
|
||||||
* by the `bodyParser()` middleware, `req.query` generated |
|
||||||
* by `query()`, and the "X-CSRF-Token" header field. |
|
||||||
* |
|
||||||
* This middleware requires session support, thus should be added |
|
||||||
* somewhere _below_ `session()` and `cookieParser()`. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* var form = '\n\ |
|
||||||
* <form action="/" method="post">\n\ |
|
||||||
* <input type="hidden" name="_csrf" value="{token}" />\n\ |
|
||||||
* <input type="text" name="user[name]" value="{user}" />\n\ |
|
||||||
* <input type="password" name="user[pass]" />\n\ |
|
||||||
* <input type="submit" value="Login" />\n\ |
|
||||||
* </form>\n\ |
|
||||||
* ';
|
|
||||||
*
|
|
||||||
* connect( |
|
||||||
* connect.cookieParser() |
|
||||||
* , connect.session({ secret: 'keyboard cat' }) |
|
||||||
* , connect.bodyParser() |
|
||||||
* , connect.csrf() |
|
||||||
*
|
|
||||||
* , function(req, res, next){ |
|
||||||
* if ('POST' != req.method) return next(); |
|
||||||
* req.session.user = req.body.user; |
|
||||||
* next(); |
|
||||||
* } |
|
||||||
*
|
|
||||||
* , function(req, res){ |
|
||||||
* res.setHeader('Content-Type', 'text/html'); |
|
||||||
* var body = form |
|
||||||
* .replace('{token}', req.session._csrf) |
|
||||||
* .replace('{user}', req.session.user && req.session.user.name || ''); |
|
||||||
* res.end(body); |
|
||||||
* } |
|
||||||
* ).listen(3000); |
|
||||||
* |
|
||||||
* Options: |
|
||||||
* |
|
||||||
* - `value` a function accepting the request, returning the token
|
|
||||||
* |
|
||||||
* @param {Object} options |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = function csrf(options) { |
|
||||||
var options = options || {} |
|
||||||
, value = options.value || defaultValue; |
|
||||||
|
|
||||||
return function(req, res, next){ |
|
||||||
// generate CSRF token
|
|
||||||
var token = req.session._csrf || (req.session._csrf = utils.uid(24)); |
|
||||||
|
|
||||||
// ignore GET (for now)
|
|
||||||
if ('GET' == req.method) return next(); |
|
||||||
|
|
||||||
// determine value
|
|
||||||
var val = value(req); |
|
||||||
|
|
||||||
// check
|
|
||||||
if (val != token) return utils.forbidden(res); |
|
||||||
|
|
||||||
next(); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Default value function, checking the `req.body` |
|
||||||
* and `req.query` for the CSRF token. |
|
||||||
* |
|
||||||
* @param {IncomingMessage} req |
|
||||||
* @return {String} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function defaultValue(req) { |
|
||||||
return (req.body && req.body._csrf) |
|
||||||
|| (req.query && req.query._csrf) |
|
||||||
|| (req.headers['x-csrf-token']); |
|
||||||
} |
|
@ -1,222 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - directory |
|
||||||
* Copyright(c) 2011 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
// TODO: icon / style for directories
|
|
||||||
// TODO: arrow key navigation
|
|
||||||
// TODO: make icons extensible
|
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var fs = require('fs') |
|
||||||
, parse = require('url').parse |
|
||||||
, utils = require('../utils') |
|
||||||
, path = require('path') |
|
||||||
, normalize = path.normalize |
|
||||||
, extname = path.extname |
|
||||||
, join = path.join; |
|
||||||
|
|
||||||
/** |
|
||||||
* Icon cache. |
|
||||||
*/ |
|
||||||
|
|
||||||
var cache = {}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Serve directory listings with the given `root` path. |
|
||||||
* |
|
||||||
* Options: |
|
||||||
* |
|
||||||
* - `hidden` display hidden (dot) files. Defaults to false. |
|
||||||
* - `icons` display icons. Defaults to false. |
|
||||||
* - `filter` Apply this filter function to files. Defaults to false. |
|
||||||
* |
|
||||||
* @param {String} root |
|
||||||
* @param {Object} options |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
exports = module.exports = function directory(root, options){ |
|
||||||
options = options || {}; |
|
||||||
|
|
||||||
// root required
|
|
||||||
if (!root) throw new Error('directory() root path required'); |
|
||||||
var hidden = options.hidden |
|
||||||
, icons = options.icons |
|
||||||
, filter = options.filter |
|
||||||
, root = normalize(root); |
|
||||||
|
|
||||||
return function directory(req, res, next) { |
|
||||||
var accept = req.headers.accept || 'text/plain' |
|
||||||
, url = parse(req.url) |
|
||||||
, dir = decodeURIComponent(url.pathname) |
|
||||||
, path = normalize(join(root, dir)) |
|
||||||
, originalUrl = parse(req.originalUrl) |
|
||||||
, originalDir = decodeURIComponent(originalUrl.pathname) |
|
||||||
, showUp = path != root && path != root + '/'; |
|
||||||
|
|
||||||
// null byte(s)
|
|
||||||
if (~path.indexOf('\0')) return utils.badRequest(res); |
|
||||||
|
|
||||||
// malicious path
|
|
||||||
if (0 != path.indexOf(root)) return utils.forbidden(res); |
|
||||||
|
|
||||||
// check if we have a directory
|
|
||||||
fs.stat(path, function(err, stat){ |
|
||||||
if (err) return 'ENOENT' == err.code |
|
||||||
? next() |
|
||||||
: next(err); |
|
||||||
|
|
||||||
if (!stat.isDirectory()) return next(); |
|
||||||
|
|
||||||
// fetch files
|
|
||||||
fs.readdir(path, function(err, files){ |
|
||||||
if (err) return next(err); |
|
||||||
if (!hidden) files = removeHidden(files); |
|
||||||
if (filter) files = files.filter(filter); |
|
||||||
files.sort(); |
|
||||||
// content-negotiation
|
|
||||||
for (var key in exports) { |
|
||||||
if (~accept.indexOf(key) || ~accept.indexOf('*/*')) { |
|
||||||
exports[key](req, res, files, next, originalDir, showUp, icons); |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
utils.notAcceptable(res); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Respond with text/html. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.html = function(req, res, files, next, dir, showUp, icons){ |
|
||||||
fs.readFile(__dirname + '/../public/directory.html', 'utf8', function(err, str){ |
|
||||||
if (err) return next(err); |
|
||||||
fs.readFile(__dirname + '/../public/style.css', 'utf8', function(err, style){ |
|
||||||
if (err) return next(err); |
|
||||||
if (showUp) files.unshift('..'); |
|
||||||
str = str |
|
||||||
.replace('{style}', style) |
|
||||||
.replace('{files}', html(files, dir, icons)) |
|
||||||
.replace('{directory}', dir) |
|
||||||
.replace('{linked-path}', htmlPath(dir)); |
|
||||||
res.setHeader('Content-Type', 'text/html'); |
|
||||||
res.setHeader('Content-Length', str.length); |
|
||||||
res.end(str); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Respond with application/json. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.json = function(req, res, files){ |
|
||||||
files = JSON.stringify(files); |
|
||||||
res.setHeader('Content-Type', 'application/json'); |
|
||||||
res.setHeader('Content-Length', files.length); |
|
||||||
res.end(files); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Respond with text/plain. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.plain = function(req, res, files){ |
|
||||||
files = files.join('\n') + '\n'; |
|
||||||
res.setHeader('Content-Type', 'text/plain'); |
|
||||||
res.setHeader('Content-Length', files.length); |
|
||||||
res.end(files); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Map html `dir`, returning a linked path. |
|
||||||
*/ |
|
||||||
|
|
||||||
function htmlPath(dir) { |
|
||||||
var curr = []; |
|
||||||
return dir.split('/').map(function(part){ |
|
||||||
curr.push(part); |
|
||||||
return '<a href="' + curr.join('/') + '">' + part + '</a>'; |
|
||||||
}).join(' / '); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Map html `files`, returning an html unordered list. |
|
||||||
*/ |
|
||||||
|
|
||||||
function html(files, dir, useIcons) { |
|
||||||
return '<ul id="files">' + files.map(function(file){ |
|
||||||
var icon = '' |
|
||||||
, classes = []; |
|
||||||
|
|
||||||
if (useIcons && '..' != file) { |
|
||||||
icon = icons[extname(file)] || icons.default; |
|
||||||
icon = '<img src="data:image/png;base64,' + load(icon) + '" />'; |
|
||||||
classes.push('icon'); |
|
||||||
} |
|
||||||
|
|
||||||
return '<li><a href="' |
|
||||||
+ join(dir, file) |
|
||||||
+ '" class="' |
|
||||||
+ classes.join(' ') + '"' |
|
||||||
+ ' title="' + file + '">' |
|
||||||
+ icon + file + '</a></li>'; |
|
||||||
|
|
||||||
}).join('\n') + '</ul>'; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Load and cache the given `icon`. |
|
||||||
* |
|
||||||
* @param {String} icon |
|
||||||
* @return {String} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function load(icon) { |
|
||||||
if (cache[icon]) return cache[icon]; |
|
||||||
return cache[icon] = fs.readFileSync(__dirname + '/../public/icons/' + icon, 'base64'); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Filter "hidden" `files`, aka files |
|
||||||
* beginning with a `.`. |
|
||||||
* |
|
||||||
* @param {Array} files |
|
||||||
* @return {Array} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function removeHidden(files) { |
|
||||||
return files.filter(function(file){ |
|
||||||
return '.' != file[0]; |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Icon map. |
|
||||||
*/ |
|
||||||
|
|
||||||
var icons = { |
|
||||||
'.js': 'page_white_code_red.png' |
|
||||||
, '.c': 'page_white_c.png' |
|
||||||
, '.h': 'page_white_h.png' |
|
||||||
, '.cc': 'page_white_cplusplus.png' |
|
||||||
, '.php': 'page_white_php.png' |
|
||||||
, '.rb': 'page_white_ruby.png' |
|
||||||
, '.cpp': 'page_white_cplusplus.png' |
|
||||||
, '.swf': 'page_white_flash.png' |
|
||||||
, '.pdf': 'page_white_acrobat.png' |
|
||||||
, 'default': 'page_white.png' |
|
||||||
}; |
|
@ -1,100 +0,0 @@ |
|||||||
/*! |
|
||||||
* Connect - errorHandler |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var utils = require('../utils') |
|
||||||
, url = require('url') |
|
||||||
, fs = require('fs'); |
|
||||||
|
|
||||||
/** |
|
||||||
* Flexible error handler, providing (_optional_) stack traces |
|
||||||
* and error message responses for requests accepting text, html, |
|
||||||
* or json. |
|
||||||
* |
|
||||||
* Options: |
|
||||||
* |
|
||||||
* - `showStack`, `stack` respond with both the error message and stack trace. Defaults to `false` |
|
||||||
* - `showMessage`, `message`, respond with the exception message only. Defaults to `false` |
|
||||||
* - `dumpExceptions`, `dump`, dump exceptions to stderr (without terminating the process). Defaults to `false` |
|
||||||
* |
|
||||||
* Text: |
|
||||||
* |
|
||||||
* By default, and when _text/plain_ is accepted a simple stack trace |
|
||||||
* or error message will be returned. |
|
||||||
* |
|
||||||
* JSON: |
|
||||||
* |
|
||||||
* When _application/json_ is accepted, connect will respond with |
|
||||||
* an object in the form of `{ "error": error }`.
|
|
||||||
* |
|
||||||
* HTML: |
|
||||||
* |
|
||||||
* When accepted connect will output a nice html stack trace. |
|
||||||
* |
|
||||||
* @param {Object} options |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
exports = module.exports = function errorHandler(options){ |
|
||||||
options = options || {}; |
|
||||||
|
|
||||||
// defaults
|
|
||||||
var showStack = options.showStack || options.stack |
|
||||||
, showMessage = options.showMessage || options.message |
|
||||||
, dumpExceptions = options.dumpExceptions || options.dump |
|
||||||
, formatUrl = options.formatUrl; |
|
||||||
|
|
||||||
return function errorHandler(err, req, res, next){ |
|
||||||
res.statusCode = 500; |
|
||||||
if (dumpExceptions) console.error(err.stack); |
|
||||||
if (showStack) { |
|
||||||
var accept = req.headers.accept || ''; |
|
||||||
// html
|
|
||||||
if (~accept.indexOf('html')) { |
|
||||||
fs.readFile(__dirname + '/../public/style.css', 'utf8', function(e, style){ |
|
||||||
fs.readFile(__dirname + '/../public/error.html', 'utf8', function(e, html){ |
|
||||||
var stack = (err.stack || '') |
|
||||||
.split('\n').slice(1) |
|
||||||
.map(function(v){ return '<li>' + v + '</li>'; }).join(''); |
|
||||||
html = html |
|
||||||
.replace('{style}', style) |
|
||||||
.replace('{stack}', stack) |
|
||||||
.replace('{title}', exports.title) |
|
||||||
.replace(/\{error\}/g, utils.escape(err.toString())); |
|
||||||
res.setHeader('Content-Type', 'text/html'); |
|
||||||
res.end(html); |
|
||||||
}); |
|
||||||
}); |
|
||||||
// json
|
|
||||||
} else if (~accept.indexOf('json')) { |
|
||||||
var json = JSON.stringify({ error: err }); |
|
||||||
res.setHeader('Content-Type', 'application/json'); |
|
||||||
res.end(json); |
|
||||||
// plain text
|
|
||||||
} else { |
|
||||||
res.writeHead(500, { 'Content-Type': 'text/plain' }); |
|
||||||
res.end(err.stack); |
|
||||||
} |
|
||||||
} else { |
|
||||||
var body = showMessage |
|
||||||
? err.toString() |
|
||||||
: 'Internal Server Error'; |
|
||||||
res.setHeader('Content-Type', 'text/plain'); |
|
||||||
res.end(body); |
|
||||||
} |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Template title. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.title = 'Connect'; |
|
@ -1,76 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - favicon |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var fs = require('fs') |
|
||||||
, utils = require('../utils'); |
|
||||||
|
|
||||||
/** |
|
||||||
* Favicon cache. |
|
||||||
*/ |
|
||||||
|
|
||||||
var icon; |
|
||||||
|
|
||||||
/** |
|
||||||
* By default serves the connect favicon, or the favicon |
|
||||||
* located by the given `path`. |
|
||||||
* |
|
||||||
* Options: |
|
||||||
* |
|
||||||
* - `maxAge` cache-control max-age directive, defaulting to 1 day |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* connect.createServer( |
|
||||||
* connect.favicon()
|
|
||||||
* ); |
|
||||||
* |
|
||||||
* connect.createServer( |
|
||||||
* connect.favicon(__dirname + '/public/favicon.ico')
|
|
||||||
* ); |
|
||||||
* |
|
||||||
* @param {String} path |
|
||||||
* @param {Object} options |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = function favicon(path, options){ |
|
||||||
var options = options || {} |
|
||||||
, path = path || __dirname + '/../public/favicon.ico' |
|
||||||
, maxAge = options.maxAge || 86400000; |
|
||||||
|
|
||||||
return function favicon(req, res, next){ |
|
||||||
if ('/favicon.ico' == req.url) { |
|
||||||
if (icon) { |
|
||||||
res.writeHead(200, icon.headers); |
|
||||||
res.end(icon.body); |
|
||||||
} else { |
|
||||||
fs.readFile(path, function(err, buf){ |
|
||||||
if (err) return next(err); |
|
||||||
icon = { |
|
||||||
headers: { |
|
||||||
'Content-Type': 'image/x-icon' |
|
||||||
, 'Content-Length': buf.length |
|
||||||
, 'ETag': '"' + utils.md5(buf) + '"' |
|
||||||
, 'Cache-Control': 'public, max-age=' + (maxAge / 1000) |
|
||||||
}, |
|
||||||
body: buf |
|
||||||
}; |
|
||||||
res.writeHead(200, icon.headers); |
|
||||||
res.end(icon.body); |
|
||||||
}); |
|
||||||
} |
|
||||||
} else { |
|
||||||
next(); |
|
||||||
} |
|
||||||
}; |
|
||||||
}; |
|
@ -1,82 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - limit |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Limit request bodies to the given size in `bytes`. |
|
||||||
* |
|
||||||
* A string representation of the bytesize may also be passed, |
|
||||||
* for example "5mb", "200kb", "1gb", etc. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* var server = connect( |
|
||||||
* connect.limit('5.5mb') |
|
||||||
* ).listen(3000); |
|
||||||
* |
|
||||||
* TODO: pause EV_READ |
|
||||||
* |
|
||||||
* @param {Number|String} bytes |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = function limit(bytes){ |
|
||||||
if ('string' == typeof bytes) bytes = parse(bytes); |
|
||||||
if ('number' != typeof bytes) throw new Error('limit() bytes required'); |
|
||||||
return function limit(req, res, next){ |
|
||||||
var received = 0 |
|
||||||
, len = req.headers['content-length'] |
|
||||||
? parseInt(req.headers['content-length'], 10) |
|
||||||
: null; |
|
||||||
|
|
||||||
// deny the request
|
|
||||||
function deny() { |
|
||||||
req.destroy(); |
|
||||||
} |
|
||||||
|
|
||||||
// self-awareness
|
|
||||||
if (req._limit) return next(); |
|
||||||
req._limit = true; |
|
||||||
|
|
||||||
// limit by content-length
|
|
||||||
if (len && len > bytes) { |
|
||||||
res.statusCode = 413; |
|
||||||
res.end('Request Entity Too Large'); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// limit
|
|
||||||
req.on('data', function(chunk){ |
|
||||||
received += chunk.length; |
|
||||||
if (received > bytes) deny(); |
|
||||||
}); |
|
||||||
|
|
||||||
next(); |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Parse byte `size` string. |
|
||||||
* |
|
||||||
* @param {String} size |
|
||||||
* @return {Number} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function parse(size) { |
|
||||||
var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb)$/) |
|
||||||
, n = parseFloat(parts[1]) |
|
||||||
, type = parts[2]; |
|
||||||
|
|
||||||
var map = { |
|
||||||
kb: 1024 |
|
||||||
, mb: 1024 * 1024 |
|
||||||
, gb: 1024 * 1024 * 1024 |
|
||||||
}; |
|
||||||
|
|
||||||
return map[type] * n; |
|
||||||
} |
|
@ -1,299 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - logger |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Log buffer. |
|
||||||
*/ |
|
||||||
|
|
||||||
var buf = []; |
|
||||||
|
|
||||||
/** |
|
||||||
* Default log buffer duration. |
|
||||||
*/ |
|
||||||
|
|
||||||
var defaultBufferDuration = 1000; |
|
||||||
|
|
||||||
/** |
|
||||||
* Log requests with the given `options` or a `format` string. |
|
||||||
* |
|
||||||
* Options: |
|
||||||
* |
|
||||||
* - `format` Format string, see below for tokens |
|
||||||
* - `stream` Output stream, defaults to _stdout_ |
|
||||||
* - `buffer` Buffer duration, defaults to 1000ms when _true_ |
|
||||||
* - `immediate` Write log line on request instead of response (for response times) |
|
||||||
* |
|
||||||
* Tokens: |
|
||||||
* |
|
||||||
* - `:req[header]` ex: `:req[Accept]` |
|
||||||
* - `:res[header]` ex: `:res[Content-Length]` |
|
||||||
* - `:http-version` |
|
||||||
* - `:response-time` |
|
||||||
* - `:remote-addr` |
|
||||||
* - `:date` |
|
||||||
* - `:method` |
|
||||||
* - `:url` |
|
||||||
* - `:referrer` |
|
||||||
* - `:user-agent` |
|
||||||
* - `:status` |
|
||||||
* |
|
||||||
* Formats: |
|
||||||
* |
|
||||||
* Pre-defined formats that ship with connect: |
|
||||||
* |
|
||||||
* - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"' |
|
||||||
* - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms' |
|
||||||
* - `tiny` ':method :url :status :res[content-length] - :response-time ms' |
|
||||||
* - `dev` concise output colored by response status for development use |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* connect.logger() // default
|
|
||||||
* connect.logger('short') |
|
||||||
* connect.logger('tiny') |
|
||||||
* connect.logger('dev') |
|
||||||
* connect.logger(':method :url - :referrer') |
|
||||||
* connect.logger(':req[content-type] -> :res[content-type]') |
|
||||||
* connect.logger(function(req, res){ return 'some format string' }) |
|
||||||
* |
|
||||||
* Defining Tokens: |
|
||||||
* |
|
||||||
* To define a token, simply invoke `connect.logger.token()` with the |
|
||||||
* name and a callback function. The value returned is then available |
|
||||||
* as ":type" in this case. |
|
||||||
* |
|
||||||
* connect.logger.token('type', function(req, res){ return req.headers['content-type']; }) |
|
||||||
* |
|
||||||
* Defining Formats: |
|
||||||
* |
|
||||||
* All default formats are defined this way, however it's public API as well: |
|
||||||
* |
|
||||||
* connect.logger.format('name', 'string or function') |
|
||||||
* |
|
||||||
* @param {String|Function|Object} format or options |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
exports = module.exports = function logger(options) { |
|
||||||
if ('object' == typeof options) { |
|
||||||
options = options || {}; |
|
||||||
} else if (options) { |
|
||||||
options = { format: options }; |
|
||||||
} else { |
|
||||||
options = {}; |
|
||||||
} |
|
||||||
|
|
||||||
// output on request instead of response
|
|
||||||
var immediate = options.immediate; |
|
||||||
|
|
||||||
// format name
|
|
||||||
var fmt = exports[options.format] || options.format || exports.default; |
|
||||||
|
|
||||||
// compile format
|
|
||||||
if ('function' != typeof fmt) fmt = compile(fmt); |
|
||||||
|
|
||||||
// options
|
|
||||||
var stream = options.stream || process.stdout |
|
||||||
, buffer = options.buffer; |
|
||||||
|
|
||||||
// buffering support
|
|
||||||
if (buffer) { |
|
||||||
var realStream = stream |
|
||||||
, interval = 'number' == typeof buffer |
|
||||||
? buffer |
|
||||||
: defaultBufferDuration; |
|
||||||
|
|
||||||
// flush interval
|
|
||||||
setInterval(function(){ |
|
||||||
if (buf.length) { |
|
||||||
realStream.write(buf.join(''), 'ascii'); |
|
||||||
buf.length = 0; |
|
||||||
} |
|
||||||
}, interval);
|
|
||||||
|
|
||||||
// swap the stream
|
|
||||||
stream = { |
|
||||||
write: function(str){ |
|
||||||
buf.push(str); |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
return function logger(req, res, next) { |
|
||||||
req._startTime = new Date; |
|
||||||
|
|
||||||
// mount safety
|
|
||||||
if (req._logging) return next(); |
|
||||||
|
|
||||||
// flag as logging
|
|
||||||
req._logging = true; |
|
||||||
|
|
||||||
// immediate
|
|
||||||
if (immediate) { |
|
||||||
var line = fmt(exports, req, res); |
|
||||||
if (null == line) return; |
|
||||||
stream.write(line + '\n', 'ascii'); |
|
||||||
} else { |
|
||||||
// proxy end to output loggging
|
|
||||||
var end = res.end; |
|
||||||
res.end = function(chunk, encoding){ |
|
||||||
res.end = end; |
|
||||||
res.end(chunk, encoding); |
|
||||||
var line = fmt(exports, req, res); |
|
||||||
if (null == line) return; |
|
||||||
stream.write(line + '\n', 'ascii'); |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
next(); |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Compile `fmt` into a function. |
|
||||||
* |
|
||||||
* @param {String} fmt |
|
||||||
* @return {Function} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function compile(fmt) { |
|
||||||
fmt = fmt.replace(/"/g, '\\"'); |
|
||||||
var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){ |
|
||||||
return '"\n + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "'; |
|
||||||
}) + '";' |
|
||||||
return new Function('tokens, req, res', js); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Define a token function with the given `name`, |
|
||||||
* and callback `fn(req, res)`. |
|
||||||
* |
|
||||||
* @param {String} name |
|
||||||
* @param {Function} fn |
|
||||||
* @return {Object} exports for chaining |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.token = function(name, fn) { |
|
||||||
exports[name] = fn; |
|
||||||
return this; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Define a `fmt` with the given `name`. |
|
||||||
* |
|
||||||
* @param {String} name |
|
||||||
* @param {String|Function} fmt |
|
||||||
* @return {Object} exports for chaining |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.format = function(name, str){ |
|
||||||
exports[name] = str; |
|
||||||
return this; |
|
||||||
}; |
|
||||||
|
|
||||||
// default format
|
|
||||||
|
|
||||||
exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'); |
|
||||||
|
|
||||||
// short format
|
|
||||||
|
|
||||||
exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms'); |
|
||||||
|
|
||||||
// tiny format
|
|
||||||
|
|
||||||
exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms'); |
|
||||||
|
|
||||||
// dev (colored)
|
|
||||||
|
|
||||||
exports.format('dev', function(tokens, req, res){ |
|
||||||
var status = res.statusCode |
|
||||||
, color = 32; |
|
||||||
|
|
||||||
if (status >= 500) color = 31 |
|
||||||
else if (status >= 400) color = 33 |
|
||||||
else if (status >= 300) color = 36; |
|
||||||
|
|
||||||
return '\033[90m' + req.method |
|
||||||
+ ' ' + req.originalUrl + ' ' |
|
||||||
+ '\033[' + color + 'm' + res.statusCode |
|
||||||
+ ' \033[90m' |
|
||||||
+ (new Date - req._startTime) |
|
||||||
+ 'ms\033[0m'; |
|
||||||
}); |
|
||||||
|
|
||||||
// request url
|
|
||||||
|
|
||||||
exports.token('url', function(req){ |
|
||||||
return req.originalUrl; |
|
||||||
}); |
|
||||||
|
|
||||||
// request method
|
|
||||||
|
|
||||||
exports.token('method', function(req){ |
|
||||||
return req.method; |
|
||||||
}); |
|
||||||
|
|
||||||
// response time in milliseconds
|
|
||||||
|
|
||||||
exports.token('response-time', function(req){ |
|
||||||
return new Date - req._startTime; |
|
||||||
}); |
|
||||||
|
|
||||||
// UTC date
|
|
||||||
|
|
||||||
exports.token('date', function(){ |
|
||||||
return new Date().toUTCString(); |
|
||||||
}); |
|
||||||
|
|
||||||
// response status code
|
|
||||||
|
|
||||||
exports.token('status', function(req, res){ |
|
||||||
return res.statusCode; |
|
||||||
}); |
|
||||||
|
|
||||||
// normalized referrer
|
|
||||||
|
|
||||||
exports.token('referrer', function(req){ |
|
||||||
return req.headers['referer'] || req.headers['referrer']; |
|
||||||
}); |
|
||||||
|
|
||||||
// remote address
|
|
||||||
|
|
||||||
exports.token('remote-addr', function(req){ |
|
||||||
return req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress)); |
|
||||||
}); |
|
||||||
|
|
||||||
// HTTP version
|
|
||||||
|
|
||||||
exports.token('http-version', function(req){ |
|
||||||
return req.httpVersionMajor + '.' + req.httpVersionMinor; |
|
||||||
}); |
|
||||||
|
|
||||||
// UA string
|
|
||||||
|
|
||||||
exports.token('user-agent', function(req){ |
|
||||||
return req.headers['user-agent']; |
|
||||||
}); |
|
||||||
|
|
||||||
// request header
|
|
||||||
|
|
||||||
exports.token('req', function(req, res, field){ |
|
||||||
return req.headers[field.toLowerCase()]; |
|
||||||
}); |
|
||||||
|
|
||||||
// response header
|
|
||||||
|
|
||||||
exports.token('res', function(req, res, field){ |
|
||||||
return (res._headers || {})[field.toLowerCase()]; |
|
||||||
}); |
|
||||||
|
|
@ -1,38 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - methodOverride |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Provides faux HTTP method support. |
|
||||||
*
|
|
||||||
* Pass an optional `key` to use when checking for |
|
||||||
* a method override, othewise defaults to _\_method_. |
|
||||||
* The original method is available via `req.originalMethod`. |
|
||||||
* |
|
||||||
* @param {String} key |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = function methodOverride(key){ |
|
||||||
key = key || "_method"; |
|
||||||
return function methodOverride(req, res, next) { |
|
||||||
req.originalMethod = req.originalMethod || req.method; |
|
||||||
|
|
||||||
// req.body
|
|
||||||
if (req.body && key in req.body) { |
|
||||||
req.method = req.body[key].toUpperCase(); |
|
||||||
delete req.body[key]; |
|
||||||
// check X-HTTP-Method-Override
|
|
||||||
} else if (req.headers['x-http-method-override']) { |
|
||||||
req.method = req.headers['x-http-method-override'].toUpperCase(); |
|
||||||
} |
|
||||||
|
|
||||||
next(); |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
@ -1,100 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - profiler |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Profile the duration of a request. |
|
||||||
* |
|
||||||
* Typically this middleware should be utilized |
|
||||||
* _above_ all others, as it proxies the `res.end()` |
|
||||||
* method, being first allows it to encapsulate all |
|
||||||
* other middleware. |
|
||||||
* |
|
||||||
* Example Output: |
|
||||||
* |
|
||||||
* GET / |
|
||||||
* response time 2ms |
|
||||||
* memory rss 52.00kb |
|
||||||
* memory vsize 2.07mb |
|
||||||
* heap before 3.76mb / 8.15mb |
|
||||||
* heap after 3.80mb / 8.15mb |
|
||||||
* |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = function profiler(){ |
|
||||||
return function(req, res, next){ |
|
||||||
var end = res.end |
|
||||||
, start = snapshot(); |
|
||||||
|
|
||||||
// state snapshot
|
|
||||||
function snapshot() { |
|
||||||
return { |
|
||||||
mem: process.memoryUsage() |
|
||||||
, time: new Date |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
// proxy res.end()
|
|
||||||
res.end = function(data, encoding){ |
|
||||||
res.end = end; |
|
||||||
res.end(data, encoding); |
|
||||||
compare(req, start, snapshot()) |
|
||||||
}; |
|
||||||
|
|
||||||
next(); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Compare `start` / `end` snapshots. |
|
||||||
* |
|
||||||
* @param {IncomingRequest} req |
|
||||||
* @param {Object} start |
|
||||||
* @param {Object} end |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function compare(req, start, end) { |
|
||||||
console.log(); |
|
||||||
row(req.method, req.url); |
|
||||||
row('response time:', (end.time - start.time) + 'ms'); |
|
||||||
row('memory rss:', formatBytes(end.mem.rss - start.mem.rss)); |
|
||||||
row('memory vsize:', formatBytes(end.mem.vsize - start.mem.vsize)); |
|
||||||
row('heap before:', formatBytes(start.mem.heapUsed) + ' / ' + formatBytes(start.mem.heapTotal)); |
|
||||||
row('heap after:', formatBytes(end.mem.heapUsed) + ' / ' + formatBytes(end.mem.heapTotal)); |
|
||||||
console.log(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Row helper |
|
||||||
* |
|
||||||
* @param {String} key |
|
||||||
* @param {String} val |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function row(key, val) { |
|
||||||
console.log(' \033[90m%s\033[0m \033[36m%s\033[0m', key, val); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Format byte-size. |
|
||||||
* |
|
||||||
* @param {Number} bytes |
|
||||||
* @return {String} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function formatBytes(bytes) { |
|
||||||
var kb = 1024 |
|
||||||
, mb = 1024 * kb |
|
||||||
, gb = 1024 * mb; |
|
||||||
if (bytes < kb) return bytes + 'b'; |
|
||||||
if (bytes < mb) return (bytes / kb).toFixed(2) + 'kb'; |
|
||||||
if (bytes < gb) return (bytes / mb).toFixed(2) + 'mb'; |
|
||||||
return (bytes / gb).toFixed(2) + 'gb'; |
|
||||||
}; |
|
@ -1,40 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - query |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* Copyright(c) 2011 Sencha Inc. |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var qs = require('qs') |
|
||||||
, parse = require('url').parse; |
|
||||||
|
|
||||||
/** |
|
||||||
* Automatically parse the query-string when available, |
|
||||||
* populating the `req.query` object. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* connect( |
|
||||||
* connect.query() |
|
||||||
* , function(req, res){ |
|
||||||
* res.end(JSON.stringify(req.query)); |
|
||||||
* } |
|
||||||
* ).listen(3000); |
|
||||||
* |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = function query(){ |
|
||||||
return function query(req, res, next){ |
|
||||||
req.query = ~req.url.indexOf('?') |
|
||||||
? qs.parse(parse(req.url).query) |
|
||||||
: {}; |
|
||||||
next(); |
|
||||||
}; |
|
||||||
}; |
|
@ -1,34 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - responseTime |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds the `X-Response-Time` header displaying the response |
|
||||||
* duration in milliseconds. |
|
||||||
* |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = function responseTime(){ |
|
||||||
return function(req, res, next){ |
|
||||||
var writeHead = res.writeHead |
|
||||||
, start = new Date; |
|
||||||
|
|
||||||
if (res._responseTime) return next(); |
|
||||||
res._responseTime = true; |
|
||||||
|
|
||||||
// proxy writeHead to calculate duration
|
|
||||||
res.writeHead = function(status, headers){ |
|
||||||
var duration = new Date - start; |
|
||||||
res.setHeader('X-Response-Time', duration + 'ms'); |
|
||||||
res.writeHead = writeHead; |
|
||||||
res.writeHead(status, headers); |
|
||||||
}; |
|
||||||
|
|
||||||
next(); |
|
||||||
}; |
|
||||||
}; |
|
@ -1,379 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - router |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var utils = require('../utils') |
|
||||||
, parse = require('url').parse; |
|
||||||
|
|
||||||
/** |
|
||||||
* Expose router. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports = module.exports = router; |
|
||||||
|
|
||||||
/** |
|
||||||
* Supported HTTP / WebDAV methods. |
|
||||||
*/ |
|
||||||
|
|
||||||
var _methods = exports.methods = [ |
|
||||||
'get' |
|
||||||
, 'post' |
|
||||||
, 'put' |
|
||||||
, 'delete' |
|
||||||
, 'connect' |
|
||||||
, 'options' |
|
||||||
, 'trace' |
|
||||||
, 'copy' |
|
||||||
, 'lock' |
|
||||||
, 'mkcol' |
|
||||||
, 'move' |
|
||||||
, 'propfind' |
|
||||||
, 'proppatch' |
|
||||||
, 'unlock' |
|
||||||
, 'report' |
|
||||||
, 'mkactivity' |
|
||||||
, 'checkout' |
|
||||||
, 'merge' |
|
||||||
]; |
|
||||||
|
|
||||||
/** |
|
||||||
* Provides Sinatra and Express-like routing capabilities. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* connect.router(function(app){ |
|
||||||
* app.get('/user/:id', function(req, res, next){ |
|
||||||
* // populates req.params.id
|
|
||||||
* }); |
|
||||||
* app.put('/user/:id', function(req, res, next){ |
|
||||||
* // populates req.params.id
|
|
||||||
* }); |
|
||||||
* }) |
|
||||||
* |
|
||||||
* @param {Function} fn |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
function router(fn){ |
|
||||||
var self = this |
|
||||||
, methods = {} |
|
||||||
, routes = {} |
|
||||||
, params = {}; |
|
||||||
|
|
||||||
if (!fn) throw new Error('router provider requires a callback function'); |
|
||||||
|
|
||||||
// Generate method functions
|
|
||||||
_methods.forEach(function(method){ |
|
||||||
methods[method] = generateMethodFunction(method.toUpperCase()); |
|
||||||
}); |
|
||||||
|
|
||||||
// Alias del -> delete
|
|
||||||
methods.del = methods.delete; |
|
||||||
|
|
||||||
// Apply callback to all methods
|
|
||||||
methods.all = function(){ |
|
||||||
var args = arguments; |
|
||||||
_methods.forEach(function(name){ |
|
||||||
methods[name].apply(this, args); |
|
||||||
}); |
|
||||||
return self; |
|
||||||
}; |
|
||||||
|
|
||||||
// Register param callback
|
|
||||||
methods.param = function(name, fn){ |
|
||||||
params[name] = fn; |
|
||||||
}; |
|
||||||
|
|
||||||
fn.call(this, methods); |
|
||||||
|
|
||||||
function generateMethodFunction(name) { |
|
||||||
var localRoutes = routes[name] = routes[name] || []; |
|
||||||
return function(path, fn){ |
|
||||||
var keys = [] |
|
||||||
, middleware = []; |
|
||||||
|
|
||||||
// slice middleware
|
|
||||||
if (arguments.length > 2) { |
|
||||||
middleware = Array.prototype.slice.call(arguments, 1, arguments.length); |
|
||||||
fn = middleware.pop(); |
|
||||||
middleware = utils.flatten(middleware); |
|
||||||
} |
|
||||||
|
|
||||||
fn.middleware = middleware; |
|
||||||
|
|
||||||
if (!path) throw new Error(name + ' route requires a path'); |
|
||||||
if (!fn) throw new Error(name + ' route ' + path + ' requires a callback'); |
|
||||||
var regexp = path instanceof RegExp |
|
||||||
? path |
|
||||||
: normalizePath(path, keys); |
|
||||||
localRoutes.push({ |
|
||||||
fn: fn |
|
||||||
, path: regexp |
|
||||||
, keys: keys |
|
||||||
, orig: path |
|
||||||
, method: name |
|
||||||
}); |
|
||||||
return self; |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
function router(req, res, next){ |
|
||||||
var route |
|
||||||
, self = this; |
|
||||||
|
|
||||||
(function pass(i){ |
|
||||||
if (route = match(req, routes, i)) { |
|
||||||
var i = 0 |
|
||||||
, keys = route.keys; |
|
||||||
|
|
||||||
req.params = route.params; |
|
||||||
|
|
||||||
// Param preconditions
|
|
||||||
(function param(err) { |
|
||||||
try { |
|
||||||
var key = keys[i++] |
|
||||||
, val = req.params[key] |
|
||||||
, fn = params[key]; |
|
||||||
|
|
||||||
if ('route' == err) { |
|
||||||
pass(req._route_index + 1); |
|
||||||
// Error
|
|
||||||
} else if (err) { |
|
||||||
next(err); |
|
||||||
// Param has callback
|
|
||||||
} else if (fn) { |
|
||||||
// Return style
|
|
||||||
if (1 == fn.length) { |
|
||||||
req.params[key] = fn(val); |
|
||||||
param(); |
|
||||||
// Middleware style
|
|
||||||
} else { |
|
||||||
fn(req, res, param, val); |
|
||||||
} |
|
||||||
// Finished processing params
|
|
||||||
} else if (!key) { |
|
||||||
// route middleware
|
|
||||||
i = 0; |
|
||||||
(function nextMiddleware(err){ |
|
||||||
var fn = route.middleware[i++]; |
|
||||||
if ('route' == err) { |
|
||||||
pass(req._route_index + 1); |
|
||||||
} else if (err) { |
|
||||||
next(err); |
|
||||||
} else if (fn) { |
|
||||||
fn(req, res, nextMiddleware); |
|
||||||
} else { |
|
||||||
route.call(self, req, res, function(err){ |
|
||||||
if (err) { |
|
||||||
next(err); |
|
||||||
} else { |
|
||||||
pass(req._route_index + 1); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
})(); |
|
||||||
// More params
|
|
||||||
} else { |
|
||||||
param(); |
|
||||||
} |
|
||||||
} catch (err) { |
|
||||||
next(err); |
|
||||||
} |
|
||||||
})(); |
|
||||||
} else if ('OPTIONS' == req.method) { |
|
||||||
options(req, res, routes); |
|
||||||
} else { |
|
||||||
next(); |
|
||||||
} |
|
||||||
})(); |
|
||||||
}; |
|
||||||
|
|
||||||
router.remove = function(path, method){ |
|
||||||
var fns = router.lookup(path, method); |
|
||||||
fns.forEach(function(fn){ |
|
||||||
routes[fn.method].splice(fn.index, 1); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
router.lookup = function(path, method, ret){ |
|
||||||
ret = ret || []; |
|
||||||
|
|
||||||
// method specific lookup
|
|
||||||
if (method) { |
|
||||||
method = method.toUpperCase(); |
|
||||||
if (routes[method]) { |
|
||||||
routes[method].forEach(function(route, i){ |
|
||||||
if (path == route.orig) { |
|
||||||
var fn = route.fn; |
|
||||||
fn.regexp = route.path; |
|
||||||
fn.keys = route.keys; |
|
||||||
fn.path = route.orig; |
|
||||||
fn.method = route.method; |
|
||||||
fn.index = i; |
|
||||||
ret.push(fn); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
// global lookup
|
|
||||||
} else { |
|
||||||
_methods.forEach(function(method){ |
|
||||||
router.lookup(path, method, ret); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
return ret; |
|
||||||
}; |
|
||||||
|
|
||||||
router.match = function(url, method, ret){ |
|
||||||
var ret = ret || [] |
|
||||||
, i = 0 |
|
||||||
, fn |
|
||||||
, req; |
|
||||||
|
|
||||||
// method specific matches
|
|
||||||
if (method) { |
|
||||||
method = method.toUpperCase(); |
|
||||||
req = { url: url, method: method }; |
|
||||||
while (fn = match(req, routes, i)) { |
|
||||||
i = req._route_index + 1; |
|
||||||
ret.push(fn); |
|
||||||
}
|
|
||||||
// global matches
|
|
||||||
} else { |
|
||||||
_methods.forEach(function(method){ |
|
||||||
router.match(url, method, ret); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
return ret; |
|
||||||
}; |
|
||||||
|
|
||||||
return router; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Respond to OPTIONS. |
|
||||||
* |
|
||||||
* @param {ServerRequest} req |
|
||||||
* @param {ServerResponse} req |
|
||||||
* @param {Array} routes |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function options(req, res, routes) { |
|
||||||
var pathname = parse(req.url).pathname |
|
||||||
, body = optionsFor(pathname, routes).join(','); |
|
||||||
res.writeHead(200, { |
|
||||||
'Content-Length': body.length |
|
||||||
, 'Allow': body |
|
||||||
}); |
|
||||||
res.end(body); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Return OPTIONS array for the given `path`, matching `routes`. |
|
||||||
* |
|
||||||
* @param {String} path |
|
||||||
* @param {Array} routes |
|
||||||
* @return {Array} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function optionsFor(path, routes) { |
|
||||||
return _methods.filter(function(method){ |
|
||||||
var arr = routes[method.toUpperCase()]; |
|
||||||
for (var i = 0, len = arr.length; i < len; ++i) { |
|
||||||
if (arr[i].path.test(path)) return true; |
|
||||||
} |
|
||||||
}).map(function(method){ |
|
||||||
return method.toUpperCase(); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Normalize the given path string, |
|
||||||
* returning a regular expression. |
|
||||||
* |
|
||||||
* An empty array should be passed, |
|
||||||
* which will contain the placeholder |
|
||||||
* key names. For example "/user/:id" will |
|
||||||
* then contain ["id"]. |
|
||||||
* |
|
||||||
* @param {String} path |
|
||||||
* @param {Array} keys |
|
||||||
* @return {RegExp} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function normalizePath(path, keys) { |
|
||||||
path = path |
|
||||||
.concat('/?') |
|
||||||
.replace(/\/\(/g, '(?:/') |
|
||||||
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){ |
|
||||||
keys.push(key); |
|
||||||
slash = slash || ''; |
|
||||||
return '' |
|
||||||
+ (optional ? '' : slash) |
|
||||||
+ '(?:' |
|
||||||
+ (optional ? slash : '') |
|
||||||
+ (format || '') + (capture || '([^/]+?)') + ')' |
|
||||||
+ (optional || ''); |
|
||||||
}) |
|
||||||
.replace(/([\/.])/g, '\\$1') |
|
||||||
.replace(/\*/g, '(.+)'); |
|
||||||
return new RegExp('^' + path + '$', 'i'); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Attempt to match the given request to |
|
||||||
* one of the routes. When successful |
|
||||||
* a route function is returned. |
|
||||||
* |
|
||||||
* @param {ServerRequest} req |
|
||||||
* @param {Object} routes |
|
||||||
* @return {Function} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function match(req, routes, i) { |
|
||||||
var captures |
|
||||||
, method = req.method |
|
||||||
, i = i || 0; |
|
||||||
if ('HEAD' == method) method = 'GET'; |
|
||||||
if (routes = routes[method]) { |
|
||||||
var url = parse(req.url) |
|
||||||
, pathname = url.pathname; |
|
||||||
for (var len = routes.length; i < len; ++i) { |
|
||||||
var route = routes[i] |
|
||||||
, fn = route.fn |
|
||||||
, path = route.path |
|
||||||
, keys = fn.keys = route.keys; |
|
||||||
if (captures = path.exec(pathname)) { |
|
||||||
fn.method = method; |
|
||||||
fn.params = []; |
|
||||||
for (var j = 1, len = captures.length; j < len; ++j) { |
|
||||||
var key = keys[j-1], |
|
||||||
val = typeof captures[j] === 'string' |
|
||||||
? decodeURIComponent(captures[j]) |
|
||||||
: captures[j]; |
|
||||||
if (key) { |
|
||||||
fn.params[key] = val; |
|
||||||
} else { |
|
||||||
fn.params.push(val); |
|
||||||
} |
|
||||||
} |
|
||||||
req._route_index = i; |
|
||||||
return fn; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,346 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - session |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var Session = require('./session/session') |
|
||||||
, MemoryStore = require('./session/memory') |
|
||||||
, Cookie = require('./session/cookie') |
|
||||||
, Store = require('./session/store') |
|
||||||
, utils = require('./../utils') |
|
||||||
, parse = require('url').parse |
|
||||||
, crypto = require('crypto'); |
|
||||||
|
|
||||||
// environment
|
|
||||||
|
|
||||||
var env = process.env.NODE_ENV; |
|
||||||
|
|
||||||
/** |
|
||||||
* Expose the middleware. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports = module.exports = session; |
|
||||||
|
|
||||||
/** |
|
||||||
* Expose constructors. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.Store = Store; |
|
||||||
exports.Cookie = Cookie; |
|
||||||
exports.Session = Session; |
|
||||||
exports.MemoryStore = MemoryStore; |
|
||||||
|
|
||||||
/** |
|
||||||
* Warning message for `MemoryStore` usage in production. |
|
||||||
*/ |
|
||||||
|
|
||||||
var warning = 'Warning: connection.session() MemoryStore is not\n' |
|
||||||
+ 'designed for a production environment, as it will leak\n' |
|
||||||
+ 'memory, and obviously only work within a single process.'; |
|
||||||
|
|
||||||
/** |
|
||||||
* Default finger-printing function. |
|
||||||
*/ |
|
||||||
|
|
||||||
function defaultFingerprint(req) { |
|
||||||
var ua = req.headers['user-agent'] || ''; |
|
||||||
return ua.replace(/;?\schromeframe\/[\d\.]+/, ''); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Paths to ignore. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.ignore = []; |
|
||||||
|
|
||||||
/** |
|
||||||
* Setup session store with the given `options`. |
|
||||||
* |
|
||||||
* Session data is _not_ saved in the cookie itself, however |
|
||||||
* cookies are used, so we must use the [cookieParser()](middleware-cookieParser.html) |
|
||||||
* middleware _before_ `session()`. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* connect.createServer( |
|
||||||
* connect.cookieParser() |
|
||||||
* , connect.session({ secret: 'keyboard cat' }) |
|
||||||
* ); |
|
||||||
* |
|
||||||
* Options: |
|
||||||
* |
|
||||||
* - `key` cookie name defaulting to `connect.sid` |
|
||||||
* - `store` Session store instance |
|
||||||
* - `fingerprint` Custom fingerprint generating function |
|
||||||
* - `cookie` Session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: 14400000 }` |
|
||||||
* - `secret` Secret string used to compute hash |
|
||||||
* |
|
||||||
* Ignore Paths: |
|
||||||
* |
|
||||||
* By default `/favicon.ico` is the only ignored path, all others |
|
||||||
* will utilize sessions, to manipulate the paths ignored, use |
|
||||||
* `connect.session.ignore.push('/my/path')`. This works for _full_ |
|
||||||
* pathnames only, not segments nor substrings. |
|
||||||
* |
|
||||||
* connect.session.ignore.push('/robots.txt'); |
|
||||||
* |
|
||||||
* ## req.session |
|
||||||
* |
|
||||||
* To store or access session data, simply use the request property `req.session`, |
|
||||||
* which is (generally) serialized as JSON by the store, so nested objects
|
|
||||||
* are typically fine. For example below is a user-specific view counter: |
|
||||||
* |
|
||||||
* connect( |
|
||||||
* connect.cookieParser() |
|
||||||
* , connect.session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}) |
|
||||||
* , connect.favicon() |
|
||||||
* , function(req, res, next){ |
|
||||||
* var sess = req.session; |
|
||||||
* if (sess.views) { |
|
||||||
* res.setHeader('Content-Type', 'text/html'); |
|
||||||
* res.write('<p>views: ' + sess.views + '</p>'); |
|
||||||
* res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>'); |
|
||||||
* res.end(); |
|
||||||
* sess.views++; |
|
||||||
* } else { |
|
||||||
* sess.views = 1; |
|
||||||
* res.end('welcome to the session demo. refresh!'); |
|
||||||
* } |
|
||||||
* } |
|
||||||
* ).listen(3000); |
|
||||||
* |
|
||||||
* ## Session#regenerate() |
|
||||||
* |
|
||||||
* To regenerate the session simply invoke the method, once complete |
|
||||||
* a new SID and `Session` instance will be initialized at `req.session`. |
|
||||||
* |
|
||||||
* req.session.regenerate(function(err){ |
|
||||||
* // will have a new session here
|
|
||||||
* }); |
|
||||||
* |
|
||||||
* ## Session#destroy() |
|
||||||
* |
|
||||||
* Destroys the session, removing `req.session`, will be re-generated next request. |
|
||||||
* |
|
||||||
* req.session.destroy(function(err){ |
|
||||||
* // cannot access session here
|
|
||||||
* }); |
|
||||||
*
|
|
||||||
* ## Session#reload() |
|
||||||
* |
|
||||||
* Reloads the session data. |
|
||||||
* |
|
||||||
* req.session.reload(function(err){ |
|
||||||
* // session updated
|
|
||||||
* }); |
|
||||||
* |
|
||||||
* ## Session#save() |
|
||||||
* |
|
||||||
* Save the session. |
|
||||||
* |
|
||||||
* req.session.save(function(err){ |
|
||||||
* // session saved
|
|
||||||
* }); |
|
||||||
* |
|
||||||
* ## Session#touch() |
|
||||||
* |
|
||||||
* Updates the `.maxAge`, and `.lastAccess` properties. Typically this is |
|
||||||
* not necessary to call, as the session middleware does this for you. |
|
||||||
* |
|
||||||
* ## Session#cookie |
|
||||||
* |
|
||||||
* Each session has a unique cookie object accompany it. This allows |
|
||||||
* you to alter the session cookie per visitor. For example we can |
|
||||||
* set `req.session.cookie.expires` to `false` to enable the cookie |
|
||||||
* to remain for only the duration of the user-agent. |
|
||||||
* |
|
||||||
* ## Session#maxAge |
|
||||||
* |
|
||||||
* Alternatively `req.session.cookie.maxAge` will return the time |
|
||||||
* remaining in milliseconds, which we may also re-assign a new value |
|
||||||
* to adjust the `.expires` property appropriately. The following |
|
||||||
* are essentially equivalent |
|
||||||
* |
|
||||||
* var hour = 3600000; |
|
||||||
* req.session.cookie.expires = new Date(Date.now() + hour); |
|
||||||
* req.session.cookie.maxAge = hour; |
|
||||||
* |
|
||||||
* For example when `maxAge` is set to `60000` (one minute), and 30 seconds |
|
||||||
* has elapsed it will return `30000` until the current request has completed, |
|
||||||
* at which time `req.session.touch()` is called to update `req.session.lastAccess`, |
|
||||||
* and reset `req.session.maxAge` to its original value. |
|
||||||
* |
|
||||||
* req.session.cookie.maxAge; |
|
||||||
* // => 30000
|
|
||||||
* |
|
||||||
* Session Store Implementation: |
|
||||||
* |
|
||||||
* Every session store _must_ implement the following methods |
|
||||||
* |
|
||||||
* - `.get(sid, callback)` |
|
||||||
* - `.set(sid, session, callback)` |
|
||||||
* - `.destroy(sid, callback)` |
|
||||||
* |
|
||||||
* Recommended methods include, but are not limited to: |
|
||||||
* |
|
||||||
* - `.length(callback)` |
|
||||||
* - `.clear(callback)` |
|
||||||
* |
|
||||||
* For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo.
|
|
||||||
* |
|
||||||
* @param {Object} options |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
function session(options){ |
|
||||||
var options = options || {} |
|
||||||
, key = options.key || 'connect.sid' |
|
||||||
, secret = options.secret |
|
||||||
, store = options.store || new MemoryStore |
|
||||||
, fingerprint = options.fingerprint || defaultFingerprint |
|
||||||
, cookie = options.cookie; |
|
||||||
|
|
||||||
// notify user that this store is not
|
|
||||||
// meant for a production environment
|
|
||||||
if ('production' == env && store instanceof MemoryStore) { |
|
||||||
console.warn(warning); |
|
||||||
} |
|
||||||
|
|
||||||
// ensure secret is present
|
|
||||||
if (!secret) { |
|
||||||
throw new Error('connect.session({ secret: "string" }) required for security'); |
|
||||||
} |
|
||||||
|
|
||||||
// session hashing function
|
|
||||||
store.hash = function(req, base) { |
|
||||||
return crypto |
|
||||||
.createHmac('sha256', secret) |
|
||||||
.update(base + fingerprint(req)) |
|
||||||
.digest('base64') |
|
||||||
.replace(/=*$/, ''); |
|
||||||
}; |
|
||||||
|
|
||||||
// generates the new session
|
|
||||||
store.generate = function(req){ |
|
||||||
var base = utils.uid(24); |
|
||||||
var sessionID = base + '.' + store.hash(req, base); |
|
||||||
req.sessionID = sessionID; |
|
||||||
req.session = new Session(req); |
|
||||||
req.session.cookie = new Cookie(cookie); |
|
||||||
}; |
|
||||||
|
|
||||||
return function session(req, res, next) { |
|
||||||
// self-awareness
|
|
||||||
if (req.session) return next(); |
|
||||||
|
|
||||||
// parse url
|
|
||||||
var url = parse(req.url) |
|
||||||
, path = url.pathname; |
|
||||||
|
|
||||||
// ignorable paths
|
|
||||||
if (~exports.ignore.indexOf(path)) return next(); |
|
||||||
|
|
||||||
// expose store
|
|
||||||
req.sessionStore = store; |
|
||||||
|
|
||||||
// proxy writeHead() to Set-Cookie
|
|
||||||
var writeHead = res.writeHead; |
|
||||||
res.writeHead = function(status, headers){ |
|
||||||
if (req.session) { |
|
||||||
var cookie = req.session.cookie; |
|
||||||
// only send secure session cookies when there is a secure connection.
|
|
||||||
// proxySecure is a custom attribute to allow for a reverse proxy
|
|
||||||
// to handle SSL connections and to communicate to connect over HTTP that
|
|
||||||
// the incoming connection is secure.
|
|
||||||
var secured = cookie.secure && (req.connection.encrypted || req.connection.proxySecure); |
|
||||||
if (secured || !cookie.secure) { |
|
||||||
res.setHeader('Set-Cookie', cookie.serialize(key, req.sessionID)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
res.writeHead = writeHead; |
|
||||||
return res.writeHead(status, headers); |
|
||||||
}; |
|
||||||
|
|
||||||
// proxy end() to commit the session
|
|
||||||
var end = res.end; |
|
||||||
res.end = function(data, encoding){ |
|
||||||
res.end = end; |
|
||||||
if (req.session) { |
|
||||||
// HACK: ensure Set-Cookie for implicit writeHead()
|
|
||||||
if (!res._header) res._implicitHeader(); |
|
||||||
req.session.resetMaxAge(); |
|
||||||
req.session.save(function(){ |
|
||||||
res.end(data, encoding); |
|
||||||
}); |
|
||||||
} else { |
|
||||||
res.end(data, encoding); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// session hashing
|
|
||||||
function hash(base) { |
|
||||||
return store.hash(req, base); |
|
||||||
} |
|
||||||
|
|
||||||
// generate the session
|
|
||||||
function generate() { |
|
||||||
store.generate(req); |
|
||||||
} |
|
||||||
|
|
||||||
// get the sessionID from the cookie
|
|
||||||
req.sessionID = req.cookies[key]; |
|
||||||
|
|
||||||
// make a new session if the browser doesn't send a sessionID
|
|
||||||
if (!req.sessionID) { |
|
||||||
generate(); |
|
||||||
next(); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// check the fingerprint
|
|
||||||
var parts = req.sessionID.split('.'); |
|
||||||
if (parts[1] != hash(parts[0])) { |
|
||||||
generate(); |
|
||||||
next(); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// generate the session object
|
|
||||||
var pause = utils.pause(req); |
|
||||||
store.get(req.sessionID, function(err, sess){ |
|
||||||
// proxy to resume() events
|
|
||||||
var _next = next; |
|
||||||
next = function(err){ |
|
||||||
_next(err); |
|
||||||
pause.resume(); |
|
||||||
} |
|
||||||
|
|
||||||
// error handling
|
|
||||||
if (err) { |
|
||||||
if ('ENOENT' == err.code) { |
|
||||||
generate(); |
|
||||||
next(); |
|
||||||
} else { |
|
||||||
next(err); |
|
||||||
} |
|
||||||
// no session
|
|
||||||
} else if (!sess) { |
|
||||||
generate(); |
|
||||||
next(); |
|
||||||
// populate req.session
|
|
||||||
} else { |
|
||||||
store.createSession(req, sess); |
|
||||||
next(); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
}; |
|
@ -1,126 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - session - Cookie |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var utils = require('../../utils'); |
|
||||||
|
|
||||||
/** |
|
||||||
* Initialize a new `Cookie` with the given `options`. |
|
||||||
* |
|
||||||
* @param {Object} options |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
var Cookie = module.exports = function Cookie(options) { |
|
||||||
this.path = '/'; |
|
||||||
this.httpOnly = true; |
|
||||||
this.maxAge = 14400000; |
|
||||||
if (options) utils.merge(this, options); |
|
||||||
this.originalMaxAge = undefined == this.originalMaxAge |
|
||||||
? this.maxAge |
|
||||||
: this.originalMaxAge; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Prototype. |
|
||||||
*/ |
|
||||||
|
|
||||||
Cookie.prototype = { |
|
||||||
|
|
||||||
/** |
|
||||||
* Set expires `date`. |
|
||||||
* |
|
||||||
* @param {Date} date |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
set expires(date) { |
|
||||||
this._expires = date; |
|
||||||
this.originalMaxAge = this.maxAge; |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Get expires `date`. |
|
||||||
* |
|
||||||
* @return {Date} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
get expires() { |
|
||||||
return this._expires; |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Set expires via max-age in `ms`. |
|
||||||
* |
|
||||||
* @param {Number} ms |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
set maxAge(ms) { |
|
||||||
this.expires = 'number' == typeof ms |
|
||||||
? new Date(Date.now() + ms) |
|
||||||
: ms; |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Get expires max-age in `ms`. |
|
||||||
* |
|
||||||
* @return {Number} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
get maxAge() { |
|
||||||
return this.expires instanceof Date |
|
||||||
? this.expires.valueOf() - Date.now() |
|
||||||
: this.expires; |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Return cookie data object. |
|
||||||
* |
|
||||||
* @return {Object} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
get data() { |
|
||||||
return { |
|
||||||
originalMaxAge: this.originalMaxAge |
|
||||||
, expires: this._expires |
|
||||||
, secure: this.secure |
|
||||||
, httpOnly: this.httpOnly |
|
||||||
, domain: this.domain |
|
||||||
, path: this.path |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Return a serialized cookie string. |
|
||||||
* |
|
||||||
* @return {String} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
serialize: function(name, val){ |
|
||||||
return utils.serializeCookie(name, val, this.data); |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Return JSON representation of this cookie. |
|
||||||
* |
|
||||||
* @return {Object} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
toJSON: function(){ |
|
||||||
return this.data; |
|
||||||
} |
|
||||||
}; |
|
@ -1,131 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - session - MemoryStore |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var Store = require('./store') |
|
||||||
, utils = require('../../utils') |
|
||||||
, Session = require('./session'); |
|
||||||
|
|
||||||
/** |
|
||||||
* Initialize a new `MemoryStore`. |
|
||||||
* |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
var MemoryStore = module.exports = function MemoryStore() { |
|
||||||
this.sessions = {}; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Inherit from `Store.prototype`. |
|
||||||
*/ |
|
||||||
|
|
||||||
MemoryStore.prototype.__proto__ = Store.prototype; |
|
||||||
|
|
||||||
/** |
|
||||||
* Attempt to fetch session by the given `sid`. |
|
||||||
* |
|
||||||
* @param {String} sid |
|
||||||
* @param {Function} fn |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
MemoryStore.prototype.get = function(sid, fn){ |
|
||||||
var self = this; |
|
||||||
process.nextTick(function(){ |
|
||||||
var expires |
|
||||||
, sess = self.sessions[sid]; |
|
||||||
if (sess) { |
|
||||||
sess = JSON.parse(sess); |
|
||||||
expires = 'string' == typeof sess.cookie.expires |
|
||||||
? new Date(sess.cookie.expires) |
|
||||||
: sess.cookie.expires; |
|
||||||
if (!expires || new Date < expires) { |
|
||||||
fn(null, sess); |
|
||||||
} else { |
|
||||||
self.destroy(sid, fn); |
|
||||||
} |
|
||||||
} else { |
|
||||||
fn(); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Commit the given `sess` object associated with the given `sid`. |
|
||||||
* |
|
||||||
* @param {String} sid |
|
||||||
* @param {Session} sess |
|
||||||
* @param {Function} fn |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
MemoryStore.prototype.set = function(sid, sess, fn){ |
|
||||||
var self = this; |
|
||||||
process.nextTick(function(){ |
|
||||||
self.sessions[sid] = JSON.stringify(sess); |
|
||||||
fn && fn(); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Destroy the session associated with the given `sid`. |
|
||||||
* |
|
||||||
* @param {String} sid |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
MemoryStore.prototype.destroy = function(sid, fn){ |
|
||||||
var self = this; |
|
||||||
process.nextTick(function(){ |
|
||||||
delete self.sessions[sid]; |
|
||||||
fn && fn(); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Invoke the given callback `fn` with all active sessions. |
|
||||||
* |
|
||||||
* @param {Function} fn |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
MemoryStore.prototype.all = function(fn){ |
|
||||||
var arr = [] |
|
||||||
, keys = Object.keys(this.sessions); |
|
||||||
for (var i = 0, len = keys.length; i < len; ++i) { |
|
||||||
arr.push(this.sessions[keys[i]]); |
|
||||||
} |
|
||||||
fn(null, arr); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Clear all sessions. |
|
||||||
* |
|
||||||
* @param {Function} fn |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
MemoryStore.prototype.clear = function(fn){ |
|
||||||
this.sessions = {}; |
|
||||||
fn && fn(); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Fetch number of sessions. |
|
||||||
* |
|
||||||
* @param {Function} fn |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
MemoryStore.prototype.length = function(fn){ |
|
||||||
fn(null, Object.keys(this.sessions).length); |
|
||||||
}; |
|
@ -1,137 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - session - Session |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var utils = require('../../utils') |
|
||||||
, Cookie = require('./cookie'); |
|
||||||
|
|
||||||
/** |
|
||||||
* Create a new `Session` with the given request and `data`. |
|
||||||
* |
|
||||||
* @param {IncomingRequest} req |
|
||||||
* @param {Object} data |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
var Session = module.exports = function Session(req, data) { |
|
||||||
Object.defineProperty(this, 'req', { value: req }); |
|
||||||
Object.defineProperty(this, 'id', { value: req.sessionID }); |
|
||||||
if ('object' == typeof data) { |
|
||||||
utils.merge(this, data); |
|
||||||
} else { |
|
||||||
this.lastAccess = Date.now(); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Update `.lastAccess` timestamp, |
|
||||||
* and reset `.cookie.maxAge` to prevent |
|
||||||
* the cookie from expiring when the |
|
||||||
* session is still active. |
|
||||||
* |
|
||||||
* @return {Session} for chaining |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
Session.prototype.touch = function(){ |
|
||||||
return this |
|
||||||
.resetLastAccess() |
|
||||||
.resetMaxAge(); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Update `.lastAccess` timestamp. |
|
||||||
* |
|
||||||
* @return {Session} for chaining |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
Session.prototype.resetLastAccess = function(){ |
|
||||||
this.lastAccess = Date.now(); |
|
||||||
return this; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Reset `.maxAge` to `.originalMaxAge`. |
|
||||||
* |
|
||||||
* @return {Session} for chaining |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
Session.prototype.resetMaxAge = function(){ |
|
||||||
this.cookie.maxAge = this.cookie.originalMaxAge; |
|
||||||
return this; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Save the session data with optional callback `fn(err)`. |
|
||||||
* |
|
||||||
* @param {Function} fn |
|
||||||
* @return {Session} for chaining |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
Session.prototype.save = function(fn){ |
|
||||||
this.req.sessionStore.set(this.id, this, fn || function(){}); |
|
||||||
return this; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Re-loads the session data _without_ altering |
|
||||||
* the maxAge or lastAccess properties. Invokes the |
|
||||||
* callback `fn(err)`, after which time if no exception |
|
||||||
* has occurred the `req.session` property will be |
|
||||||
* a new `Session` object, although representing the |
|
||||||
* same session. |
|
||||||
* |
|
||||||
* @param {Function} fn |
|
||||||
* @return {Session} for chaining |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
Session.prototype.reload = function(fn){ |
|
||||||
var req = this.req |
|
||||||
, store = this.req.sessionStore; |
|
||||||
store.get(this.id, function(err, sess){ |
|
||||||
if (err) return fn(err); |
|
||||||
if (!sess) return fn(new Error('failed to load session')); |
|
||||||
store.createSession(req, sess); |
|
||||||
fn(); |
|
||||||
}); |
|
||||||
return this; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Destroy `this` session. |
|
||||||
* |
|
||||||
* @param {Function} fn |
|
||||||
* @return {Session} for chaining |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
Session.prototype.destroy = function(fn){ |
|
||||||
delete this.req.session; |
|
||||||
this.req.sessionStore.destroy(this.id, fn); |
|
||||||
return this; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Regenerate this request's session. |
|
||||||
* |
|
||||||
* @param {Function} fn |
|
||||||
* @return {Session} for chaining |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
Session.prototype.regenerate = function(fn){ |
|
||||||
this.req.sessionStore.regenerate(this.req, fn); |
|
||||||
return this; |
|
||||||
}; |
|
@ -1,87 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - session - Store |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var EventEmitter = require('events').EventEmitter |
|
||||||
, Session = require('./session') |
|
||||||
, Cookie = require('./cookie') |
|
||||||
, utils = require('../../utils'); |
|
||||||
|
|
||||||
/** |
|
||||||
* Initialize abstract `Store`. |
|
||||||
* |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
var Store = module.exports = function Store(options){}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Inherit from `EventEmitter.prototype`. |
|
||||||
*/ |
|
||||||
|
|
||||||
Store.prototype.__proto__ = EventEmitter.prototype; |
|
||||||
|
|
||||||
/** |
|
||||||
* Re-generate the given requests's session. |
|
||||||
* |
|
||||||
* @param {IncomingRequest} req |
|
||||||
* @return {Function} fn |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
Store.prototype.regenerate = function(req, fn){ |
|
||||||
var self = this; |
|
||||||
this.destroy(req.sessionID, function(err){ |
|
||||||
self.generate(req); |
|
||||||
fn(err); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Load a `Session` instance via the given `sid` |
|
||||||
* and invoke the callback `fn(err, sess)`. |
|
||||||
* |
|
||||||
* @param {String} sid |
|
||||||
* @param {Function} fn |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
Store.prototype.load = function(sid, fn){ |
|
||||||
var self = this; |
|
||||||
this.get(sid, function(err, sess){ |
|
||||||
if (err) return fn(err); |
|
||||||
if (!sess) return fn(); |
|
||||||
var req = { sessionID: sid, sessionStore: self }; |
|
||||||
sess = self.createSession(req, sess, false); |
|
||||||
fn(null, sess); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Create session from JSON `sess` data. |
|
||||||
* |
|
||||||
* @param {IncomingRequest} req |
|
||||||
* @param {Object} sess |
|
||||||
* @return {Session} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
Store.prototype.createSession = function(req, sess, update){ |
|
||||||
var expires = sess.cookie.expires |
|
||||||
, orig = sess.cookie.originalMaxAge |
|
||||||
, update = null == update ? true : false; |
|
||||||
sess.cookie = new Cookie(sess.cookie); |
|
||||||
if ('string' == typeof expires) sess.cookie.expires = new Date(expires); |
|
||||||
sess.cookie.originalMaxAge = orig; |
|
||||||
req.session = new Session(req, sess); |
|
||||||
if (update) req.session.resetLastAccess(); |
|
||||||
return req.session; |
|
||||||
}; |
|
@ -1,225 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - staticProvider |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var fs = require('fs') |
|
||||||
, path = require('path') |
|
||||||
, join = path.join |
|
||||||
, basename = path.basename |
|
||||||
, normalize = path.normalize |
|
||||||
, utils = require('../utils') |
|
||||||
, Buffer = require('buffer').Buffer |
|
||||||
, parse = require('url').parse |
|
||||||
, mime = require('mime'); |
|
||||||
|
|
||||||
/** |
|
||||||
* Static file server with the given `root` path. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* var oneDay = 86400000; |
|
||||||
* |
|
||||||
* connect( |
|
||||||
* connect.static(__dirname + '/public') |
|
||||||
* ).listen(3000); |
|
||||||
* |
|
||||||
* connect( |
|
||||||
* connect.static(__dirname + '/public', { maxAge: oneDay }) |
|
||||||
* ).listen(3000); |
|
||||||
* |
|
||||||
* Options: |
|
||||||
* |
|
||||||
* - `maxAge` Browser cache maxAge in milliseconds. defaults to 0 |
|
||||||
* - `hidden` Allow transfer of hidden files. defaults to false |
|
||||||
* - `redirect` Redirect to trailing "/" when the pathname is a dir |
|
||||||
* |
|
||||||
* @param {String} root |
|
||||||
* @param {Object} options |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
exports = module.exports = function static(root, options){ |
|
||||||
options = options || {}; |
|
||||||
|
|
||||||
// root required
|
|
||||||
if (!root) throw new Error('static() root path required'); |
|
||||||
options.root = root; |
|
||||||
|
|
||||||
return function static(req, res, next) { |
|
||||||
options.path = req.url; |
|
||||||
options.getOnly = true; |
|
||||||
send(req, res, next, options); |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Expose mime module. |
|
||||||
*/ |
|
||||||
|
|
||||||
exports.mime = mime; |
|
||||||
|
|
||||||
/** |
|
||||||
* Respond with 416 "Requested Range Not Satisfiable" |
|
||||||
* |
|
||||||
* @param {ServerResponse} res |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function invalidRange(res) { |
|
||||||
var body = 'Requested Range Not Satisfiable'; |
|
||||||
res.setHeader('Content-Type', 'text/plain'); |
|
||||||
res.setHeader('Content-Length', body.length); |
|
||||||
res.statusCode = 416; |
|
||||||
res.end(body); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Attempt to tranfer the requseted file to `res`. |
|
||||||
* |
|
||||||
* @param {ServerRequest} |
|
||||||
* @param {ServerResponse} |
|
||||||
* @param {Function} next |
|
||||||
* @param {Object} options |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
var send = exports.send = function(req, res, next, options){ |
|
||||||
options = options || {}; |
|
||||||
if (!options.path) throw new Error('path required'); |
|
||||||
|
|
||||||
// setup
|
|
||||||
var maxAge = options.maxAge || 0 |
|
||||||
, ranges = req.headers.range |
|
||||||
, head = 'HEAD' == req.method |
|
||||||
, get = 'GET' == req.method |
|
||||||
, root = options.root ? normalize(options.root) : null |
|
||||||
, redirect = false === options.redirect ? false : true |
|
||||||
, getOnly = options.getOnly |
|
||||||
, fn = options.callback |
|
||||||
, hidden = options.hidden |
|
||||||
, done; |
|
||||||
|
|
||||||
// replace next() with callback when available
|
|
||||||
if (fn) next = fn; |
|
||||||
|
|
||||||
// ignore non-GET requests
|
|
||||||
if (getOnly && !get && !head) return next(); |
|
||||||
|
|
||||||
// parse url
|
|
||||||
var url = parse(options.path) |
|
||||||
, path = decodeURIComponent(url.pathname) |
|
||||||
, type; |
|
||||||
|
|
||||||
// null byte(s)
|
|
||||||
if (~path.indexOf('\0')) return utils.badRequest(res); |
|
||||||
|
|
||||||
// when root is not given, consider .. malicious
|
|
||||||
if (!root && ~path.indexOf('..')) return utils.forbidden(res); |
|
||||||
|
|
||||||
// join / normalize from optional root dir
|
|
||||||
path = normalize(join(root, path)); |
|
||||||
|
|
||||||
// malicious path
|
|
||||||
if (root && 0 != path.indexOf(root)) return fn |
|
||||||
? fn(new Error('Forbidden')) |
|
||||||
: utils.forbidden(res); |
|
||||||
|
|
||||||
// index.html support
|
|
||||||
if (normalize('/') == path[path.length - 1]) path += 'index.html'; |
|
||||||
|
|
||||||
// "hidden" file
|
|
||||||
if (!hidden && '.' == basename(path)[0]) return next(); |
|
||||||
|
|
||||||
fs.stat(path, function(err, stat){ |
|
||||||
// mime type
|
|
||||||
type = mime.lookup(path); |
|
||||||
|
|
||||||
// ignore ENOENT
|
|
||||||
if (err) { |
|
||||||
if (fn) return fn(err); |
|
||||||
return 'ENOENT' == err.code |
|
||||||
? next() |
|
||||||
: next(err); |
|
||||||
// redirect directory in case index.html is present
|
|
||||||
} else if (stat.isDirectory()) { |
|
||||||
if (!redirect) return next(); |
|
||||||
res.statusCode = 301; |
|
||||||
res.setHeader('Location', url.pathname + '/'); |
|
||||||
res.end('Redirecting to ' + url.pathname + '/'); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// header fields
|
|
||||||
if (!res.getHeader('Date')) res.setHeader('Date', new Date().toUTCString()); |
|
||||||
if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + (maxAge / 1000)); |
|
||||||
if (!res.getHeader('Last-Modified')) res.setHeader('Last-Modified', stat.mtime.toUTCString()); |
|
||||||
if (!res.getHeader('ETag')) res.setHeader('ETag', utils.etag(stat)); |
|
||||||
if (!res.getHeader('content-type')) { |
|
||||||
var charset = mime.charsets.lookup(type); |
|
||||||
res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); |
|
||||||
} |
|
||||||
res.setHeader('Accept-Ranges', 'bytes'); |
|
||||||
|
|
||||||
// conditional GET support
|
|
||||||
if (utils.conditionalGET(req)) { |
|
||||||
if (!utils.modified(req, res)) { |
|
||||||
req.emit('static'); |
|
||||||
return utils.notModified(res); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var opts = {}; |
|
||||||
var chunkSize = stat.size; |
|
||||||
|
|
||||||
// we have a Range request
|
|
||||||
if (ranges) { |
|
||||||
ranges = utils.parseRange(stat.size, ranges); |
|
||||||
// valid
|
|
||||||
if (ranges) { |
|
||||||
// TODO: stream options
|
|
||||||
// TODO: multiple support
|
|
||||||
opts.start = ranges[0].start; |
|
||||||
opts.end = ranges[0].end; |
|
||||||
chunkSize = opts.end - opts.start + 1; |
|
||||||
res.statusCode = 206; |
|
||||||
res.setHeader('Content-Range', 'bytes ' |
|
||||||
+ opts.start |
|
||||||
+ '-' |
|
||||||
+ opts.end |
|
||||||
+ '/' |
|
||||||
+ stat.size); |
|
||||||
// invalid
|
|
||||||
} else { |
|
||||||
return fn |
|
||||||
? fn(new Error('Requested Range Not Satisfiable')) |
|
||||||
: invalidRange(res); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
res.setHeader('Content-Length', chunkSize); |
|
||||||
|
|
||||||
// transfer
|
|
||||||
if (head) return res.end(); |
|
||||||
|
|
||||||
// stream
|
|
||||||
var stream = fs.createReadStream(path, opts); |
|
||||||
req.emit('static', stream); |
|
||||||
stream.pipe(res); |
|
||||||
|
|
||||||
// callback
|
|
||||||
if (fn) { |
|
||||||
function callback(err) { done || fn(err); done = true } |
|
||||||
req.on('close', callback); |
|
||||||
stream.on('end', callback); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
@ -1,175 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - staticCache |
|
||||||
* Copyright(c) 2011 Sencha Inc. |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var http = require('http') |
|
||||||
, utils = require('../utils') |
|
||||||
, Cache = require('../cache') |
|
||||||
, url = require('url') |
|
||||||
, fs = require('fs'); |
|
||||||
|
|
||||||
/** |
|
||||||
* Enables a memory cache layer on top of |
|
||||||
* the `static()` middleware, serving popular |
|
||||||
* static files. |
|
||||||
* |
|
||||||
* By default a maximum of 128 objects are |
|
||||||
* held in cache, with a max of 256k each, |
|
||||||
* totalling ~32mb. |
|
||||||
* |
|
||||||
* A Least-Recently-Used (LRU) cache algo |
|
||||||
* is implemented through the `Cache` object, |
|
||||||
* simply rotating cache objects as they are |
|
||||||
* hit. This means that increasingly popular |
|
||||||
* objects maintain their positions while |
|
||||||
* others get shoved out of the stack and |
|
||||||
* garbage collected. |
|
||||||
* |
|
||||||
* Benchmarks: |
|
||||||
* |
|
||||||
* static(): 2700 rps |
|
||||||
* node-static: 5300 rps |
|
||||||
* static() + staticCache(): 7500 rps |
|
||||||
* |
|
||||||
* Options: |
|
||||||
* |
|
||||||
* - `maxObjects` max cache objects [128] |
|
||||||
* - `maxLength` max cache object length 256kb |
|
||||||
* |
|
||||||
* @param {Type} name |
|
||||||
* @return {Type} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = function staticCache(options){ |
|
||||||
var options = options || {} |
|
||||||
, cache = new Cache(options.maxObjects || 128) |
|
||||||
, maxlen = options.maxLength || 1024 * 256; |
|
||||||
|
|
||||||
return function staticCache(req, res, next){ |
|
||||||
var path = url.parse(req.url).pathname |
|
||||||
, ranges = req.headers.range |
|
||||||
, hit = cache.get(path) |
|
||||||
, hitCC |
|
||||||
, uaCC |
|
||||||
, header |
|
||||||
, age; |
|
||||||
|
|
||||||
// cache static
|
|
||||||
req.on('static', function(stream){ |
|
||||||
var headers = res._headers |
|
||||||
, cc = utils.parseCacheControl(headers['cache-control'] || '') |
|
||||||
, contentLength = headers['content-length'] |
|
||||||
, hit; |
|
||||||
|
|
||||||
// ignore larger files
|
|
||||||
if (!contentLength || contentLength > maxlen) return; |
|
||||||
|
|
||||||
// dont cache items we shouldn't be
|
|
||||||
if ( cc['no-cache'] |
|
||||||
|| cc['no-store'] |
|
||||||
|| cc['private'] |
|
||||||
|| cc['must-revalidate']) return; |
|
||||||
|
|
||||||
// if already in cache then validate
|
|
||||||
if (hit = cache.get(path)){ |
|
||||||
if (headers.etag == hit[0].etag) { |
|
||||||
hit[0].date = new Date; |
|
||||||
return; |
|
||||||
} else { |
|
||||||
cache.remove(path); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// validation notifiactions don't contain a steam
|
|
||||||
if (null == stream) return; |
|
||||||
|
|
||||||
// add the cache object
|
|
||||||
var arr = cache.add(path); |
|
||||||
arr.push(headers); |
|
||||||
|
|
||||||
// store the chunks
|
|
||||||
stream.on('data', function(chunk){ |
|
||||||
arr.push(chunk); |
|
||||||
}); |
|
||||||
|
|
||||||
// flag it as complete
|
|
||||||
stream.on('end', function(){ |
|
||||||
arr.complete = true; |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
// cache hit, doesnt support range requests
|
|
||||||
if (hit && hit.complete && !ranges) { |
|
||||||
header = utils.merge({}, hit[0]); |
|
||||||
header.Age = age = (new Date - new Date(header.date)) / 1000 | 0; |
|
||||||
header.date = new Date().toUTCString(); |
|
||||||
|
|
||||||
// parse cache-controls
|
|
||||||
hitCC = utils.parseCacheControl(header['cache-control'] || ''); |
|
||||||
uaCC = utils.parseCacheControl(req.headers['cache-control'] || ''); |
|
||||||
|
|
||||||
// check if we must revalidate(bypass)
|
|
||||||
if (hitCC['no-cache'] || uaCC['no-cache']) return next(); |
|
||||||
|
|
||||||
// check freshness of entity
|
|
||||||
if (isStale(hitCC, age) || isStale(uaCC, age)) return next(); |
|
||||||
|
|
||||||
// conditional GET support
|
|
||||||
if (utils.conditionalGET(req)) { |
|
||||||
if (!utils.modified(req, res, header)) { |
|
||||||
header['content-length'] = 0; |
|
||||||
res.writeHead(304, header); |
|
||||||
return res.end(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// HEAD support
|
|
||||||
if ('HEAD' == req.method) { |
|
||||||
header['content-length'] = 0; |
|
||||||
res.writeHead(200, header); |
|
||||||
return res.end(); |
|
||||||
} |
|
||||||
|
|
||||||
// respond with cache
|
|
||||||
res.writeHead(200, header); |
|
||||||
|
|
||||||
// backpressure
|
|
||||||
function write(i) { |
|
||||||
var buf = hit[i]; |
|
||||||
if (!buf) return res.end(); |
|
||||||
if (false === res.write(buf)) { |
|
||||||
res.once('drain', function(){ |
|
||||||
write(++i); |
|
||||||
}); |
|
||||||
} else { |
|
||||||
write(++i); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return write(1); |
|
||||||
} |
|
||||||
|
|
||||||
next(); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Check if cache item is stale |
|
||||||
* |
|
||||||
* @param {Object} cc |
|
||||||
* @param {Number} age |
|
||||||
* @return {Boolean} |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
|
|
||||||
function isStale(cc, age) { |
|
||||||
return cc['max-age'] && cc['max-age'] <= age; |
|
||||||
} |
|
@ -1,44 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect - vhost |
|
||||||
* Copyright(c) 2010 Sencha Inc. |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Setup vhost for the given `hostname` and `server`. |
|
||||||
* |
|
||||||
* Examples: |
|
||||||
* |
|
||||||
* connect( |
|
||||||
* connect.vhost('foo.com', |
|
||||||
* connect.createServer(...middleware...) |
|
||||||
* ), |
|
||||||
* connect.vhost('bar.com', |
|
||||||
* connect.createServer(...middleware...) |
|
||||||
* ) |
|
||||||
* ); |
|
||||||
* |
|
||||||
* @param {String} hostname |
|
||||||
* @param {Server} server |
|
||||||
* @return {Function} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
module.exports = function vhost(hostname, server){ |
|
||||||
if (!hostname) throw new Error('vhost hostname required'); |
|
||||||
if (!server) throw new Error('vhost server required'); |
|
||||||
var regexp = new RegExp('^' + hostname.replace(/[*]/g, '(.*?)') + '$'); |
|
||||||
if (server.onvhost) server.onvhost(hostname); |
|
||||||
return function vhost(req, res, next){ |
|
||||||
if (!req.headers.host) return next(); |
|
||||||
var host = req.headers.host.split(':')[0]; |
|
||||||
if (req.subdomains = regexp.exec(host)) { |
|
||||||
req.subdomains = req.subdomains[0].split('.').slice(0, -1); |
|
||||||
server.emit("request", req, res); |
|
||||||
} else { |
|
||||||
next(); |
|
||||||
} |
|
||||||
}; |
|
||||||
}; |
|
@ -1,79 +0,0 @@ |
|||||||
|
|
||||||
/*! |
|
||||||
* Connect |
|
||||||
* Copyright(c) 2011 TJ Holowaychuk |
|
||||||
* MIT Licensed |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* Module dependencies. |
|
||||||
*/ |
|
||||||
|
|
||||||
var http = require('http') |
|
||||||
, res = http.OutgoingMessage.prototype; |
|
||||||
|
|
||||||
// original setHeader()
|
|
||||||
|
|
||||||
var setHeader = res.setHeader; |
|
||||||
|
|
||||||
// original _renderHeaders()
|
|
||||||
|
|
||||||
var _renderHeaders = res._renderHeaders; |
|
||||||
|
|
||||||
if (res._hasConnectPatch) return; |
|
||||||
|
|
||||||
/** |
|
||||||
* Provide a public "header sent" flag |
|
||||||
* until node does. |
|
||||||
* |
|
||||||
* @return {Boolean} |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
res.__defineGetter__('headerSent', function(){ |
|
||||||
return this._headerSent; |
|
||||||
}); |
|
||||||
|
|
||||||
/** |
|
||||||
* Set header `field` to `val`, special-casing |
|
||||||
* the `Set-Cookie` field for multiple support. |
|
||||||
* |
|
||||||
* @param {String} field |
|
||||||
* @param {String} val |
|
||||||
* @api public |
|
||||||
*/ |
|
||||||
|
|
||||||
res.setHeader = function(field, val){ |
|
||||||
var key = field.toLowerCase() |
|
||||||
, prev; |
|
||||||
|
|
||||||
// special-case Set-Cookie
|
|
||||||
if (this._headers && 'set-cookie' == key) { |
|
||||||
if (prev = this.getHeader(field)) { |
|
||||||
val = Array.isArray(prev) |
|
||||||
? prev.concat(val) |
|
||||||
: [prev, val]; |
|
||||||
} |
|
||||||
// charset
|
|
||||||
} else if ('content-type' == key && this.charset) { |
|
||||||
val += '; charset=' + this.charset; |
|
||||||
} |
|
||||||
|
|
||||||
return setHeader.call(this, field, val); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Proxy `res.end()` to expose a 'header' event, |
|
||||||
* allowing arbitrary augmentation before the header |
|
||||||
* fields are written to the socket. |
|
||||||
* |
|
||||||
* NOTE: this _only_ supports node's progressive header |
|
||||||
* field API aka `res.setHeader()`. |
|
||||||
*/ |
|
||||||
|
|
||||||
res._renderHeaders = function(){ |
|
||||||
this.emit('header'); |
|
||||||
return _renderHeaders.call(this); |
|
||||||
}; |
|
||||||
|
|
||||||
res._hasConnectPatch = true; |
|
@ -1,75 +0,0 @@ |
|||||||
<html> |
|
||||||
<head> |
|
||||||
<title>listing directory {directory}</title> |
|
||||||
<style>{style}</style> |
|
||||||
<script> |
|
||||||
function $(id){ |
|
||||||
var el = 'string' == typeof id |
|
||||||
? document.getElementById(id) |
|
||||||
: id; |
|
||||||
|
|
||||||
el.on = function(event, fn){ |
|
||||||
if ('content loaded' == event) event = 'DOMContentLoaded'; |
|
||||||
el.addEventListener(event, fn, false); |
|
||||||
}; |
|
||||||
|
|
||||||
el.all = function(selector){ |
|
||||||
return $(el.querySelectorAll(selector)); |
|
||||||
}; |
|
||||||
|
|
||||||
el.each = function(fn){ |
|
||||||
for (var i = 0, len = el.length; i < len; ++i) { |
|
||||||
fn($(el[i]), i); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
el.getClasses = function(){ |
|
||||||
return this.getAttribute('class').split(/\s+/); |
|
||||||
}; |
|
||||||
|
|
||||||
el.addClass = function(name){ |
|
||||||
var classes = this.getAttribute('class'); |
|
||||||
el.setAttribute('class', classes |
|
||||||
? classes + ' ' + name |
|
||||||
: name); |
|
||||||
}; |
|
||||||
|
|
||||||
el.removeClass = function(name){ |
|
||||||
var classes = this.getClasses().filter(function(curr){ |
|
||||||
return curr != name; |
|
||||||
}); |
|
||||||
this.setAttribute('class', classes); |
|
||||||
}; |
|
||||||
|
|
||||||
return el; |
|
||||||
} |
|
||||||
|
|
||||||
function search() { |
|
||||||
var str = $('search').value |
|
||||||
, links = $('files').all('a'); |
|
||||||
|
|
||||||
links.each(function(link){ |
|
||||||
var text = link.textContent; |
|
||||||
|
|
||||||
if ('..' == text) return; |
|
||||||
if (str.length && ~text.indexOf(str)) { |
|
||||||
link.addClass('highlight'); |
|
||||||
} else { |
|
||||||
link.removeClass('highlight'); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
$(window).on('content loaded', function(){ |
|
||||||
$('search').on('keyup', search); |
|
||||||
}); |
|
||||||
</script> |
|
||||||
</head> |
|
||||||
<body class="directory"> |
|
||||||
<input id="search" type="text" placeholder="Search" autocomplete="off" /> |
|
||||||
<div id="wrapper"> |
|
||||||
<h1>{linked-path}</h1> |
|
||||||
{files} |
|
||||||
</div> |
|
||||||
</body> |
|
||||||
</html> |
|
@ -1,13 +0,0 @@ |
|||||||
<html> |
|
||||||
<head> |
|
||||||
<title>{error}</title> |
|
||||||
<style>{style}</style> |
|
||||||
</head> |
|
||||||
<body> |
|
||||||
<div id="wrapper"> |
|
||||||
<h1>{title}</h1> |
|
||||||
<h2><em>500</em> {error}</h2> |
|
||||||
<ul id="stacktrace">{stack}</ul> |
|
||||||
</div> |
|
||||||
</body> |
|
||||||
</html> |
|
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 635 B |
Before Width: | Height: | Size: 739 B |
Before Width: | Height: | Size: 794 B |
Before Width: | Height: | Size: 818 B |
Before Width: | Height: | Size: 663 B |
Before Width: | Height: | Size: 740 B |
Before Width: | Height: | Size: 807 B |
Before Width: | Height: | Size: 793 B |
Before Width: | Height: | Size: 817 B |
Before Width: | Height: | Size: 879 B |
Before Width: | Height: | Size: 833 B |
Before Width: | Height: | Size: 779 B |
Before Width: | Height: | Size: 621 B |
Before Width: | Height: | Size: 801 B |
Before Width: | Height: | Size: 839 B |
Before Width: | Height: | Size: 830 B |
Before Width: | Height: | Size: 813 B |
Before Width: | Height: | Size: 703 B |
Before Width: | Height: | Size: 641 B |
Before Width: | Height: | Size: 858 B |
Before Width: | Height: | Size: 774 B |
Before Width: | Height: | Size: 294 B |
Before Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 664 B |
Before Width: | Height: | Size: 512 B |
Before Width: | Height: | Size: 587 B |
Before Width: | Height: | Size: 656 B |
Before Width: | Height: | Size: 666 B |
Before Width: | Height: | Size: 603 B |
Before Width: | Height: | Size: 587 B |
Before Width: | Height: | Size: 592 B |
Before Width: | Height: | Size: 724 B |
Before Width: | Height: | Size: 309 B |
Before Width: | Height: | Size: 621 B |
Before Width: | Height: | Size: 700 B |
Before Width: | Height: | Size: 639 B |
Before Width: | Height: | Size: 579 B |