Merge pull request #1608 from sharelatex/bg-allow-reserved-names-safely

allow reserved names safely

GitOrigin-RevId: d0c5f287411f5cd6d0639f57ef925409571acf8a
This commit is contained in:
Brian Gough 2019-03-13 11:04:32 +00:00 committed by sharelatex
parent 0f22a626c8
commit 32925f0c4e
5 changed files with 56 additions and 16 deletions

View file

@ -122,6 +122,9 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
return callback(err) if err? return callback(err) if err?
ProjectLocator.findElement {project, element_id: entity_id, type: entityType}, (err, entity, entityPath)-> ProjectLocator.findElement {project, element_id: entity_id, type: entityType}, (err, entity, entityPath)->
return callback(err) if err? return callback(err) if err?
# Prevent top-level docs/files with reserved names (to match v1 behaviour)
if self._blockedFilename entityPath, entityType
return callback new Errors.InvalidNameError("blocked element name")
self._checkValidMove project, entityType, entity, entityPath, destFolderId, (error) -> self._checkValidMove project, entityType, entity, entityPath, destFolderId, (error) ->
return callback(error) if error? return callback(error) if error?
ProjectEntityHandler.getAllEntitiesFromProject project, (error, oldDocs, oldFiles) -> ProjectEntityHandler.getAllEntitiesFromProject project, (error, oldDocs, oldFiles) ->
@ -153,10 +156,13 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
return callback(error) if error? return callback(error) if error?
ProjectLocator.findElement {project:project, element_id:entity_id, type:entityType}, (error, entity, entPath, parentFolder)=> ProjectLocator.findElement {project:project, element_id:entity_id, type:entityType}, (error, entity, entPath, parentFolder)=>
return callback(error) if error? return callback(error) if error?
endPath = path.join(path.dirname(entPath.fileSystem), newName)
# Prevent top-level docs/files with reserved names (to match v1 behaviour)
if self._blockedFilename {fileSystem: endPath}, entityType
return callback new Errors.InvalidNameError("blocked element name")
# check if the new name already exists in the current folder # check if the new name already exists in the current folder
self._checkValidElementName parentFolder, newName, (error) => self._checkValidElementName parentFolder, newName, (error) =>
return callback(error) if error? return callback(error) if error?
endPath = path.join(path.dirname(entPath.fileSystem), newName)
conditions = {_id:project_id} conditions = {_id:project_id}
update = "$set":{}, "$inc":{} update = "$set":{}, "$inc":{}
namePath = entPath.mongo+".name" namePath = entPath.mongo+".name"
@ -257,6 +263,9 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
# check if the path would be too long # check if the path would be too long
if not SafePath.isAllowedLength newPath.fileSystem if not SafePath.isAllowedLength newPath.fileSystem
return callback new Errors.InvalidNameError("path too long") return callback new Errors.InvalidNameError("path too long")
# Prevent top-level docs/files with reserved names (to match v1 behaviour)
if self._blockedFilename newPath, type
return callback new Errors.InvalidNameError("blocked element name")
self._checkValidElementName folder, element.name, (err) => self._checkValidElementName folder, element.name, (err) =>
return callback(err) if err? return callback(err) if err?
id = element._id+'' id = element._id+''
@ -276,6 +285,18 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
return callback(err) return callback(err)
callback(err, {path:newPath}, newProject) callback(err, {path:newPath}, newProject)
_blockedFilename: (entityPath, entityType) ->
# check if name would be blocked in v1
# javascript reserved names are forbidden for docs and files
# at the top-level (but folders with reserved names are allowed).
isFolder = (entityType in ['folder', 'folders'])
[dir, file] = [path.dirname(entityPath.fileSystem), path.basename(entityPath.fileSystem)]
isTopLevel = dir is '/'
if isTopLevel and !isFolder && SafePath.isBlockedFilename file
return true
else
return false
_checkValidElementName: (folder, name, callback = (err) ->) -> _checkValidElementName: (folder, name, callback = (err) ->) ->
# check if the name is already taken by a doc, file or # check if the name is already taken by a doc, file or
# folder. If so, return an error "file already exists". # folder. If so, return an error "file already exists".

View file

