From 323511b2ab11951b170e229d1786810b79028037 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 22 Jan 2024 15:35:02 +0100 Subject: [PATCH] Properly sanitize Windows reserved names in evaluator paths (#2964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 https://github.com/com-lihaoyi/mill/issues/2961 Pull request: https://github.com/com-lihaoyi/mill/pull/2964 --- main/eval/src/mill/eval/EvaluatorPaths.scala | 12 ++++++++- .../src/mill/eval/EvaluatorPathsTests.scala | 26 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 main/eval/test/src/mill/eval/EvaluatorPathsTests.scala diff --git a/main/eval/src/mill/eval/EvaluatorPaths.scala b/main/eval/src/mill/eval/EvaluatorPaths.scala index 00ed315ed5b..d92c968c0b6 100644 --- a/main/eval/src/mill/eval/EvaluatorPaths.scala +++ b/main/eval/src/mill/eval/EvaluatorPaths.scala @@ -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", @@ -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 + } + } } diff --git a/main/eval/test/src/mill/eval/EvaluatorPathsTests.scala b/main/eval/test/src/mill/eval/EvaluatorPathsTests.scala new file mode 100644 index 00000000000..7f905b93973 --- /dev/null +++ b/main/eval/test/src/mill/eval/EvaluatorPathsTests.scala @@ -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) + } + } + } + } +}