Как я могу кодировать строки строки таблицы хранения 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);