256
267
winToText :: FilePath -> T.Text
257
winToText p = T.concat (root : chunks) where
268
winToText p = case pathRoot p of
269
Just RootWindowsUnc{} -> uncToText p
272
dosToText :: FilePath -> T.Text
273
dosToText p = T.concat (root : chunks) where
258
274
root = rootText (pathRoot p)
259
275
chunks = intersperse (T.pack "\\") (map unescape' (directoryChunks p))
277
uncToText :: FilePath -> T.Text
278
uncToText p = T.concat (root : chunks) where
279
root = if all T.null chunks
280
then rootText (pathRoot p)
281
else rootText (pathRoot p) `T.append` T.pack "\\"
282
chunks = intersperse (T.pack "\\") (filter (not . T.null) (map unescape' (directoryChunks p)))
261
284
winFromText :: T.Text -> FilePath
262
285
winFromText text = if T.null text then empty else path where
263
286
path = FilePath root directories basename exts
288
-- Windows has various types of absolute paths:
290
-- * C:\foo\bar -> DOS-style absolute path
291
-- * \\?\C:\foo\bar -> extended-length absolute path
292
-- * \\host\share\foo\bar -> UNC path
293
-- * \\?\UNC\host\share\foo\bar -> extended-length UNC path
295
-- \foo\bar looks like an absolute path, but is actually a path
296
-- relative to the current DOS drive.
298
-- http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
299
(root, pastRoot) = if T.isPrefixOf (T.pack "\\\\") text
300
then case stripUncasedPrefix (T.pack "\\\\?\\UNC\\") text of
301
Just stripped -> parseUncRoot stripped True
302
Nothing -> case T.stripPrefix (T.pack "\\\\?\\") text of
303
Just stripped -> parseDosRoot stripped True
304
Nothing -> case T.stripPrefix (T.pack "\\\\") text of
305
Just stripped -> parseUncRoot stripped False
306
Nothing -> parseDosRoot text False
307
else case T.stripPrefix (T.pack "\\??\\") text of
308
Just stripped -> parseDoubleQmark stripped
309
Nothing -> parseDosRoot text False
311
(directories, filename)
312
| P.null pastRoot = ([], Nothing)
313
| otherwise = case last pastRoot of
314
fn | fn == T.pack "." -> (goodDirs pastRoot, Just "")
315
fn | fn == T.pack ".." -> (goodDirs pastRoot, Just "")
316
fn -> (goodDirs (init pastRoot), Just (escape fn))
318
goodDirs :: [T.Text] -> [Chunk]
319
goodDirs = map escape . filter (not . T.null)
321
(basename, exts) = case filename of
322
Just fn -> parseFilename fn
323
Nothing -> (Nothing, [])
325
stripUncasedPrefix :: T.Text -> T.Text -> Maybe T.Text
326
stripUncasedPrefix prefix text = if T.toCaseFold prefix == T.toCaseFold (T.take (T.length prefix) text)
327
then Just (T.drop (T.length prefix) text)
330
parseDosRoot :: T.Text -> Bool -> (Maybe Root, [T.Text])
331
parseDosRoot text extended = parsed where
265
332
split = textSplitBy (\c -> c == '/' || c == '\\') text
267
(root, pastRoot) = let
271
then (Just RootWindowsCurrentVolume, tail')
272
else if T.any (== ':') head'
273
then (Just (parseDrive head'), tail')
336
parsed = if T.null head'
337
then (Just RootWindowsCurrentVolume, tail')
338
else if T.any (== ':') head'
339
then (Just (parseDrive head'), tail')
274
340
else (Nothing, split)
276
parseDrive = RootWindowsVolume . toUpper . T.head
278
(directories, filename)
279
| P.null pastRoot = ([], "")
280
| otherwise = case last pastRoot of
281
fn | fn == T.pack "." -> (goodDirs pastRoot, "")
282
fn | fn == T.pack ".." -> (goodDirs pastRoot, "")
283
fn -> (goodDirs (init pastRoot), escape fn)
285
goodDirs :: [T.Text] -> [Chunk]
286
goodDirs = map escape . filter (not . T.null)
288
(basename, exts) = parseFilename filename
342
parseDrive c = RootWindowsVolume (toUpper (T.head c)) extended
344
parseDoubleQmark :: T.Text -> (Maybe Root, [T.Text])
345
parseDoubleQmark text = (Just RootWindowsDoubleQMark, components) where
346
components = textSplitBy (\c -> c == '/' || c == '\\') text
348
parseUncRoot :: T.Text -> Bool -> (Maybe Root, [T.Text])
349
parseUncRoot text extended = parsed where
350
(host, pastHost) = T.break (== '\\') text
351
(share, pastShare) = T.break (== '\\') (T.drop 1 pastHost)
352
split = if T.null pastShare
354
else textSplitBy (== '\\') pastShare
355
parsed = (Just (RootWindowsUnc (T.unpack host) (T.unpack share) extended), split)
290
357
winValid :: FilePath -> Bool
291
winValid p = validRoot && noReserved && validCharacters where
358
winValid p = case pathRoot p of
359
Nothing -> dosValid p
360
Just RootWindowsCurrentVolume -> dosValid p
361
Just (RootWindowsVolume v _) -> elem v ['A'..'Z'] && dosValid p
362
Just (RootWindowsUnc host share _) -> uncValid p host share
363
-- don't even try to validate \??\ paths
364
Just RootWindowsDoubleQMark -> True
365
Just RootPosix -> False
367
dosValid :: FilePath -> Bool
368
dosValid p = noReserved && validCharacters where
292
369
reservedChars = map chr [0..0x1F] ++ "/\\?*:|\"<>"
294
371
[ "AUX", "CLOCK$", "COM1", "COM2", "COM3", "COM4"