diff --git a/WHATSNEW.md b/WHATSNEW.md index e15f1b8e..51594b04 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -10,6 +10,8 @@ This document attempts to track **major** changes and additions in ENiGMA½. For * The `message` arg used by `msg_list` has been deprecated. Please starting using `messageIndex` for this purpose. Support for `message` will be removed in the future. * A number of new MCI codes (see [MCI](./docs/art/mci.md)) * Added ability to export/download messages. This is enabled in the default menu. See `messageAreaViewPost` in [the default message base template](./misc/menu_templates/message_base.in.hjson) and look for the download options (`@method:addToDownloadQueue`, etc.) for details on adding to your system! +* The Gopher server has had a revamp! Standard `gophermap` files are now served along with any other content you configure for your Gopher Hole! A default [gophermap](https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu) can be found [in the misc directory](./misc/gophermap) that behaves like the previous implementation. See [Gopher docs](./docs/servers/gopher.md) for more information. +* Default file browser up/down/pageUp/pageDown scrolls description (e.g. FILE_ID.DIZ). If you want to expose this on an existing system see the `fileBaseListEntries` in the default `file_base.in.hjson` template. ## 0.0.11-beta * Upgraded from `alpha` to `beta` -- The software is far along and mature enough at this point! diff --git a/art/themes/luciano_blocktronics/FBHELP.ANS b/art/themes/luciano_blocktronics/FBHELP.ANS index 5dc95322..08ea5885 100644 Binary files a/art/themes/luciano_blocktronics/FBHELP.ANS and b/art/themes/luciano_blocktronics/FBHELP.ANS differ diff --git a/art/themes/luciano_blocktronics/FBRWSE.ANS b/art/themes/luciano_blocktronics/FBRWSE.ANS index 52db95e0..c2b617db 100644 Binary files a/art/themes/luciano_blocktronics/FBRWSE.ANS and b/art/themes/luciano_blocktronics/FBRWSE.ANS differ diff --git a/core/config_default.js b/core/config_default.js index 82d3eb1d..98aeb4af 100644 --- a/core/config_default.js +++ b/core/config_default.js @@ -263,7 +263,7 @@ module.exports = () => { port : 8070, publicHostname : 'another-fine-enigma-bbs.org', publicPort : 8070, // adjust if behind NAT/etc. - bannerFile : 'gopher_banner.asc', + staticRoot : paths.join(__dirname, './../gopher'), // // Set messageConferences{} to maps of confTag -> [ areaTag1, areaTag2, ... ] diff --git a/core/file_area_list.js b/core/file_area_list.js index a62271ca..12abfd0d 100644 --- a/core/file_area_list.js +++ b/core/file_area_list.js @@ -144,7 +144,10 @@ exports.getModule = class FileAreaList extends MenuModule { }, displayHelp : (formData, extraArgs, cb) => { return this.displayHelpPage(cb); - } + }, + movementKeyPressed : (formData, extraArgs, cb) => { + return this._handleMovementKeyPress(_.get(formData, 'key.name'), cb); + }, }; } @@ -505,6 +508,23 @@ exports.getModule = class FileAreaList extends MenuModule { ); } + _handleMovementKeyPress(keyName, cb) { + const descView = this.viewControllers.browse.getView(MciViewIds.browse.desc); + if (!descView) { + return cb(null); + } + + switch (keyName) { + case 'down arrow' : descView.scrollDocumentUp(); break; + case 'up arrow' : descView.scrollDocumentDown(); break; + case 'page up' : descView.keyPressPageUp(); break; + case 'page down' : descView.keyPressPageDown(); break; + } + + this.viewControllers.browse.switchFocus(MciViewIds.browse.navMenu); + return cb(null); + } + fetchAndDisplayWebDownloadLink(cb) { const self = this; diff --git a/core/servers/content/gopher.js b/core/servers/content/gopher.js index 4e51c889..442ecdcb 100644 --- a/core/servers/content/gopher.js +++ b/core/servers/content/gopher.js @@ -27,6 +27,7 @@ const _ = require('lodash'); const fs = require('graceful-fs'); const paths = require('path'); const moment = require('moment'); +const async = require('async'); const ModuleInfo = exports.moduleInfo = { name : 'Gopher', @@ -81,8 +82,8 @@ exports.getModule = class GopherModule extends ServerModule { this.publicHostname = config.contentServers.gopher.publicHostname; this.publicPort = config.contentServers.gopher.publicPort; - this.addRoute(/^\/?\r\n$/, this.defaultGenerator); - this.addRoute(/^\/msgarea(\/[a-z0-9_-]+(\/[a-z0-9_-]+)?(\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(_raw)?)?)?\/?\r\n$/, this.messageAreaGenerator); + this.addRoute(/^\/?msgarea(\/[a-z0-9_-]+(\/[a-z0-9_-]+)?(\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(_raw)?)?)?\/?\r\n$/, this.messageAreaGenerator); + this.addRoute(/^(\/?[^\t\r\n]*)\r\n$/, this.staticGenerator); this.server = net.createServer( socket => { socket.setEncoding('ascii'); @@ -161,22 +162,56 @@ exports.getModule = class GopherModule extends ServerModule { return `${itemType}${text}\t${selector}\t${hostname}\t${port}\r\n`; } - defaultGenerator(selectorMatch, cb) { - this.log.debug( { selector : selectorMatch[0] }, 'Serving default content'); + staticGenerator(selectorMatch, cb) { + this.log.debug( { selector : selectorMatch[1] || '(gophermap)' }, 'Serving static content'); - let bannerFile = _.get(Config(), 'contentServers.gopher.bannerFile', 'gopher_banner.asc'); - bannerFile = paths.isAbsolute(bannerFile) ? bannerFile : paths.join(__dirname, '../../../misc', bannerFile); - fs.readFile(bannerFile, 'utf8', (err, banner) => { - if(err) { - return cb('You have reached an ENiGMA½ Gopher server!'); + const requestedPath = selectorMatch[1]; + let path = this.resolveContentPath(requestedPath); + if (!path) { + return cb('Not found'); + } + + fs.stat(path, (err, stats) => { + if (err) { + return cb('Not found'); } - banner = splitTextAtTerms(banner).map(l => this.makeItem(ItemTypes.InfoMessage, l)).join(''); - banner += this.makeItem(ItemTypes.SubMenu, 'Public Message Area', '/msgarea'); - return cb(banner); + let isGopherMap = false; + if (stats.isDirectory()) { + path = paths.join(path, 'gophermap'); + isGopherMap = true; + } + + fs.readFile(path, isGopherMap ? 'utf8' : null, (err, content) => { + if (err) { + let content = 'You have reached an ENiGMA½ Gopher server!\r\n'; + content += this.makeItem(ItemTypes.SubMenu, 'Public Message Area', '/msgarea'); + return cb(content); + } + + if (isGopherMap) { + // Convert any UNIX style LF's to DOS CRLF's + content = content.replace(/\r?\n/g, '\r\n'); + + // variable support + content = content + .replace(/{publicHostname}/g, this.publicHostname) + .replace(/{publicPort}/g, this.publicPort); + } + + return cb(content); + }); }); } + resolveContentPath(requestPath) { + const staticRoot = _.get(Config(), 'contentServers.gopher.staticRoot'); + const path = paths.resolve(staticRoot, `.${requestPath}`); + if (path.startsWith(staticRoot)) { + return path; + } + } + notFoundGenerator(selector, cb) { this.log.debug( { selector }, 'Serving not found content'); return cb('Not found'); diff --git a/core/servers/content/web.js b/core/servers/content/web.js index 04b9ccdd..70ede1c1 100644 --- a/core/servers/content/web.js +++ b/core/servers/content/web.js @@ -215,20 +215,22 @@ exports.getModule = class WebServerModule extends ServerModule { routeIndex(req, resp) { const filePath = paths.join(Config().contentServers.web.staticRoot, 'index.html'); - return this.returnStaticPage(filePath, resp); } routeStaticFile(req, resp) { const fileName = req.url.substr(req.url.indexOf('/', 1)); - const filePath = paths.join(Config().contentServers.web.staticRoot, fileName); - + const filePath = this.resolveStaticPath(fileName); return this.returnStaticPage(filePath, resp); } returnStaticPage(filePath, resp) { const self = this; + if (!filePath) { + return this.fileNotFound(resp); + } + fs.stat(filePath, (err, stats) => { if(err || !stats.isFile()) { return self.fileNotFound(resp); @@ -245,6 +247,14 @@ exports.getModule = class WebServerModule extends ServerModule { }); } + resolveStaticPath(requestPath) { + const staticRoot = _.get(Config(), 'contentServers.web.staticRoot'); + const path = paths.resolve(staticRoot, `.${requestPath}`); + if (path.startsWith(staticRoot)) { + return path; + } + } + routeTemplateFilePage(templatePath, preprocessCallback, resp) { const self = this; diff --git a/docs/servers/gopher.md b/docs/servers/gopher.md index 343c7d80..03c34bed 100644 --- a/docs/servers/gopher.md +++ b/docs/servers/gopher.md @@ -3,7 +3,7 @@ layout: page title: Gopher Server --- ## The Gopher Content Server -The Gopher *content server* provides access to publicly exposed message conferences and areas over Gopher (gopher://). +The Gopher *content server* provides access to publicly exposed message conferences and areas over Gopher (gopher://) as well as any other content you wish to serve in your Gopher Hole! ## Configuration Gopher configuration is found in `contentServers.gopher` in `config.hjson`. @@ -11,14 +11,38 @@ Gopher configuration is found in `contentServers.gopher` in `config.hjson`. | Item | Required | Description | |------|----------|-------------| | `enabled` | :+1: | Set to `true` to enable Gopher | +| `staticRoot` | :+1: | Sets the path serving as the static root path for all Gopher content. Defaults to `enigma-bbs/gopher`.
See also **Gophermap's** below | | `port` | :-1: | Override the default port of `8070` | -| `publicHostName` | :+1: | Set the **public** hostname/domain that Gopher will serve to the outside world. Example: `myfancybbs.com` | +| `publicHostname` | :+1: | Set the **public** hostname/domain that Gopher will serve to the outside world. Example: `myfancybbs.com` | | `publicPort` | :+1: | Set the **public** port that Gopher will serve to the outside world. | -| `messageConferences` | :+1: | An map of *conference tags* to *area tags* that are publicly exposed via Gopher. See example below. | +| `messageConferences` | :-1: | An map of *conference tags* to *area tags* that are publicly exposed via Gopher. See example below. | -Notes on `publicHostName` and `publicPort`: +Notes on `publicHostname` and `publicPort`: The Gopher protocol serves content that contains host/domain and port even when referencing it's own documents. Due to this, these members must be set to your publicly addressable Gopher server! +## Gophermap's +[Gophermap's](https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu) are how to build menus for your Gopher Hole. Each map is a simple text file named `gophermap` (all lowercase, no extension) with DOS style CRLF endings. + +Within any directory nested within your `staticRoot` may live a `gophermap`. A template may be found in the `enigma-bbsmisc` directory. + +ENiGMA will pre-process `gophermap` files replacing in following variables: +* `{publicHostname}`: The public hostname from your config. +* `{publicPort}`: The public port from your config. + +:information_source: See [Wikipedia](https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu) for more information on the `gophermap` format. + +:information_source: See [RFC 1436](https://tools.ietf.org/html/rfc1436) for the original Gopher spec. + +:bulb: Tools such as [gfu](https://rawtext.club/~sloum/gfu.html) may help you with `gophermap`'s + +### Example Gophermap +An example `gophermap` living in `enigma-bbs/gopher`: +``` +iWelcome to a Gopher server! {publicHostname} {publicPort} +1Public Message Area /msgarea {publicHostname} {publicPort} +. +``` + ### Example Let's suppose you are serving Gopher for your BBS at `myfancybbs.com`. Your ENiGMA½ system is listening on the default Gopher `port` of 8070 but you're behind a firewall and want port 70 exposed to the public. Lastly, you want to expose some fsxNet areas: @@ -26,9 +50,10 @@ Let's suppose you are serving Gopher for your BBS at `myfancybbs.com`. Your ENiG contentServers: { gopher: { enabled: true - publicHostName: myfancybbs.com + publicHostname: myfancybbs.com publicPort: 70 + // Expose some public message conferences/areas messageConferences: { fsxnet: { // fsxNet's conf tag // Areas of fsxNet we want to expose: diff --git a/gopher/README b/gopher/README new file mode 100644 index 00000000..9f773a65 --- /dev/null +++ b/gopher/README @@ -0,0 +1 @@ +Copy ../misc/gophermap to this directory and edit to your liking! diff --git a/misc/config_template.in.hjson b/misc/config_template.in.hjson index 1db5406d..21cefeb6 100644 --- a/misc/config_template.in.hjson +++ b/misc/config_template.in.hjson @@ -242,8 +242,8 @@ port: XXXXX enabled: false - // bannerFile path in misc/ by default. Full paths allowed. - bannerFile: XXXXX + // The root directory to serve gophermaps and other content + staticRoot: XXXXX // // The Gopher Content Server can export message base diff --git a/misc/gopher_banner.asc b/misc/gopher_banner.asc deleted file mode 100644 index b758e066..00000000 --- a/misc/gopher_banner.asc +++ /dev/null @@ -1,9 +0,0 @@ -_____________________ _____ ____________________ __________\_ / -\__ ____/\_ ____ \ /____/ / _____ __ \ / ______/ // /___jp! -// __|___// | \// |// | \// | | \// \ /___ /_____ -/____ _____| __________ ___|__| ____| \ / _____ \ ----- \______\ -- |______\ ------ /______/ ---- |______\ - |______\ /__/ // ___/ - /__ _\ - <*> ENiGMA½ // HTTPS://GITHUB.COM/NUSKOOLER/ENIGMA-BBS <*> /__/ - -------------------------------------------------------------------------------- diff --git a/misc/gophermap b/misc/gophermap new file mode 100644 index 00000000..c4973670 --- /dev/null +++ b/misc/gophermap @@ -0,0 +1,12 @@ +i_____________________ _____ ____________________ __________\_ / +i\__ ____/\_ ____ \ /____/ / _____ __ \ / ______/ // /___jp! +i// __|___// | \// |// | \// | | \// \ /___ /_____ +i/____ _____| __________ ___|__| ____| \ / _____ \ +i---- \______\ -- |______\ ------ /______/ ---- |______\ - |______\ /__/ // ___/ +i /__ _\ +i <*> ENiGMA½ // HTTPS://GITHUB.COM/NUSKOOLER/ENIGMA-BBS <*> /__/ +i +i------------------------------------------------------------------------------- +i +1Public Message Area /msgarea {publicHostname} {publicPort} +. diff --git a/misc/install.sh b/misc/install.sh index 741c4585..9a894a37 100755 --- a/misc/install.sh +++ b/misc/install.sh @@ -146,6 +146,12 @@ install_node_packages() { fi } +copy_template_files() { + if [[ ! -f "./gopher/gophermap" ]]; then + cp "./misc/gophermap" "./gopher/gophermap" + fi +} + enigma_footer() { log "ENiGMA½ installation complete!" echo -e "\e[1;33m" @@ -189,6 +195,7 @@ install_nvm configure_nvm download_enigma_source install_node_packages +copy_template_files enigma_footer } # this ensures the entire script is downloaded before execution diff --git a/misc/menu_templates/file_base.in.hjson b/misc/menu_templates/file_base.in.hjson index 572abf97..afd497d0 100644 --- a/misc/menu_templates/file_base.in.hjson +++ b/misc/menu_templates/file_base.in.hjson @@ -73,6 +73,7 @@ mci: { MT1: { mode: preview + acceptsFocus: false } HM2: { @@ -152,6 +153,10 @@ keys: [ "?" ] action: @method:displayHelp } + { + keys: [ "down arrow", "up arrow", "page up", "page down" ] + action: @method:movementKeyPressed + } ] }