【问题标题】:Regex without escaping Characters - Problems没有转义字符的正则表达式 - 问题
【发布时间】:2014-02-18 23:55:44
【问题描述】:

我为我的问题找到了一些解决方案,这很简单: 我有一个字符串,看起来像这样:

"\r\nContent-Disposition: form-data; name=\"ctl00$cphMainContent$grid$ctl03$ucPicture$ctl00\"" 

我的目标是分解它,所以我有一个值字典,例如: 键=“名称”,值? “ctl...”

我的方法是:用“\r\n”分割,然后用等号或冒号分割。 这很好用,但后来一些有趣的测试人员上传了一个包含所有允许字符的文件,这使得字符串看起来像这样:

"\r\nContent-Disposition: form-data; name=\"ctl00_cphMainContent_grid_ctl03_ucPicture_btnUpload$fileUpload\"; filename=\"C:\\Users\\matthias.mueller\\Desktop\\- ie+![]{}_-´;,.$¨@#ç %&()=~^`'.jpg\"\r\nContent-Type: image/jpeg"

当然,简单的拆分不再起作用,因为它现在拆分了文件名。 我通过读出“filename=”并转义我要拆分的符号来纠正这个问题,然后创建一个正则表达式。

现在我的问题来了:我找到了两个正则表达式样本,它们可以处理等号、分号和冒号。一个是:

[^\\]=

我找到的另一个是:

(?<!\\\\)=

问题是,第一个不只是拆分,而是拆分等号和这个符号之前的一个字符,这意味着我在字典中的键是“nam”而不是“name”

第二个在这个问题上工作正常,但它仍然拆分文件名中的转义等号。

我解决这个问题的方法是否有效?会有更好的解决方案吗?为什么第一个正则表达式会删减一个字符?

编辑:为避免混淆,我的转义字符串如下所示: “内容处置:表单数据;名称=\”ctl00_cphMainContent_grid_ctl03_ucPicture_btnUpload$fileUpload\”;文件名=\”C\:\Users\matthias.mueller\Desktop\-ie+![]{}_-´\;,.$ ¨@#ç %&()\=~^`'.jpg\""

所以我基本上想要:除转义的以外,用等号分割。顺便说一句:这里的字符串只显示了一个\,但是有2个。

编辑 2:好的,我似乎有一个可行的解决方案,但它太丑了:

Dictionary<string, string> ParseHeader(byte[] bytes, int pos)
    {
        Dictionary<string, string> items;
        string header;
        string[] headerLines;
        int start;
        int end;

        string input = _encoding.GetString(bytes, pos, bytes.Length - pos);

        start = input.IndexOf("\r\n", 0);
        if (start < 0) return null;

        end = input.IndexOf("\r\n\r\n", start);
        if (end < 0) return null;

        WriteBytes(false, bytes, pos, end + 4 - 0); // Write the header to the form content

        header = input.Substring(start, end - start);

        items = new Dictionary<string, string>();

        headerLines = Regex.Split(header, "\r\n");

        Regex regLineParts = new Regex(@"(?<!\\\\);");
        Regex regColon = new Regex(@"(?<!\\\\):");
        Regex regEqualSign = new Regex(@"(?<!\\\\)=");


        foreach (string hl in headerLines)
        {
            string workString = hl;
            //Escape the Semicolon in filename
            if (hl.Contains("filename"))
            {
                String orig = hl.Substring(hl.IndexOf("filename=\"") + 10);
                orig = orig.Substring(0, orig.IndexOf('"'));
                string toReplace = orig;
                toReplace = toReplace.Replace(toReplace, toReplace.Replace(";", @"\\;"));
                toReplace = toReplace.Replace(toReplace, toReplace.Replace(":", @"\\:"));
                toReplace = toReplace.Replace(toReplace, toReplace.Replace("=", @"\\="));
                workString = hl.Replace(orig, toReplace);
            }

            string[] lineParts = regLineParts.Split(workString);

            for (int i = 0; i < lineParts.Length; i++)
            {
                string[] p;

                if (i == 0)
                    p = regColon.Split(lineParts[i]);
                else
                    p = regEqualSign.Split(lineParts[i]);

                if (p.Length == 2)
                {
                    string orig = p[0];
                    orig = orig.Replace(@"\\;", ";");
                    orig = orig.Replace(@"\\:", ":");
                    orig = orig.Replace(@"\\=", "=");
                    p[0] = orig;

                    orig = p[1];
                    orig = orig.Replace(@"\\;", ";");
                    orig = orig.Replace(@"\\:", ":");
                    orig = orig.Replace(@"\\=", "=");
                    p[1] = orig;

                    items.Add(p[0].Trim(), p[1].Trim());
                }
            }
        }

        return items;
    }

需要进一步测试。

【问题讨论】:

  • “文件名中的转义等号”在哪里?我没看到。
  • 你不能为此使用一些 mime/email/http 标头解析器吗?
  • 请注意,“\”不包含反斜杠,只有一个"
  • ContentDispositionHeaderValue.Parse

标签: c# regex


【解决方案1】:

我尝试为你编写一个解析器。它将文字字符串(如"here is a string")作为名称-值对中的值进行处理。我还编写了一些测试,最后一个在文字字符串中显示了一个 '=' 字符。它还通过转义为 \" 来处理文字字符串中的转义引号 (") -- 我不确定这是否正确,但您可以更改它。

快速解释。我首先找到任何看起来像文字字符串的东西,然后将其替换为PLACEHOLDER8230498234098230498 之类的值。这意味着整个事情现在都是字面的名称-值对;例如

