SafePath.coffee 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. # This file is shared between the frontend and server code of web, so that
  2. # filename validation is the same in both implementations.
  3. # Both copies must be kept in sync:
  4. # app/coffee/Features/Project/SafePath.coffee
  5. # public/coffee/ide/directives/SafePath.coffee
  6. load = () ->
  7. BADCHAR_RX = ///
  8. [
  9. \/ # no forward slashes
  10. \\ # no back slashes
  11. \* # no asterisk
  12. \u0000-\u001F # no control characters (0-31)
  13. \u007F # no delete
  14. \u0080-\u009F # no unicode control characters (C1)
  15. \uD800-\uDFFF # no unicode surrogate characters
  16. ]
  17. ///g
  18. BADFILE_RX = ///
  19. (^\.$) # reject . as a filename
  20. | (^\.\.$) # reject .. as a filename
  21. | (^\s+) # reject leading space
  22. | (\s+$) # reject trailing space
  23. ///g
  24. # Put a block on filenames which match javascript property names, as they
  25. # can cause exceptions where the code puts filenames into a hash. This is a
  26. # temporary workaround until the code in other places is made safe against
  27. # property names.
  28. #
  29. # The list of property names is taken from
  30. # ['prototype'].concat(Object.getOwnPropertyNames(Object.prototype))
  31. BLOCKEDFILE_RX = ///
  32. ^(
  33. prototype
  34. |constructor
  35. |toString
  36. |toLocaleString
  37. |valueOf
  38. |hasOwnProperty
  39. |isPrototypeOf
  40. |propertyIsEnumerable
  41. |__defineGetter__
  42. |__lookupGetter__
  43. |__defineSetter__
  44. |__lookupSetter__
  45. |__proto__
  46. )$
  47. ///
  48. MAX_PATH = 1024 # Maximum path length, in characters. This is fairly arbitrary.
  49. SafePath =
  50. # convert any invalid characters to underscores in the given filename
  51. clean: (filename) ->
  52. filename = filename.replace BADCHAR_RX, '_'
  53. # for BADFILE_RX replace any matches with an equal number of underscores
  54. filename = filename.replace BADFILE_RX, (match) ->
  55. return new Array(match.length + 1).join("_")
  56. # replace blocked filenames 'prototype' with '@prototype'
  57. filename = filename.replace BLOCKEDFILE_RX, "@$1"
  58. return filename
  59. # returns whether the filename is 'clean' (does not contain any invalid
  60. # characters or reserved words)
  61. isCleanFilename: (filename) ->
  62. return SafePath.isAllowedLength(filename) &&
  63. !BADCHAR_RX.test(filename) &&
  64. !BADFILE_RX.test(filename)
  65. isBlockedFilename: (filename) ->
  66. return BLOCKEDFILE_RX.test(filename)
  67. # returns whether a full path is 'clean' - e.g. is a full or relative path
  68. # that points to a file, and each element passes the rules in 'isCleanFilename'
  69. isCleanPath: (path) ->
  70. elements = path.split('/')
  71. lastElementIsEmpty = elements[elements.length - 1].length == 0
  72. return false if lastElementIsEmpty
  73. for element in elements
  74. return false if element.length > 0 && !SafePath.isCleanFilename element
  75. # check for a top-level reserved name
  76. return false if BLOCKEDFILE_RX.test(path.replace(/^\/?/,'')) # remove leading slash if present
  77. return true
  78. isAllowedLength: (pathname) ->
  79. return pathname.length > 0 && pathname.length <= MAX_PATH
  80. if define?
  81. define [], load
  82. else
  83. module.exports = load()