Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ on:
branches: []

env:
CI: "true"
CI: 'true'

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node: [ 20, 22 ]
node: [24]
name: Node ${{ matrix.node }}
steps:
- uses: actions/checkout@v4
Expand All @@ -27,4 +27,3 @@ jobs:
- run: npm version
- run: npm install --ignore-scripts
- run: npm run ci

4 changes: 2 additions & 2 deletions lib/appConfig.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const os = require('os')
const fs = require('fs')
const path = require('path')
const mkdirp = require('./mkdirp')
const crypto = require('crypto')
const { merge } = require('./utils')
const mkdirp = require('./mkdirp.js')
const { merge } = require('./utils.js')

const APP = 'md-fileserver'
const CONF = 'config.json'
Expand Down
3 changes: 2 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function setupApp() {
mw.error
)

app.use(mw.session(token)).use(mw.error)
app.use(mw.session(token), mw.error)

app.get('/config', mw.renderConfig({ highlightStyles }))
app.post(
Expand All @@ -41,6 +41,7 @@ function setupApp() {
app
.use(mw.forbiddenRemote)
.use(mw.unescape)
.use(mw.forbiddenPaths)
.use(mw.noextfile)
.use(mw.stat)
.use(mw.plaintext)
Expand Down
9 changes: 6 additions & 3 deletions lib/markd.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ const { promisify } = require('util')
const fs = require('fs')
const fsP = require('fs/promises')
const path = require('path')
const markedpp = require('markedpp')
const markedpp = require('markedpp').default
const md = require('markdown-it')
const { newError } = require('./utils')
const confluencer = require('confluencer')
const { newError, contentSecurityPolicy } = require('./utils.js')

const tmpl = fs.readFileSync(path.resolve(__dirname, 'pages', 'template.html'))

Expand Down Expand Up @@ -75,7 +75,10 @@ function renderRequest(filename, req, res, next, status = 200) {

render(filename, title, req._config)
.then((rendered) => {
res.writeHead(status, { 'Content-Type': 'text/html; charset=utf-8' })
res.writeHead(status, {
'Content-Type': 'text/html; charset=utf-8',
'Content-Security-Policy': contentSecurityPolicy
})
res.write(rendered)
res.end()
})
Expand Down
43 changes: 34 additions & 9 deletions lib/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,30 @@ const serveIndex = require('serve-index')
const serveStatic = require('serve-static')
const cookie = require('cookie')

const config = require('../config')
const { renderRequest } = require('./markd')
const { renderConfig, updateConfig } = require('./renderConfig')
const config = require('../config.js')
const { renderRequest } = require('./markd.js')
const { renderConfig, updateConfig } = require('./renderConfig.js')
const {
newError,
homedir,
drive,
uri2filename,
filename2uri,
urlWithoutDrive
} = require('./utils')
} = require('./utils.js')

const MARKDOWNEXT = /\.(md|mdown|markdown)$/
const FILETEXT = /[/\\]([A-Za-z]+)$/
const FILENOEXT = /[/\\]([^.]+)$/
const FORBIDDEN_RE =
/^\/(etc|bin|sbin|usr|boot|opt|var|lib|[Cc]:\/[Ww]indows\/[Ss]ystem32)(\/|$)/

/**
* middlewares
*/
const M = {
// only allow access from localhost
forbiddenRemote: function (req, res, next) {
forbiddenRemote: function (req, _res, next) {
if (
['127.0.0.1', '::ffff:127.0.0.1', '::1'].indexOf(
req.connection.remoteAddress
Expand All @@ -38,6 +40,17 @@ const M = {
next()
}
},

// forbid access to system paths
forbiddenPaths: function (req, _res, next) {
const reqPath = uri2filename(req.url)
if (FORBIDDEN_RE.test(reqPath)) {
next(newError(403))
return
}
next()
},

// check session
session: (token) => (req, res, next) => {
const cookies = cookie.parse(req.headers.cookie || '')
Expand All @@ -56,29 +69,35 @@ const M = {
next(newError(403))
}
},

// load config
config: (appConfig) => (req, res, next) => {
req._config = Object.assign({}, config, appConfig.config)
next()
},
renderConfig,
updateConfig,

// render cheatsheet
cheatsheet: (req, res, next) => {
cheatsheet: (req, _res, next) => {
const file = path.resolve(__dirname, '..', 'test', 'cheatsheet.md')
req.url = filename2uri(file)
next()
},

// redirect to homeDir
home: (req, res) => {
res.redirect(homedir())
},

// unescape an escaped URL
unescape: function (req, res, next) {
unescape: function (req, _res, next) {
req.url = decodeURIComponent(req.url)
next()
},

// check if file exists and if is directory
stat: function (req, res, next) {
stat: function (req, _res, next) {
const filename = uri2filename(req.url)
fs.stat(filename, (err, stats) => {
if (err) {
Expand All @@ -89,6 +108,7 @@ const M = {
}
})
},

// try to autocorrect markdown links (e.g. for gitlab)
noextfile: function (req, res, next) {
let path
Expand Down Expand Up @@ -117,6 +137,7 @@ const M = {
next()
}
},

// serve files without extension as plaintext files
plaintext: function (req, res, next) {
if (!req.isDirectory && FILETEXT.test(req.url)) {
Expand All @@ -135,6 +156,7 @@ const M = {
next()
}
},

// make markdown conversion
markdown: function (req, res, next) {
// transform markdown pages
Expand All @@ -145,6 +167,7 @@ const M = {
next()
}
},

// show index for directories
serveIndex: function (req, res, next) {
if (!req.isDirectory) {
Expand Down Expand Up @@ -174,6 +197,7 @@ const M = {
next(err)
})
},

// serve static files
serveStatic: function (path, options) {
return function (req, res, next) {
Expand All @@ -190,12 +214,13 @@ const M = {
})
}
},

// final 404 handler
four0four: function (req, res, next) {
next(newError(404))
},
// error handler

// error handler
error: function (err, req, res, _next) {
if (!err.status) {
// Error objects
Expand Down
8 changes: 6 additions & 2 deletions lib/renderConfig.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const _template = require('lodash/template')
const fs = require('fs')
const path = require('path')
const { markdown } = require('./markd')
const { markdown } = require('./markd.js')
const { contentSecurityPolicy } = require('./utils.js')

function renderConfig({ highlightStyles }) {
const styles = fs
Expand All @@ -22,7 +23,10 @@ function renderConfig({ highlightStyles }) {

markdown(req.url, data, req._config)
.then((data) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
'Content-Security-Policy': contentSecurityPolicy
})
res.write(data)
res.end()
})
Expand Down
15 changes: 14 additions & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ const urlWithoutDrive = (uri) =>

const homedir = () => (isWin32 ? filename2uri(os.homedir()) : os.homedir())

const contentSecurityPolicy = [
"default-src 'self' 'unsafe-inline'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
'img-src data: *',
'font-src data: *',
"connect-src 'self'",
'media-src data: *',
'object-src data: *',
'prefetch-src data: *'
].join('; ')

/**
* sets error
* @param {Error|Number} err
Expand Down Expand Up @@ -74,5 +86,6 @@ module.exports = {
urlWithoutDrive,
newError,
merge,
requireResolve
requireResolve,
contentSecurityPolicy
}
4 changes: 2 additions & 2 deletions lib/watch.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const fs = require('fs')
const mw = require('./middlewares')
const { uri2filename, filename2uri } = require('./utils')
const WebSocket = require('ws')
const mw = require('./middlewares.js')
const { uri2filename, filename2uri } = require('./utils.js')

const MARKDOWNEXT = mw.MARKDOWNEXT
const WATCHOPTS = { persistent: true, recursive: false }
Expand Down
54 changes: 29 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "md-fileserver",
"version": "1.10.2",
"version": "1.10.3-0",
"description": "Locally view markdown files in a browser",
"keywords": [
"fileserver",
Expand Down Expand Up @@ -45,15 +45,16 @@
"readme": "markedpp --githubid -i README.md -o README.md",
"test": "mocha",
"test:md": "bin/mdstart.js test/cheatsheet.md",
"test:cnfl": "bin/mdstart.js test/cnfl.md"
"test:cnfl": "bin/mdstart.js test/cnfl.md",
"test:exf": "bin/mdstart.js test/exfiltrate.md"
},
"mocha": {
"exit": true
},
"dependencies": {
"@commenthol/markdown-it-katex": "^2.0.8",
"asyncc": "^2.0.7",
"body-parser": "^1.20.3",
"asyncc": "^2.0.9",
"body-parser": "^2.2.0",
"confluencer": "^1.5.2",
"cookie": "^1.0.2",
"express": "^4.21.2",
Expand All @@ -63,7 +64,7 @@
"markdown-it-abbr": "^2.0.0",
"markdown-it-admon": "^1.0.1",
"markdown-it-anchor": "^9.2.0",
"markdown-it-attrs": "~4.2.0",
"markdown-it-attrs": "4.2.0",
"markdown-it-deflist": "^3.0.0",
"markdown-it-emoji": "^3.0.0",
"markdown-it-footnote": "^4.0.0",
Expand All @@ -73,37 +74,40 @@
"markdown-it-sub": "^2.0.0",
"markdown-it-sup": "^2.0.0",
"markdown-it-task-lists": "^2.1.1",
"markedpp": "^1.4.0",
"markedpp": "^2.0.3",
"serve-index": "^1.9.1",
"serve-static": "^1.16.2",
"ws": "^8.18.0"
"serve-static": "^2.2.0",
"ws": "^8.18.3"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/core": "^7.28.5",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-transform-react-jsx": "^7.25.9",
"@babel/preset-env": "^7.26.0",
"@babel/register": "^7.25.9",
"@babel/plugin-transform-react-jsx": "^7.27.1",
"@babel/preset-env": "^7.28.5",
"@babel/register": "^7.28.3",
"babel-eslint": "^10.1.0",
"babel-loader": "^9.2.1",
"babel-loader": "^10.0.0",
"css-loader": "^7.1.2",
"eslint": "^9.17.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"globals": "^15.14.0",
"katex": "^0.16.19",
"mini-css-extract-plugin": "^2.9.2",
"mocha": "^11.0.1",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"globals": "^16.5.0",
"katex": "^0.16.25",
"mini-css-extract-plugin": "^2.9.4",
"mocha": "^11.7.5",
"normalize.css": "^8.0.1",
"npm-run-all2": "^7.0.2",
"rimraf": "^6.0.1",
"npm-run-all2": "^8.0.4",
"rimraf": "^6.1.0",
"style-loader": "^4.0.0",
"supertest": "^7.0.0",
"webpack": "^5.97.1",
"supertest": "^7.1.4",
"webpack": "^5.102.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.0"
"webpack-dev-server": "^5.2.2"
},
"engine": {
"node": ">=10.0.0"
},
"c4uIgnore": {
"markdown-it-attrs": "4.2.0"
}
}
6 changes: 3 additions & 3 deletions test/cheatsheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,12 +392,12 @@ img[alt=img100x] {width: 100px; border: 5px solid red; border-radius: 25%}

![Alt Text](path_to/img.png)
![Alt Text](path_to/img.png "Optional Title")
![Alt Text](http://placehold.it/150x100)
![Alt Text](https://picsum.photos/200/200)
![broken image](http://localhost/my-broken-image)

![Alt Text](path_to/img.png)
![Alt Text](path_to/img.png "Optional Title")
![Alt Text](http://placehold.it/150x100)
![Alt Text](path_to/img.png 'Optional Title')
![Alt Text](https://picsum.photos/200/200)
![broken image](http://localhost/my-broken-image)

Using plain html can be used to size images:
Expand Down
Loading