key="value"

变成

key=PLACEHOLDER8230498234098230498

原始字符串值存储在literalStrings 字典中以供以后使用。

所以现在我们拆分分号(得到key=value 字符串),然后拆分等于,得到正确的键/值对。

然后我在返回结果之前替换占位符值。

public class HttpHeaderParser
{
    public NameValueCollection Parse(string header)
    {
        var result = new NameValueCollection();

        // 'register' any string values;
        var stringLiteralRx = new Regex(@"""(?<content>(\\""|[^\""])+?)""", RegexOptions.IgnorePatternWhitespace);
        var equalsRx = new Regex("=", RegexOptions.IgnorePatternWhitespace);
        var semiRx = new Regex(";", RegexOptions.IgnorePatternWhitespace);

        Dictionary<string, string> literalStrings = new Dictionary<string, string>();
        var cleanedHeader = stringLiteralRx.Replace(header, m =>
        {
            var replacement = "PLACEHOLDER" + Guid.NewGuid().ToString("N");
            var stringLiteral = m.Groups["content"].Value.Replace("\\\"", "\"");
            literalStrings.Add(replacement, stringLiteral);
            return replacement;
        });

        // now it's safe to split on semicolons to get name-value pairs
        var nameValuePairs = semiRx.Split(cleanedHeader);
        foreach(var nameValuePair in nameValuePairs)
        {
            var nameAndValuePieces = equalsRx.Split(nameValuePair);
            var name = nameAndValuePieces[0].Trim();
            var value = nameAndValuePieces[1];
            string replacementValue;
            if (literalStrings.TryGetValue(value, out replacementValue))
            {
                value = replacementValue;
            }
            result.Add(name, value);
        }

        return result;

    }
}

其中很可能存在一些适当的错误。

这里有一些你也应该合并的单元测试;

    [TestMethod]
    public void TestMethod1()
    {
        var tests = new[] {
            new { input=@"foo=bar; baz=quux", expected = @"foo|bar^baz|quux"},
            new { input=@"foo=bar;baz=""quux""", expected = @"foo|bar^baz|quux"},
            new { input=@"foo=""bar"";baz=""quux""", expected = @"foo|bar^baz|quux"},
            new { input=@"foo=""b,a,r"";baz=""quux""", expected = @"foo|b,a,r^baz|quux"},
            new { input=@"foo=""b;r"";baz=""quux""", expected = @"foo|b;r^baz|quux"},
            new { input=@"foo=""b\""r"";baz=""quux""", expected = @"foo|b""r^baz|quux"},
            new { input=@"foo=""b=r"";baz=""quux""", expected = @"foo|b=r^baz|quux"},
        };

        var parser = new HttpHeaderParser();
        foreach(var test in tests)
        {
            var actual = parser.Parse(test.input);
            var actualAsString = String.Join("^", actual.Keys.Cast<string>().Select(k => string.Format("{0}|{1}", k, actual[k])));
            Assert.AreEqual(test.expected, actualAsString);
        }
    }

【讨论】:

  • 很好,谢谢。我将此掩盖为答案,因为它比我的修补更合适。
【解决方案2】:

在我看来,与正则表达式拆分相比,您需要更多可靠的解析器。根据this page,名称/值对可以是“原始”;

x=1

或引用;

x="foo bar baz"

因此,您需要寻找一种解决方案,它不仅在等号上拆分,而且忽略内部的任何等号;

x="y=z"

您可能有更好或更易于管理的方式来访问此信息。如果您使用的是经典的 ASP.NET WebForms FileUpload 控件,则可以访问文件名using the properties of the control,如

FileUpload1.HasFile
FileUpload1.FileName

如果您使用 MVC,则可以使用 HttpPostedFileBase 类作为操作方法的参数。看到这个answer

[HttpPost]
public ActionResult Index(HttpPostedFileBase file)
{
    // Verify that the user selected a file
    if (file != null && file.ContentLength > 0) 
    {
        // extract only the fielname
        var fileName = Path.GetFileName(file.FileName);
        // store the file inside ~/App_Data/uploads folder
        var path = Path.Combine(Server.MapPath("~/App_Data/uploads"), fileName);
        file.SaveAs(path);
    }
    // redirect back to the index action to show the form once again
    return RedirectToAction("Index");        
}

【讨论】:

  • 非常感谢,这肯定是正确的解决方案。可悲的是,我的问题只是复杂 web 控件的一部分,我只通过字节数组接收数据。
【解决方案3】:

这个:

(?<!\\\\)=

匹配前面没有\\=

应该是:

(?<!\\)=

(确保您在正则表达式中使用@(逐字)字符串,以避免混淆)

【讨论】:

  • 我编辑了我的帖子,向您展示字符串。我希望它被拆分,除非 \\= 发生。但它似乎不起作用......
  • 您的问题令人困惑,因为我不知道该字符串是它在 C# 中的显示方式,还是实际的字符串。在 C# 中,您的 = 应变为 \\\"(或逐字字符串中的 \"")。您的实际字符串应包含\"
  • 是的,对不起,我自己搞错了,所以你是对的,先生。
  • 哇,我自己也很困惑。不知何故,我混淆了="。最后 3 件应为 \\=\=\=
猜你喜欢
  • 2015-09-18
  • 1970-01-01
  • 2020-02-05
  • 2010-12-22
  • 2010-09-21
  • 2020-07-24
相关资源
最近更新 更多