@ -19,7 +19,7 @@ load = () ->
BADFILE_RX = /// BADFILE_RX = ///
(^\.$) # reject . as a filename (^\.$) # reject . as a filename
| (^\.\.$) # reject .. as a filename | (^\.\.$) # reject .. as a filename
| (^\s+) # reject leading space | (^\s+) # reject leading space
| (\s+$) # reject trailing space | (\s+$) # reject trailing space
///g ///g
@ -67,8 +67,10 @@ load = () ->
isCleanFilename: (filename) -> isCleanFilename: (filename) ->
return SafePath.isAllowedLength(filename) && return SafePath.isAllowedLength(filename) &&
!BADCHAR_RX.test(filename) && !BADCHAR_RX.test(filename) &&
!BADFILE_RX.test(filename) && !BADFILE_RX.test(filename)
!BLOCKEDFILE_RX.test(filename)
isBlockedFilename: (filename) ->
return BLOCKEDFILE_RX.test(filename)
# returns whether a full path is 'clean' - e.g. is a full or relative path # returns whether a full path is 'clean' - e.g. is a full or relative path
# that points to a file, and each element passes the rules in 'isCleanFilename' # that points to a file, and each element passes the rules in 'isCleanFilename'
@ -81,6 +83,9 @@ load = () ->
for element in elements for element in elements
return false if element.length > 0 && !SafePath.isCleanFilename element return false if element.length > 0 && !SafePath.isCleanFilename element
# check for a top-level reserved name
return false if BLOCKEDFILE_RX.test(path.replace(/^\/?/,'')) # remove leading slash if present
return true return true
isAllowedLength: (pathname) -> isAllowedLength: (pathname) ->

View file

@ -87,8 +87,7 @@ prototype\
return ( return (
SafePath.isAllowedLength(filename) && SafePath.isAllowedLength(filename) &&
!BADCHAR_RX.test(filename) && !BADCHAR_RX.test(filename) &&
!BADFILE_RX.test(filename) && !BADFILE_RX.test(filename)
!BLOCKEDFILE_RX.test(filename)
) )
}, },

View file

@ -168,16 +168,18 @@ define([
} }
getMultiSelectedEntityChildNodes() { getMultiSelectedEntityChildNodes() {
// use pathnames with a leading slash to avoid
// problems with reserved Object properties
const entities = this.getMultiSelectedEntities() const entities = this.getMultiSelectedEntities()
const paths = {} const paths = {}
for (var entity of Array.from(entities)) { for (var entity of Array.from(entities)) {
paths[this.getEntityPath(entity)] = entity paths["/" + this.getEntityPath(entity)] = entity
} }
const prefixes = {} const prefixes = {}
for (var path in paths) { for (var path in paths) {
entity = paths[path] entity = paths[path]
const parts = path.split('/') const parts = path.split('/')
if (parts.length <= 1) { if (parts.length <= 2) {
continue continue
} else { } else {
// Record prefixes a/b/c.tex -> 'a' and 'a/b' // Record prefixes a/b/c.tex -> 'a' and 'a/b'
@ -186,7 +188,7 @@ define([
asc ? i <= end : i >= end; asc ? i <= end : i >= end;
asc ? i++ : i-- asc ? i++ : i--
) { ) {
prefixes[parts.slice(0, i).join('/')] = true prefixes["/" + parts.slice(0, i).join('/')] = true
} }
} }
} }

View file

@ -63,17 +63,18 @@ describe 'SafePath', ->
result = @SafePath.isCleanFilename 'foo\uD800\uDFFFbar' result = @SafePath.isCleanFilename 'foo\uD800\uDFFFbar'
result.should.equal false result.should.equal false
it 'should not accept javascript property names', -> it 'should accept javascript property names', ->
result = @SafePath.isCleanFilename 'prototype' result = @SafePath.isCleanFilename 'prototype'
result.should.equal false result.should.equal true
it 'should not accept javascript property names in the prototype', -> it 'should accept javascript property names in the prototype', ->
result = @SafePath.isCleanFilename 'hasOwnProperty' result = @SafePath.isCleanFilename 'hasOwnProperty'
result.should.equal false result.should.equal true
it 'should not accept javascript property names resulting from substitutions', -> # this test never worked correctly because the spaces are not replaced by underscores in isCleanFilename
result = @SafePath.isCleanFilename ' proto ' # it 'should not accept javascript property names resulting from substitutions', ->
result.should.equal false # result = @SafePath.isCleanFilename ' proto '
# result.should.equal false
# it 'should not accept a trailing .', -> # it 'should not accept a trailing .', ->
# result = @SafePath.isCleanFilename 'hello.' # result = @SafePath.isCleanFilename 'hello.'
@ -136,6 +137,18 @@ describe 'SafePath', ->
result = @SafePath.isCleanPath 'foo//*/bar' result = @SafePath.isCleanPath 'foo//*/bar'
result.should.equal false result.should.equal false
it 'should not accept javascript property names', ->
result = @SafePath.isCleanPath 'prototype'
result.should.equal false
it 'should not accept javascript property names in the prototype', ->
result = @SafePath.isCleanPath 'hasOwnProperty'
result.should.equal false
it 'should not accept javascript property names resulting from substitutions', ->
result = @SafePath.isCleanPath ' proto '
result.should.equal false
describe 'isAllowedLength', -> describe 'isAllowedLength', ->
it 'should accept a valid path "main.tex"', -> it 'should accept a valid path "main.tex"', ->
result = @SafePath.isAllowedLength 'main.tex' result = @SafePath.isAllowedLength 'main.tex'