Skip to content

Commit 0af7a24

Browse files
committed
Backport bugfix for splitFileName on windows
Wrt #219
1 parent cdb5171 commit 0af7a24

File tree

4 files changed

+45
-15
lines changed

4 files changed

+45
-15
lines changed

System/FilePath/Internal.hs

+34-10
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@ isDrive x = not (null x) && null (dropDrive x)
602602
-- > Posix: splitFileName "/" == ("/","")
603603
-- > Windows: splitFileName "c:" == ("c:","")
604604
-- > Windows: splitFileName "\\\\?\\A:\\fred" == ("\\\\?\\A:\\","fred")
605+
-- > Windows: splitFileName "\\\\?\\A:" == ("\\\\?\\A:","")
605606
splitFileName :: FILEPATH -> (STRING, STRING)
606607
splitFileName x = if null path
607608
then (dotSlash, file)
@@ -644,20 +645,43 @@ splitFileName_ fp
644645
-- or UNC location "\\?\UNC\foo", where path separator is a part of the drive name.
645646
-- We can test this by trying dropDrive and falling back to splitDrive.
646647
| isWindows
647-
, Just (s1, _s2, bs') <- uncons2 dirSlash
648-
, isPathSeparator s1
649-
-- If bs' is empty, then s2 as the last character of dirSlash must be a path separator,
650-
-- so we are in the middle of shared drive.
651-
-- Otherwise, since s1 is a path separator, we might be in the middle of UNC path.
652-
, null bs' || maybe False isIncompleteUNC (readDriveUNC dirSlash)
653-
= (fp, mempty)
648+
= case uncons2 dirSlash of
649+
Just (s1, s2, bs')
650+
| isPathSeparator s1
651+
-- If bs' is empty, then s2 as the last character of dirSlash must be a path separator,
652+
-- so we are in the middle of shared drive.
653+
-- Otherwise, since s1 is a path separator, we might be in the middle of UNC path.
654+
, null bs' || maybe False isIncompleteUNC (readDriveUNC dirSlash)
655+
-> (fp, mempty)
656+
-- This handles inputs like "//?/A:" and "//?/A:foo"
657+
| isPathSeparator s1
658+
, isPathSeparator s2
659+
, Just (s3, s4, bs'') <- uncons2 bs'
660+
, s3 == _question
661+
, isPathSeparator s4
662+
, null bs''
663+
, Just (drive, rest) <- readDriveLetter file
664+
-> (dirSlash <> drive, rest)
665+
_ -> (dirSlash, file)
654666
| otherwise
655-
= (dirSlash, file)
667+
= (dirSlash, file)
656668
where
657669
(dirSlash, file) = breakEnd isPathSeparator fp
658-
670+
dropExcessTrailingPathSeparators x
671+
| hasTrailingPathSeparator x
672+
, let x' = dropWhileEnd isPathSeparator x
673+
, otherwise = if | null x' -> singleton (last x)
674+
| otherwise -> addTrailingPathSeparator x'
675+
| otherwise = x
676+
677+
-- an "incomplete" UNC is one without a path (but potentially a drive)
659678
isIncompleteUNC (pref, suff) = null suff && not (hasPenultimateColon pref)
660-
hasPenultimateColon = maybe False (maybe False ((== _colon) . snd) . unsnoc . fst) . unsnoc
679+
680+
-- e.g. @//?/a:/@ or @//?/a://@, but not @//?/a:@
681+
hasPenultimateColon pref
682+
| hasTrailingPathSeparator pref
683+
= maybe False (maybe False ((== _colon) . snd) . unsnoc . fst) . unsnoc . dropExcessTrailingPathSeparators $ pref
684+
| otherwise = False
661685

662686
-- | Set the filename.
663687
--

changelog.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
_Note: below all `FilePath` values are unquoted, so `\\` really means two backslashes._
44

5+
## 1.4.300.0. *Jan 2024*
6+
7+
* Backport bugfix for [`splitFileName`](https://github.com/haskell/filepath/issues/219) on windows
8+
59
## 1.4.200.1. *Dec 2023*
610

711
* Improve deprecation warnings wrt [#209](https://github.com/haskell/filepath/issues/209)

filepath.cabal

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cabal-version: 2.2
22
name: filepath
3-
version: 1.4.200.1
3+
version: 1.4.300.1
44

55
-- NOTE: Don't forget to update ./changelog.md
66
license: BSD-3-Clause

tests/filepath-tests/TestGen.hs

+6-4
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ import Data.String
1414
import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )
1515
import GHC.IO.Encoding.UTF16 ( mkUTF16le )
1616
import GHC.IO.Encoding.UTF8 ( mkUTF8 )
17-
import System.OsString.Internal.Types
18-
import System.OsPath.Encoding.Internal
17+
import System.OsString.Internal.Types.Hidden
18+
import System.OsPath.Encoding.Internal.Hidden
1919
import qualified Data.Char as C
20-
import qualified System.OsPath.Data.ByteString.Short as SBS
21-
import qualified System.OsPath.Data.ByteString.Short.Word16 as SBS16
20+
import qualified System.OsPath.Data.ByteString.Short.Hidden as SBS
21+
import qualified System.OsPath.Data.ByteString.Short.Word16.Hidden as SBS16
2222
import qualified System.FilePath.Windows as W
2323
import qualified System.FilePath.Posix as P
2424
import qualified System.OsPath.Windows as AFP_W
@@ -458,6 +458,8 @@ tests =
458458
,("AFP_W.splitFileName (\"c:\") == ((\"c:\"), (\"\"))", property $ AFP_W.splitFileName ("c:") == (("c:"), ("")))
459459
,("W.splitFileName \"\\\\\\\\?\\\\A:\\\\fred\" == (\"\\\\\\\\?\\\\A:\\\\\", \"fred\")", property $ W.splitFileName "\\\\?\\A:\\fred" == ("\\\\?\\A:\\", "fred"))
460460
,("AFP_W.splitFileName (\"\\\\\\\\?\\\\A:\\\\fred\") == ((\"\\\\\\\\?\\\\A:\\\\\"), (\"fred\"))", property $ AFP_W.splitFileName ("\\\\?\\A:\\fred") == (("\\\\?\\A:\\"), ("fred")))
461+
,("W.splitFileName \"\\\\\\\\?\\\\A:\" == (\"\\\\\\\\?\\\\A:\", \"\")", property $ W.splitFileName "\\\\?\\A:" == ("\\\\?\\A:", ""))
462+
,("AFP_W.splitFileName (\"\\\\\\\\?\\\\A:\") == ((\"\\\\\\\\?\\\\A:\"), (\"\"))", property $ AFP_W.splitFileName ("\\\\?\\A:") == (("\\\\?\\A:"), ("")))
461463
,("P.replaceFileName \"/directory/other.txt\" \"file.ext\" == \"/directory/file.ext\"", property $ P.replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext")
462464
,("W.replaceFileName \"/directory/other.txt\" \"file.ext\" == \"/directory/file.ext\"", property $ W.replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext")
463465
,("AFP_P.replaceFileName (\"/directory/other.txt\") (\"file.ext\") == (\"/directory/file.ext\")", property $ AFP_P.replaceFileName ("/directory/other.txt") ("file.ext") == ("/directory/file.ext"))

0 commit comments

Comments
 (0)