| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- // @ts-check
- 'use strict'
- const path = require('path-browserify')
- /**
- * Regular expressions for Overleaf v2 taken from
- * https://github.com/overleaf/internal/blob/f7b287b6a07354000a6b463ca3a5828104e4a811/services/web/app/src/Features/Project/SafePath.js
- */
- //
- // Regex of characters that are invalid in filenames
- //
- // eslint-disable-next-line no-control-regex
- const BAD_CHAR_RX = /[/*\u0000-\u001F\u007F\u0080-\u009F\uD800-\uDFFF]/g
- //
- // Regex of filename patterns that are invalid ("." ".." and leading/trailing
- // whitespace)
- //
- const BAD_FILE_RX = /(^\.$)|(^\.\.$)|(^\s+)|(\s+$)/g
- //
- // Put a block on filenames which match javascript property names, as they
- // can cause exceptions where the code puts filenames into a hash. This is a
- // temporary workaround until the code in other places is made safe against
- // property names.
- //
- // See https://github.com/overleaf/write_latex/wiki/Using-javascript-Objects-as-Maps
- //
- const BLOCKED_FILE_RX =
- /^(prototype|constructor|toString|toLocaleString|valueOf|hasOwnProperty|isPrototypeOf|propertyIsEnumerable|__defineGetter__|__lookupGetter__|__defineSetter__|__lookupSetter__|__proto__)$/
- //
- // Maximum path length, in characters. This is fairly arbitrary.
- //
- const MAX_PATH = 1024
- /**
- * Replace invalid characters and filename patterns in a filename with
- * underscores.
- * @param {string} filename
- */
- function cleanPart(filename) {
- filename = filename.replace(BAD_CHAR_RX, '_')
- filename = filename.replace(BAD_FILE_RX, function (match) {
- return new Array(match.length + 1).join('_')
- })
- return filename
- }
- /**
- * All pathnames in a Snapshot must be clean. We want pathnames that:
- *
- * 1. are unambiguous (e.g. no `.`s or redundant path separators)
- * 2. do not allow directory traversal attacks (e.g. no `..`s or absolute paths)
- * 3. do not contain leading/trailing space
- * 4. do not contain the character '*' in filenames
- *
- * We normalise the pathname, split it by the separator and then clean each part
- * as a filename
- *
- * @param {string} pathname
- * @return {String}
- */
- exports.clean = function (pathname) {
- return exports.cleanDebug(pathname)[0]
- }
- /**
- * See clean
- * @param {string} pathname
- * @return {[string,string]}
- */
- exports.cleanDebug = function (pathname) {
- let prev = pathname
- let reason = ''
- /**
- * @param {string} label
- */
- function recordReasonIfChanged(label) {
- if (pathname === prev) return
- if (reason) reason += ','
- reason += label
- prev = pathname
- }
- pathname = path.normalize(pathname)
- recordReasonIfChanged('normalize')
- pathname = pathname.replace(/\\/g, '/')
- recordReasonIfChanged('workaround for IE')
- pathname = pathname.replace(/\/+/g, '/')
- recordReasonIfChanged('no multiple slashes')
- pathname = pathname.replace(/^(\/.*)$/, '_$1')
- recordReasonIfChanged('no leading /')
- pathname = pathname.replace(/^(.+)\/$/, '$1')
- recordReasonIfChanged('no trailing /')
- pathname = pathname.replace(/^ *(.*)$/, '$1')
- recordReasonIfChanged('no leading spaces')
- pathname = pathname.replace(/^(.*[^ ]) *$/, '$1')
- recordReasonIfChanged('no trailing spaces')
- if (pathname.length === 0) pathname = '_'
- recordReasonIfChanged('empty')
- pathname = pathname.split('/').map(cleanPart).join('/')
- recordReasonIfChanged('cleanPart')
- pathname = pathname.replace(BLOCKED_FILE_RX, '@$1')
- recordReasonIfChanged('BLOCKED_FILE_RX')
- return [pathname, reason]
- }
- /**
- * A pathname is clean (see clean) and not too long.
- *
- * @param {string} pathname
- * @return {Boolean}
- */
- exports.isClean = function pathnameIsClean(pathname) {
- return exports.isCleanDebug(pathname)[0]
- }
- /**
- * A pathname is clean (see clean) and not too long.
- *
- * @param {string} pathname
- * @return {[boolean,string]}
- */
- exports.isCleanDebug = function (pathname) {
- if (pathname.length > MAX_PATH) return [false, 'MAX_PATH']
- if (pathname.length === 0) return [false, 'empty']
- const [cleanPathname, reason] = exports.cleanDebug(pathname)
- if (cleanPathname !== pathname) return [false, reason]
- return [true, '']
- }
|