/src/server/cache.js |
@@ -0,0 +1,157 @@ |
#!/usr/bin/env node |
|
/*************************************************************************/ |
/* Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3 */ |
/*************************************************************************/ |
|
const fs = require('fs'); |
const stream = require('stream'); |
const util = require('util'); |
const tz = require('moment-timezone'); |
const forge = require('node-forge'); |
const EventEmitter = require('events').EventEmitter; |
|
// Cache constructor. |
function Cache(config, client, request, response) { |
// Create events emitters for logging and data. |
EventEmitter.call(this); |
|
// Pass through objects needed for caching. |
this.config = config; |
this.client = client; |
this.request = request; |
this.response = response; |
}; |
|
// Cache handling. |
Cache.prototype.process = function(resource, input, type) { |
EventEmitter.call(this); |
const self = this; |
|
// Read the resource and cache or not depending on configuration settings. |
fs.stat(resource, (error, stats) => { |
var expires = 0; |
Object.keys(self.config.site.cache).forEach((key) => { |
self.config.site.cache[key].forEach((expire) => { |
if (expire.test(resource)) { |
expires = key; |
} |
}); |
}); |
|
switch (self.request.httpVersion) { |
case '1.1': // HTTP 1.1 |
self.response.setHeader('Cache-Control', |
"max-age=" + expires + ", public" |
); |
const sha1 = forge.md.sha1.create(); |
const data = new stream.Readable({ |
objectMode: true, |
read(size) {} |
}); |
input |
.on('data', (chunk) => { |
sha1.update(chunk); |
data.push(chunk); |
}) |
.on('end', () => { |
const etag = sha1.digest().toHex(); |
|
// Set the ETag for the resource. |
self.response.setHeader('ETag', etag); |
|
const ifNoneMatch = Object |
.getOwnPropertyNames(self.request.headers) |
.filter((header) => header.toUpperCase() === |
'If-None-Match'.toUpperCase()); |
|
const ifModifiedSince = Object |
.getOwnPropertyNames(self.request.headers) |
.filter((header) => header.toUpperCase() === |
'If-Modified-Since'.toUpperCase()); |
|
// Set content type. |
self.response.setHeader('Content-Type', type); |
|
if ((ifNoneMatch.length !== 0 && |
self.request.headers[ifNoneMatch].toUpperCase() === etag.toUpperCase()) || |
(ifModifiedSince.length !== 0 && |
tz(self.request.headers[ifModifiedSince]).tz('UTC') < tz(stat.mtime).tz('UTC'))) { |
// Send a cache hit response. |
self.emit('log', { |
message: 'Client: ' + |
self.client.address + ':' + |
self.client.port + |
' cached resource: ' + |
resource, |
severity: 'info' |
}); |
self.emit('data', { |
status: 304, |
data: new stream.Readable({ |
read(size) { |
this.push(null); |
} |
}), |
type: type |
}); |
return; |
} |
|
// Send the resource. |
self.emit('log', { |
message: 'Client: ' + |
self.client.address + ':' + |
self.client.port + |
' sent resource: ' + |
resource, |
severity: 'info' |
}); |
data.push(null); |
self.emit('data', { |
status: 200, |
data: data, |
type: type |
}); |
}); |
|
return; |
default: |
self.response.setHeader('Last-Modified', |
tz(stats.mtime) |
.tz('UTC') |
.format("ddd, DD MMM YYYY HH:mm:ss z") |
); |
self.response.setHeader('Expires', |
tz() |
.tz('UTC') |
.add(expires, 'seconds') |
.format("ddd, DD MMM YYYY HH:mm:ss z") |
); |
|
// Set content type. |
self.response.setHeader('Content-Type', type); |
|
// Send the resource. |
self.emit('log', { |
message: 'Client: ' + |
self.client.address + ':' + |
self.client.port + |
' sent resource: ' + |
resource, |
severity: 'info' |
}); |
self.emit('data', { |
status: 200, |
data: input, |
type: type |
}); |
break; |
} |
}); |
|
return this; |
}; |
|
util.inherits(Cache, EventEmitter); |
module.exports = Cache; |
/src/server/certs.js |
@@ -0,0 +1,81 @@ |
#!/usr/bin/env node |
|
/*************************************************************************/ |
/* Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3 */ |
/*************************************************************************/ |
|
const forge = require('node-forge'); |
const tz = require('moment-timezone'); |
|
module.exports = { |
// Generate certificates on the fly using incremental serials. |
generate: (name, domain, keySize, callback) => { |
process.nextTick(() => { |
// Generate 1024-bit key-pair. |
const keys = forge |
.pki |
.rsa |
.generateKeyPair(keySize); |
// Create self-signed certificate. |
const cert = forge |
.pki |
.createCertificate(); |
cert.serialNumber = tz().tz('UTC').format('x'); |
cert.publicKey = keys.publicKey; |
cert |
.validity |
.notBefore = tz().tz('UTC').toDate(); |
cert |
.validity |
.notAfter |
.setFullYear( |
cert |
.validity |
.notBefore |
.getFullYear() + 1 |
); |
cert.setSubject([{ |
name: 'commonName', |
value: domain |
}, { |
name: 'organizationName', |
value: name |
}]); |
cert.setIssuer([{ |
name: 'commonName', |
value: domain |
}, { |
name: 'organizationName', |
value: name |
}]); |
|
// Self-sign certificate. |
cert.sign( |
keys.privateKey, |
forge |
.md |
.sha256 |
.create() |
); |
|
// Summon the callback with the certificate block. |
callback({ |
privateKey: forge |
.pki |
.privateKeyToPem( |
keys |
.privateKey |
), |
publicKey: forge |
.pki |
.publicKeyToPem( |
keys |
.publicKey |
), |
certificate: forge |
.pki |
.certificateToPem(cert) |
}); |
}); |
} |
}; |