O que ninguém parece perceber é que nenhum dos System.Uri
construtores manipula corretamente certos caminhos com sinais de porcentagem neles.
new Uri(@"C:\%51.txt").AbsoluteUri;
Isso dá a você, em "file:///C:/Q.txt"
vez de"file:///C:/%2551.txt"
.
Nenhum dos valores do argumento dontEscape descontinuado faz qualquer diferença e a especificação do UriKind também fornece o mesmo resultado. Tentar com o UriBuilder também não ajuda:
new UriBuilder() { Scheme = Uri.UriSchemeFile, Host = "", Path = @"C:\%51.txt" }.Uri.AbsoluteUri
Isso retorna "file:///C:/Q.txt"
também.
Tanto quanto posso dizer, o framework realmente não tem nenhuma maneira de fazer isso corretamente.
Podemos tentar isso substituindo as barras invertidas por barras invertidas e alimentando o caminho para Uri.EscapeUriString
- ie
new Uri(Uri.EscapeUriString(filePath.Replace(Path.DirectorySeparatorChar, '/'))).AbsoluteUri
Isso parece funcionar no começo, mas se você der o caminho C:\a b.txt
, você termina com, em file:///C:/a%2520b.txt
vez de file:///C:/a%20b.txt
- de alguma forma, decide que algumas seqüências devem ser decodificadas, mas não outras. Agora podemos apenas prefixar a "file:///"
nós mesmos, no entanto, isso não leva \\remote\share\foo.txt
em consideração os caminhos UNC - o que parece ser geralmente aceito no Windows é transformá-los em pseudo-urls do formulário file://remote/share/foo.txt
, por isso devemos levar isso em consideração também.
EscapeUriString
também tem o problema de não escapar do '#'
personagem. Parece que, nesse ponto, não temos outra escolha a não ser fazer nosso próprio método a partir do zero. Então é isso que eu sugiro:
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();
}
Isso deixa intencionalmente + e: sem codificação, pois parece que é assim que geralmente é feito no Windows. Ele também codifica latin1, pois o Internet Explorer não pode entender caracteres unicode em URLs de arquivo se eles estiverem codificados.
var path = new Uri("file:///C:/whatever.txt").LocalPath;
transforma um Uri de volta em um caminho de arquivo local também para quem precisa disso.