web-dev-qa-db-ja.com

Uri.EscapeDataString()-無効なURI:Uri文字列が長すぎます

WindowsMo​​bileでコンパクトフレームワーク/ C#を使用しています。

私のアプリケーションでは、オブジェクトをシリアル化し、HttpWebRequest/POSTリクエストを使用して情報を送信することにより、サーバーにデータをアップロードしています。サーバー上で、投稿データは逆シリアル化され、データベースに保存されます。

先日、投稿データの特殊文字(アンパサンドなど)に問題があることに気づきました。そこで、Uri.EscapeDataString()をメソッドに導入しましたが、すべてうまくいきました。

しかし、今日、アプリケーションが大量のデータをアップロードしようとすると問題があることがわかりました(現時点では、正確に「大」を意味するものがわかりません!)

既存のコード(種類)

var uploadData = new List<Things>();

uploadData.Add(new Thing() { Name = "Test 01" });
uploadData.Add(new Thing() { Name = "Test 02" });
uploadData.Add(new Thing() { Name = "Test with an & Ampersand " }); // Do this a lot!!

var postData = "uploadData=" + Uri.EscapeDataString(JsonConvert.SerializeObject(uploadData, new IsoDateTimeConverter()));

問題

Uri.EscapeDataString()を呼び出すと、次の例外が発生します。

System.UriFormatException:無効なURI:Uri文字列が長すぎます。

質問

アップロード用のデータを準備する他の方法はありますか?

私が見る限り、HttpUtility(独自のEncode/Decodeメソッドを持つ)はコンパクトフレームワークでは使用できません。

29
ETFairfax

または、関数の再実装を回避するために、文字列を分割してブロックごとにUri.EscapeDataString(string)を呼び出すこともできます。

サンプルコード:

        String value = "large string to encode";
        int limit = 2000;

        StringBuilder sb = new StringBuilder();
        int loops = value.Length / limit;

        for (int i = 0; i <= loops; i++)
        {
            if (i < loops)
            {
                sb.Append(Uri.EscapeDataString(value.Substring(limit * i, limit)));
            }
            else
            {
                sb.Append(Uri.EscapeDataString(value.Substring(limit * i)));
            }
        }
35
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < originalString.Length; i++)
{
    if ((originalString[i] >= 'a' && originalString[i] <= 'z') || 
        (originalString[i] >= 'A' && originalString[i] <= 'Z') || 
        (originalString[i] >= '0' && originalString[i] <= '9'))
    {
        stringBuilder.Append(originalString[i]);
    }
    else
    {
        stringBuilder.AppendFormat("%{0:X2}", (int)originalString[i]);
    }
}

string result = stringBuilder.ToString();
2
Doug

「アルベルト・デ・パオラ」の答えは良いです。

それでも、エスケープされたデータをエスケープ解除するには、エンコードされた文字の途中でエンコードされた文字列を切断しないようにする必要があるため、少し注意が必要です(または、元の文字列の整合性が損なわれます)。

この問題を修正する私の方法は次のとおりです。

public static string EncodeString(string str)
{
    //maxLengthAllowed .NET < 4.5 = 32765;
    //maxLengthAllowed .NET >= 4.5 = 65519;
    int maxLengthAllowed = 65519;
    StringBuilder sb = new StringBuilder();
    int loops = str.Length / maxLengthAllowed;

    for (int i = 0; i <= loops; i++)
    {
        sb.Append(Uri.EscapeDataString(i < loops
            ? str.Substring(maxLengthAllowed * i, maxLengthAllowed)
            : str.Substring(maxLengthAllowed * i)));
    }

    return sb.ToString();
}

public static string DecodeString(string encodedString)
{
    //maxLengthAllowed .NET < 4.5 = 32765;
    //maxLengthAllowed .NET >= 4.5 = 65519;
    int maxLengthAllowed = 65519;

    int charsProcessed = 0;
    StringBuilder sb = new StringBuilder();

    while (encodedString.Length > charsProcessed)
    {
        var stringToUnescape = encodedString.Substring(charsProcessed).Length > maxLengthAllowed
            ? encodedString.Substring(charsProcessed, maxLengthAllowed)
            : encodedString.Substring(charsProcessed);

        // If the loop cut an encoded tag (%xx), we cut before the encoded char to not loose the entire char for decoding
        var incorrectStrPos = stringToUnescape.Length == maxLengthAllowed ? stringToUnescape.IndexOf("%", stringToUnescape.Length - 4, StringComparison.InvariantCulture) : -1;
        if (incorrectStrPos > -1)
        {
            stringToUnescape = encodedString.Substring(charsProcessed).Length > incorrectStrPos
                ? encodedString.Substring(charsProcessed, incorrectStrPos)
                : encodedString.Substring(charsProcessed);
        }

        sb.Append(Uri.UnescapeDataString(stringToUnescape));
        charsProcessed += stringToUnescape.Length;
    }

    var decodedString = sb.ToString();

    // ensure the string is sanitized here or throw exception if XSS / SQL Injection is found
    SQLHelper.SecureString(decodedString);
    return decodedString;
}

これらの機能をテストするには:

var testString = "long string to encode";
var encodedString = EncodeString(testString);
var decodedString = DecodeString(encodedString);

Console.WriteLine(decodedString == testString ? "integrity respected" : "integrity broken");

これがいくつかの頭痛を避けるのに役立つことを願っています;)

2
Pouki

私はSystem.Web.HttpUtility.UrlEncodeを使用していて、長い文字列をはるかにうまく処理しているようです。

1
themullet

使用する System.Web.HttpUtility.UrlEncodeこの回答 に基づく):

        value = HttpUtility.UrlEncode(value)
            .Replace("!", "%21")
            .Replace("(", "%28")
            .Replace(")", "%29")
            .Replace("*", "%2A")
            .Replace("%7E", "~"); // undo escape
1
Jeroen K

キリル文字を処理してシンボルをカットすると、Poukiのソリューションが機能しないため、別のソリューションが必要でした。

代替ソリューションは次のとおりです。

    protected const int MaxLengthAllowed = 32765;
    private static string UnescapeString(string encodedString)
    {
        var charsProccessed = 0;

        var sb = new StringBuilder();

        while (encodedString.Length > charsProccessed)
        {
            var isLastIteration = encodedString.Substring(charsProccessed).Length < MaxLengthAllowed;

            var stringToUnescape = isLastIteration
                ? encodedString.Substring(charsProccessed)
                : encodedString.Substring(charsProccessed, MaxLengthAllowed);

            while (!Uri.IsWellFormedUriString(stringToUnescape, UriKind.RelativeOrAbsolute) || stringToUnescape.Length == 0)
            {
                stringToUnescape = stringToUnescape.Substring(0, stringToUnescape.Length - 1);
            }

            sb.Append(Uri.UnescapeDataString(stringToUnescape));
            charsProccessed += stringToUnescape.Length;
        }

        return sb.ToString();
    }
0
BIGDOGICO