Skip to content

Commit

Permalink
Properly sanitize Windows reserved names in evaluator paths (#2964)
Browse files Browse the repository at this point in the history
In case a segment path matches one of the reserved name of the Windows
filesystem namespace, we insert a `~` directly after the offending
segment part.

Here is a excerpt from an official [Win32
documentation](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#naming-conventions):

> * Do not use the following reserved names for the name of a file:
>
> CON, PRN, AUX, NUL, COM0, COM1, COM2, COM3, COM4, COM5, COM6, COM7,
COM8, COM9, COM¹, COM², COM³, LPT0, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6,
LPT7, LPT8, LPT9, LPT¹, LPT², and LPT³. Also avoid these names followed
immediately by an extension; for example, NUL.txt and NUL.tar.gz are
both equivalent to NUL. For more information, see
[Namespaces](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#win32-file-namespaces).

We do the insertions on all platforms, not only Windows, for consistency
of the `out/` folder.

Fix #2961

Pull request: #2964
  • Loading branch information
lefou authored Jan 22, 2024
1 parent a866173 commit 323511b
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 1 deletion.
12 changes: 11 additions & 1 deletion main/eval/src/mill/eval/EvaluatorPaths.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object EvaluatorPaths {
): EvaluatorPaths = {
val refinedSegments = foreignSegments.map(_ ++ segments).getOrElse(segments)
val segmentStrings = makeSegmentStrings(refinedSegments)
val targetPath = workspacePath / segmentStrings
val targetPath = workspacePath / segmentStrings.map(sanitizePathSegment)
EvaluatorPaths(
targetPath / os.up / s"${targetPath.last}.dest",
targetPath / os.up / s"${targetPath.last}.json",
Expand All @@ -43,4 +43,14 @@ object EvaluatorPaths {
workspacePath: os.Path,
task: NamedTask[_]
): EvaluatorPaths = resolveDestPaths(workspacePath, task.ctx.segments, task.ctx.foreign)

// case-insensitive match on reserved names
private val ReservedWinNames =
raw"^([cC][oO][nN]|[pP][rR][nN]|[aA][uU][xX]|[nN][uU][lL]|[cC][oO][mM][0-9¹²³]|[lL][pP][tT][0-9¹²³])($$|[.].*$$)".r
def sanitizePathSegment(segment: String): os.PathChunk = {
segment match {
case ReservedWinNames(keyword, rest) => s"${keyword}~${rest}"
case s => s
}
}
}
26 changes: 26 additions & 0 deletions main/eval/test/src/mill/eval/EvaluatorPathsTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package mill.eval

import utest._

object EvaluatorPathsTests extends TestSuite {

override def tests: Tests = Tests {
"sanitizedPathSegment" - {
"WindowsReservedNames" - {
val replace = Seq(
"com1.json" -> "com1~.json",
"LPT¹" -> "LPT¹~"
)
val noReplace = Seq(
"con10.json"
)
for {
(segment, result) <- replace ++ noReplace.map(s => (s, s))
} yield {
EvaluatorPaths.sanitizePathSegment(segment).toString ==> result
(segment, result)
}
}
}
}
}

0 comments on commit 323511b

Please sign in to comment.