diff options
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | lib/app.js | 37 | ||||
-rw-r--r-- | lib/serve.js | 6 |
3 files changed, 43 insertions, 1 deletions
@@ -122,6 +122,7 @@ To make config options persistent, set them in `~/.ssb/config`, e.g.: - `encode_msgids`: whether to URL-encode message ids in local links. default: `true` - `auth`: HTTP auth password. default: `null` (no password required) - `allowAddresses`: Array of IP addresses allowed to connect. default: `null` (allow any to connect). Note if host is `localhost` then this setting is useless. +- `allowHosts`: Array of hostnames allowed that patchfoo may be connected at, or `*` to allow using any hostname. Default is to allow patchfoo's configured port, at patchfoo's configured host, `localhost`, `127.0.0.1` or `::1`. If hostname includes trailing colon without port, it means use patchfoo's server port. `*` for the port means allow connections at any port. If hostname begins with `.`, subdomains under it are allowed too. - `filter`: Filter setting. `"all"` to show all messages. `"invert"` to show messages that would be hidden by the default setting. Otherwise the default setting applies, which is so to only show messages authored or upvoted by yourself or by a feed that you you follow. Exceptions are that if you navigate to a user feed page, you will see messages authored by that feed, and if you navigate to a message page, you will see that message - regardless of the filter setting. The `filter` setting may also be specified per-request as a query string parameter. - `showPrivates`: Whether or not to show private messages. Default is `true`. Overridden by `filter=all`. - `previewVotes`: Whether to preview creating votes/likes/digs (`true`) or publish them immediately (`false`). default: `false` @@ -35,6 +35,7 @@ function getKey(msg) { } function App(sbot, config) { + var self = this this.sbot = sbot this.config = config @@ -55,11 +56,31 @@ function App(sbot, config) { this.voteBranches = !!config.voteBranches this.copyableIds = config.copyableIds == null ? true : config.copyableIds - this.hostname = (/:/.test(this.host) ? '[' + this.host + ']' : this.host) + this.port + this.hostname = (/:/.test(this.host) ? '[' + this.host + ']' : this.host) + ':' + this.port this.dir = path.join(config.path, conf.dir || 'patchfoo') this.scriptDir = path.join(this.dir, conf.scriptDir || 'script') this.draftsDir = path.join(this.dir, conf.draftsDir || 'drafts') + this.allowHosts = u.toArray(conf.allowHosts || + ['localhost:', '127.0.0.1:', '[::1]:', this.hostname]) + this.allowHostsParsed = this.allowHosts.indexOf('*') > -1 ? [{ + subdomains: true, + host: '', + port: '*' + }] : this.allowHosts.map(function (hostname) { + var m = /^(\.)?(.*?)(:([0-9]*?|\*))?$/.exec(hostname) + if (!m) return void console.trace('Unable to parse hostname pattern "'+hostname+'"') + var port = !m[3] ? 80 : + !m[4] ? Number(self.port) : + '*' === m[4] ? '*' : Number(m[4]) + if (port !== '*' && isNaN(port)) return void console.trace('Unable to parse port in hostname pattern "'+hostname+'". Default port: "'+self.port+'"') + return { + subdomains: !!m[1], + host: m[2], + port: port + } + }).filter(Boolean) + var base = conf.base || '/' this.opts = { base: base, @@ -117,6 +138,20 @@ function App(sbot, config) { ] } +App.prototype.isAllowedHostHeader = function (hostname) { + var m = /^(\[[^\]]+\]|[^:]+)(?::([0-9]+))?/.exec(hostname) + if (!m) return void console.error('Unable to parse Host header "'+hostname+'"') + var host = m[1], port = Number(m[2] || 80) + for (var i = 0; i < this.allowHostsParsed.length; i++) { + var allow = this.allowHostsParsed[i] + if ((allow.port === '*' || allow.port === port) + && (allow.host === '' || allow.host === host || + (allow.subdomains && host.endsWith('.'+allow.host))) + ) return true + } + return false +} + App.prototype.go = function () { var self = this var server = http.createServer(function (req, res) { diff --git a/lib/serve.js b/lib/serve.js index c3e2115..fdf24f7 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -112,6 +112,12 @@ Serve.prototype.go = function () { } } + if (!this.app.isAllowedHostHeader(this.req.headers.host)) { + console.error('Host header not allowed: "' + this.req.headers.host + '"') + this.res.writeHead(403) + return this.res.end('Forbidden') + } + this.replyMentionFeeds = conf.replyMentionFeeds == null ? true : Boolean(conf.replyMentionFeeds) |