Как я могу кодировать строки строки таблицы хранения Azure и ключи разделов?
Я использую таблицы хранения Azure, и у меня есть данные, относящиеся к RowKey, в котором есть слэши. Согласно этой странице MSDN, следующие символы запрещены как в разделах PartitionKey, так и в RowKey:
-
Символ прямой косой черты (/)
-
Символ обратной косой черты()
-
Символ номер (#)
-
Символ вопросительного знака (?)
-
Управляющие символы от U + 0000 до U + 001F, включая:
-
Символ горизонтальной вкладки (\ t)
-
Символ linefeed (\n)
-
Символ возврата каретки (\ r)
-
Управляющие символы от U + 007F до U + 009F
Я видел, как некоторые люди используют кодировку URL, чтобы обойти это. К сожалению, есть несколько сбоев, которые могут возникнуть из-за этого, например, возможность вставлять, но не удалять определенные объекты. Я также видел, что некоторые люди используют кодировку base64, однако это также может содержать запрещенные символы.
Как я могу эффективно закодировать свой RowKey, не запускаясь с запрещенными символами, или скопировав собственную кодировку?
Ответы
Ответ 1
Когда URL-адрес кодируется Base64, единственным символом, который является недопустимым в столбце ключей хранилища Azure, является косая черта ('/'). Чтобы устранить это, просто замените символ косой черты другим символом, который является (1) действительным в столбце ключей хранилища Azure Table, и (2) не является символом Base64. Наиболее распространенный пример, который я нашел (который приведен в других ответах), заключается в замене косой черты ('/') на символ подчеркивания ('_').
private static String EncodeUrlInKey(String url)
{
var keyBytes = System.Text.Encoding.UTF8.GetBytes(url);
var base64 = System.Convert.ToBase64String(keyBytes);
return base64.Replace('/','_');
}
При декодировании просто отмените замененный символ (сначала!), а затем Base64 декодируйте полученную строку. Это все, что нужно.
private static String DecodeUrlInKey(String encodedKey)
{
var base64 = encodedKey.Replace('_', '/');
byte[] bytes = System.Convert.FromBase64String(base64);
return System.Text.Encoding.UTF8.GetString(bytes);
}
Некоторые люди предположили, что другим символам Base64 также требуется кодировка. В соответствии с Документами хранения таблиц Azure это не так.
Ответ 2
Я столкнулся с такой же необходимостью.
Я не был удовлетворен кодировкой Base64, потому что он превращает удобочитаемую строку в неузнаваемую строку и раздувает размер строк независимо от того, следуют ли они правилам (потеря, когда подавляющее большинство персонажей не являются незаконными символы, которые должны быть экранированы).
Здесь кодер/декодер, использующий '!' как escape-символ почти так же, как традиционно использовать символ обратной косой черты.
public static class TableKeyEncoding
{
// https://msdn.microsoft.com/library/azure/dd179338.aspx
//
// The following characters are not allowed in values for the PartitionKey and RowKey properties:
// The forward slash(/) character
// The backslash(\) character
// The number sign(#) character
// The question mark (?) character
// Control characters from U+0000 to U+001F, including:
// The horizontal tab(\t) character
// The linefeed(\n) character
// The carriage return (\r) character
// Control characters from U+007F to U+009F
public static string Encode(string unsafeForUseAsAKey)
{
StringBuilder safe = new StringBuilder();
foreach (char c in unsafeForUseAsAKey)
{
switch (c)
{
case '/':
safe.Append("!f");
break;
case '\\':
safe.Append("!b");
break;
case '#':
safe.Append("!p");
break;
case '?':
safe.Append("!q");
break;
case '\t':
safe.Append("!t");
break;
case '\n':
safe.Append("!n");
break;
case '\r':
safe.Append("!r");
break;
case '!':
safe.Append("!!");
break;
default:
if (c <= 0x1f || (c >= 0x7f && c <= 0x9f))
{
int charCode = c;
safe.Append("!x" + charCode.ToString("x2"));
}
else
{
safe.Append(c);
}
break;
}
}
return safe.ToString();
}
public static string Decode(string key)
{
StringBuilder decoded = new StringBuilder();
int i = 0;
while (i < key.Length)
{
char c = key[i++];
if (c != '!' || i == key.Length)
{
// There no escape character ('!'), or the escape should be ignored because it the end of the array
decoded.Append(c);
}
else
{
char escapeCode = key[i++];
switch (escapeCode)
{
case 'f':
decoded.Append('/');
break;
case 'b':
decoded.Append('\\');
break;
case 'p':
decoded.Append('#');
break;
case 'q':
decoded.Append('?');
break;
case 't':
decoded.Append('\t');
break;
case 'n':
decoded.Append("\n");
break;
case 'r':
decoded.Append("\r");
break;
case '!':
decoded.Append('!');
break;
case 'x':
if (i + 2 <= key.Length)
{
string charCodeString = key.Substring(i, 2);
int charCode;
if (int.TryParse(charCodeString, NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out charCode))
{
decoded.Append((char)charCode);
}
i += 2;
}
break;
default:
decoded.Append('!');
break;
}
}
}
return decoded.ToString();
}
}
Так как следует соблюдать особую осторожность при написании собственного кодера, я также написал для него некоторые модульные тесты.
using Xunit;
namespace xUnit_Tests
{
public class TableKeyEncodingTests
{
const char Unicode0X1A = (char) 0x1a;
public void RoundTripTest(string unencoded, string encoded)
{
Assert.Equal(encoded, TableKeyEncoding.Encode(unencoded));
Assert.Equal(unencoded, TableKeyEncoding.Decode(encoded));
}
[Fact]
public void RoundTrips()
{
RoundTripTest("!\n", "!!!n");
RoundTripTest("left" + Unicode0X1A + "right", "left!x1aright");
}
// The following characters are not allowed in values for the PartitionKey and RowKey properties:
// The forward slash(/) character
// The backslash(\) character
// The number sign(#) character
// The question mark (?) character
// Control characters from U+0000 to U+001F, including:
// The horizontal tab(\t) character
// The linefeed(\n) character
// The carriage return (\r) character
// Control characters from U+007F to U+009F
[Fact]
void EncodesAllForbiddenCharacters()
{
List<char> forbiddenCharacters = "\\/#?\t\n\r".ToCharArray().ToList();
forbiddenCharacters.AddRange(Enumerable.Range(0x00, 1+(0x1f-0x00)).Select(i => (char)i));
forbiddenCharacters.AddRange(Enumerable.Range(0x7f, 1+(0x9f-0x7f)).Select(i => (char)i));
string allForbiddenCharacters = String.Join("", forbiddenCharacters);
string allForbiddenCharactersEncoded = TableKeyEncoding.Encode(allForbiddenCharacters);
// Make sure decoding is same as encoding
Assert.Equal(allForbiddenCharacters, TableKeyEncoding.Decode(allForbiddenCharactersEncoded));
// Ensure encoding does not contain any forbidden characters
Assert.Equal(0, allForbiddenCharacters.Count( c => allForbiddenCharactersEncoded.Contains(c) ));
}
}
}
Ответ 3
посмотреть эти ссылки
http://tools.ietf.org/html/rfc4648#page-7
Код для декодирования/кодирования измененного URL-адреса base64 (см. также второй ответ: fooobar.com/questions/56167/...)
У меня была проблема сама. Это мои собственные функции, которые я использую для этого сейчас. Я использую трюк во втором ответе, о котором я упоминал, а также об изменении +
и /
, которые несовместимы с лазурными ключами, которые могут появляться.
private static String EncodeSafeBase64(String toEncode)
{
if (toEncode == null)
throw new ArgumentNullException("toEncode");
String base64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(toEncode));
StringBuilder safe = new StringBuilder();
foreach (Char c in base64String)
{
switch (c)
{
case '+':
safe.Append('-');
break;
case '/':
safe.Append('_');
break;
default:
safe.Append(c);
break;
}
}
return safe.ToString();
}
private static String DecodeSafeBase64(String toDecode)
{
if (toDecode == null)
throw new ArgumentNullException("toDecode");
StringBuilder deSafe = new StringBuilder();
foreach (Char c in toDecode)
{
switch (c)
{
case '-':
deSafe.Append('+');
break;
case '_':
deSafe.Append('/');
break;
default:
deSafe.Append(c);
break;
}
}
return Encoding.UTF8.GetString(Convert.FromBase64String(deSafe.ToString()));
}
Ответ 4
Если это только косые черты, вы можете просто заменить их при записи в таблицу другим символом, например, '|' и снова замените их при чтении.
Ответ 5
То, что я видел, состоит в том, что, хотя многие из символов, отличных от буквенно-цифровых символов, технически разрешены, на самом деле это не очень хорошо работает как раздел и ключ строки.
Я посмотрел на те, которые уже были указаны здесь и в других местах, и написал следующее:
https://github.com/JohanNorberg/AlphaNumeric
Два буквенно-цифровых кодера.
Если вам нужно избежать строки, которая в основном буквенно-цифровая, вы можете использовать это:
AlphaNumeric.English.Encode(str);
Если вам нужно избежать строки, которая в основном не является буквенно-цифровым, вы можете использовать это:
AlphaNumeric.Data.EncodeString(str);
Данные кодирования:
var base64 = Convert.ToBase64String(bytes);
var alphaNumericEncodedString = base64
.Replace("0", "01")
.Replace("+", "02")
.Replace("/", "03")
.Replace("=", "04");
Но, если вы хотите использовать, например, адрес электронной почты в качестве rowkey, вам нужно будет просто избегать "@" и ".". Этот код будет делать это:
char[] validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456789".ToCharArray();
char[] allChars = rawString.ToCharArray();
StringBuilder builder = new StringBuilder(rawString.Length * 2);
for(int i = 0; i < allChars.Length; i++)
{
int c = allChars[i];
if((c >= 51 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122))
{
builder.Append(allChars[i]);
}
else
{
int index = builder.Length;
int count = 0;
do
{
builder.Append(validChars[c % 59]);
c /= 59;
count++;
} while (c > 0);
if (count == 1) builder.Insert(index, '0');
else if (count == 2) builder.Insert(index, '1');
else if (count == 3) builder.Insert(index, '2');
else throw new Exception("Base59 has invalid count, method must be wrong Count is: " + count);
}
}
return builder.ToString();
Ответ 6
Как насчет функций кодирования/декодирования URL. Он заботится о символах '/'
, '?'
и '#'
.
string url = "http://www.google.com/search?q=Example";
string key = HttpUtility.UrlEncode(url);
string urlBack = HttpUtility.UrlDecode(key);