То, что никто, кажется, не понимает, - то, что ни один из конструкторов System.Uri
правильно не обрабатывает определенные пути с символами процента в них.
new Uri(@"C:\%51.txt").AbsoluteUri;
Это дает вам "file:///C:/Q.txt"
вместо "file:///C:/%2551.txt"
.
Ни одно из значений устаревшего аргумента dontEscape не имеет значения, и указание UriKind также дает тот же результат. Попытка с UriBuilder также не помогает:
new UriBuilder() { Scheme = Uri.UriSchemeFile, Host = "", Path = @"C:\%51.txt" }.Uri.AbsoluteUri
Это также возвращает "file:///C:/Q.txt"
.
Насколько я могу судить, в фреймворке отсутствует какой-либо способ сделать это правильно.
Мы можем попытаться сделать это, заменив обратную косую черту прямой косой чертой и передавая путь к Uri.EscapeUriString
- т.е.
new Uri(Uri.EscapeUriString(filePath.Replace(Path.DirectorySeparatorChar, '/'))).AbsoluteUri
Поначалу кажется, что это работает, но если вы дадите ему путь C:\a b.txt
, вы получите file:///C:/a%2520b.txt
вместо file:///C:/a%20b.txt
- каким-то образом он решит, что некоторые последовательности должны быть декодированы, но не другие. Теперь мы можем просто добавить префикс к "file:///"
самим, однако это не учитывает UNC-пути, такие как \\remote\share\foo.txt
, - в Windows принято считать их псевдо-URLами вида file://remote/share/foo.txt
, поэтому мы следует принять это во внимание также.
EscapeUriString
также имеет проблему, что он не экранирует символ '#'
. В этот момент может показаться, что у нас нет другого выбора, кроме как создать собственный метод с нуля. Вот что я предлагаю:
public static string FilePathToFileUrl(string filePath)
{
StringBuilder uri = new StringBuilder();
foreach (char v in filePath)
{
if ((v >= 'a' && v <= 'z') || (v >= 'A' && v <= 'Z') || (v >= '0' && v <= '9') ||
v == '+' || v == '/' || v == ':' || v == '.' || v == '-' || v == '_' || v == '~' ||
v > '\xFF')
{
uri.Append(v);
}
else if (v == Path.DirectorySeparatorChar || v == Path.AltDirectorySeparatorChar)
{
uri.Append('/');
}
else
{
uri.Append(String.Format("%{0:X2}", (int)v));
}
}
if (uri.Length >= 2 && uri[0] == '/' && uri[1] == '/') // UNC path
uri.Insert(0, "file:");
else
uri.Insert(0, "file:///");
return uri.ToString();
}
Это преднамеренно оставляет + и: незакодированным, так как кажется, что это обычно делается в Windows. Он также кодирует только латиницу 1, поскольку Internet Explorer не может понять символы юникода в URL-адресах файлов, если они закодированы.