Skip to content

Commit fd00a3e

Browse files
Add support to RFC2184 on header parameters.
1 parent 7817e05 commit fd00a3e

8 files changed

Lines changed: 406 additions & 30 deletions

File tree

‎Class Library/ActiveUp.Net.Common/ActiveUp.Net.Common.csproj‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@
166166
<Compile Include="Rfc2047\Rfc2047Codec.cs" />
167167
<Compile Include="Rfc2047\Separator.cs" />
168168
<Compile Include="Rfc2047\EncodedWord.cs" />
169+
<Compile Include="Rfc2184\Rfc2184ContentParameterParser.cs" />
170+
<Compile Include="Rfc2184\Rfc2184ItemParser.cs" />
169171
<Compile Include="SaslMechanism.cs" />
170172
<Compile Include="Server.cs" />
171173
<Compile Include="ServerCollection.cs" />

‎Class Library/ActiveUp.Net.Common/Parser.cs‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using System.Text;
2424
using ActiveUp.Net.Security;
2525
using System.IO;
26+
using ActiveUp.Net.Common;
2627

2728
namespace ActiveUp.Net.Mail
2829
{
@@ -101,12 +102,16 @@ private static ContentType GetContentType(string input)
101102
{
102103
MimeType = Regex.Match(input, @"(?<=: ?)\S+?(?=([;\s]|\Z))").Value
103104
};
105+
106+
var parameterParser = new Rfc2184ContentParameterParser();
104107
var parammatch = Regex.Match(input, @"(?<=;\s*)[^;\s?]*=[^;]*(?=(;|\Z))");
105-
while (parammatch.Success)
108+
for (; parammatch.Success; parammatch = parammatch.NextMatch())
106109
{
107-
field.Parameters.Add(FormatFieldName(parammatch.Value.Substring(0, parammatch.Value.IndexOf('='))).ToLower(), parammatch.Value.Substring(parammatch.Value.IndexOf('=') + 1).Replace("\"", "").Trim('\r', '\n'));
108-
parammatch = parammatch.NextMatch();
110+
parameterParser.Add(parammatch.Value);
109111
}
112+
parameterParser.Parse();
113+
field.Parameters.Add(parameterParser.Parameters);
114+
110115
return field;
111116
}
112117

@@ -117,12 +122,21 @@ private static ContentType GetContentType(string input)
117122
/// <returns></returns>
118123
private static ContentDisposition GetContentDisposition(string input)
119124
{
120-
ContentDisposition field = new ContentDisposition();
121-
//TODO: include TAB detection in Regex
122-
field.Disposition = Regex.Match(input.Replace("\t", ""), @"(?<=: ?)\S+?(?=([;\s]|\Z))").Value;
125+
var field = new ContentDisposition()
126+
{
127+
Disposition = Regex.Match(input.Replace("\t", ""), @"(?<=: ?)\S+?(?=([;\s]|\Z))").Value
128+
};
129+
130+
var parameterParser = new Rfc2184ContentParameterParser();
123131
//TODO: include TAB detection in Regex
124-
Match parammatch = Regex.Match(input.Replace("\t", ""), @"(?<=;[ \t]?)[^;]*=[^;]*(?=(;|\Z))");
125-
for (; parammatch.Success; parammatch = parammatch.NextMatch()) field.Parameters.Add(FormatFieldName(parammatch.Value.Substring(0, parammatch.Value.IndexOf('='))), parammatch.Value.Substring(parammatch.Value.IndexOf('=') + 1).Replace("\"", "").Trim('\r', '\n'));
132+
var parammatch = Regex.Match(input.Replace("\t", ""), @"(?<=;[ \t]?)[^;]*=[^;]*(?=(;|\Z))");
133+
for (; parammatch.Success; parammatch = parammatch.NextMatch())
134+
{
135+
parameterParser.Add(parammatch.Value);
136+
}
137+
parameterParser.Parse();
138+
field.Parameters.Add(parameterParser.Parameters);
139+
126140
return field;
127141
}
128142

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System.Collections.Generic;
2+
using System.Collections.Specialized;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace ActiveUp.Net.Common
7+
{
8+
internal class Rfc2184ContentParameterParser
9+
{
10+
private IList<string> _originalParameterList;
11+
private IList<Rfc2184ItemParser> _multiLineParameterList;
12+
public NameValueCollection Parameters { get; set; }
13+
14+
public Rfc2184ContentParameterParser()
15+
{
16+
Parameters = new NameValueCollection();
17+
_originalParameterList = new List<string>();
18+
_multiLineParameterList = new List<Rfc2184ItemParser>();
19+
}
20+
21+
public Rfc2184ContentParameterParser(IList<string> parameters) : this()
22+
{
23+
_originalParameterList = parameters;
24+
}
25+
26+
/// <summary>
27+
/// Add new string parameter to process list.
28+
/// </summary>
29+
/// <param name="simpleParameter"></param>
30+
public void Add(string simpleParameter)
31+
{
32+
_originalParameterList.Add(simpleParameter);
33+
}
34+
35+
/// <summary>
36+
/// Parse all parameter line strings received.
37+
/// </summary>
38+
public void Parse()
39+
{
40+
foreach (var param in _originalParameterList)
41+
{
42+
var actualItemParser = new Rfc2184ItemParser(param);
43+
if (actualItemParser.IsSingleLine)
44+
{
45+
AddSingleLineParameter(actualItemParser);
46+
}
47+
else
48+
{
49+
AddMultiLineParameter(actualItemParser);
50+
}
51+
}
52+
RecoverMultiLineParameters();
53+
}
54+
55+
/// <summary>
56+
/// Process all identified Multi-line itens to parameter collection.
57+
/// </summary>
58+
private void RecoverMultiLineParameters()
59+
{
60+
var multilineGroupList = _multiLineParameterList.OrderBy(i => i.Name).ThenBy(i => i.Index).GroupBy(i => i.Name);
61+
foreach (var item in multilineGroupList)
62+
{
63+
var itemName = item.Key;
64+
var itemValue = new StringBuilder();
65+
foreach (var paramValue in item)
66+
{
67+
itemValue.Append(paramValue.Value);
68+
}
69+
Parameters.Add(itemName, itemValue.ToString());
70+
}
71+
}
72+
73+
/// <summary>
74+
/// Add identified multiline item to future process.
75+
/// </summary>
76+
/// <param name="actualItemParser">Parsed item.</param>
77+
private void AddMultiLineParameter(Rfc2184ItemParser actualItemParser)
78+
{
79+
_multiLineParameterList.Add(actualItemParser);
80+
}
81+
82+
/// <summary>
83+
/// Add singleline item to final parameter list.
84+
/// </summary>
85+
/// <param name="actualItemParser"></param>
86+
private void AddSingleLineParameter(Rfc2184ItemParser actualItemParser)
87+
{
88+
if (!Parameters.AllKeys.ToList().Exists(p => p == actualItemParser.Name))
89+
{
90+
Parameters.Add(actualItemParser.Name, actualItemParser.Value);
91+
}
92+
}
93+
}
94+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System;
2+
3+
namespace ActiveUp.Net.Common
4+
{
5+
/// <summary>
6+
/// Parse line with RFC2184 rules (https://tools.ietf.org/html/rfc2184)
7+
/// </summary>
8+
internal class Rfc2184ItemParser
9+
{
10+
private string _OriginalItem;
11+
public bool IsSingleLine { get; private set; }
12+
public bool TextContainsSpecialChar { get; private set; }
13+
public int Index { get; private set; }
14+
public string Name { get; private set; }
15+
public string Language { get; private set; }
16+
public string CharSet { get; private set; }
17+
public string Value { get; private set; }
18+
19+
public Rfc2184ItemParser(string itemLine)
20+
{
21+
_OriginalItem = itemLine;
22+
ParseLine();
23+
}
24+
25+
private void ParseLine()
26+
{
27+
ExtractNameData(_OriginalItem.Substring(0, _OriginalItem.IndexOf('=')));
28+
ExtractValueData(_OriginalItem.Substring(_OriginalItem.IndexOf('=') + 1).Replace("\"", "").Trim('\r', '\n'));
29+
}
30+
31+
/// <summary>
32+
/// Extract all information about FieldName.
33+
/// </summary>
34+
/// <param name="fieldName">String of fieldName.</param>
35+
private void ExtractNameData(string fieldName)
36+
{
37+
IsSingleLine = true;
38+
TextContainsSpecialChar = false;
39+
Index = 0;
40+
41+
var nameStructure = (fieldName ?? "").ToLower().Trim();
42+
43+
// If last char is *, then line contain CharSet or Lang.
44+
if (nameStructure.Length > 0 && nameStructure.Substring(nameStructure.Length-1) == "*")
45+
{
46+
TextContainsSpecialChar = true;
47+
nameStructure = nameStructure.Substring(0, nameStructure.Length - 1);
48+
}
49+
50+
var firstAsteriskIndex = nameStructure.IndexOf('*');
51+
if (firstAsteriskIndex >= 0)
52+
{
53+
Name = nameStructure.Substring(0, firstAsteriskIndex).Trim();
54+
var itemCount = nameStructure.Substring(firstAsteriskIndex);
55+
if (itemCount.Contains("*"))
56+
{
57+
var actualIndex = itemCount.Replace("*", "");
58+
if (Int32.TryParse(actualIndex, out int newIndex))
59+
{
60+
Index = newIndex;
61+
}
62+
}
63+
IsSingleLine = false;
64+
}
65+
else
66+
{
67+
Name = nameStructure;
68+
}
69+
}
70+
71+
/// <summary>
72+
/// Extract all information about FieldValue.
73+
/// </summary>
74+
/// <param name="fieldValue">Value string to extract information.</param>
75+
private void ExtractValueData(string fieldValue)
76+
{
77+
if (string.IsNullOrWhiteSpace(fieldValue)) return;
78+
var valueToProcess = (fieldValue ?? "").Trim();
79+
if (valueToProcess.Length > 1 && valueToProcess.Substring(valueToProcess.Length - 1, 1) == ";") valueToProcess = valueToProcess.Substring(0, valueToProcess.Length - 1);
80+
81+
if (!TextContainsSpecialChar || !valueToProcess.Contains("'"))
82+
{
83+
Value = valueToProcess;
84+
}
85+
86+
var splitedFieldValue = valueToProcess.Split('\'');
87+
if (splitedFieldValue.Length < 3)
88+
{
89+
CharSet = null;
90+
Language = null;
91+
Value = (valueToProcess ?? "").Trim();
92+
}
93+
else
94+
{
95+
CharSet = splitedFieldValue[0];
96+
Language = splitedFieldValue[1];
97+
Value = splitedFieldValue[2];
98+
}
99+
}
100+
}
101+
}

‎Class Library/ActiveUp.Net.Tests/ActiveUp.Net.Tests.csproj‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@
8282
<Compile Include="Common\MimePartTests.cs" />
8383
<Compile Include="Common\RFC2047\Rfc2047DecoderTests.cs" />
8484
<Compile Include="Common\RFC2047\Rfc2047DecoderTests.Splitter.cs" />
85+
<Compile Include="Common\RFC2184\Rfc2184ContentParameterParserTests.cs" />
86+
<Compile Include="Common\RFC2184\Rfc2184ItemParserTests.cs" />
8587
<Compile Include="FluentTests.cs" />
8688
<Compile Include="Groupware\ParserTests.cs" />
8789
<Compile Include="ImapTests.cs" />
@@ -202,6 +204,7 @@
202204
<ItemGroup>
203205
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
204206
</ItemGroup>
207+
<ItemGroup />
205208
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
206209
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
207210
Other similar extension points exist, see Microsoft.Common.targets.

‎Class Library/ActiveUp.Net.Tests/Common/ParserTests.cs‎

Lines changed: 20 additions & 22 deletions
Large diffs are not rendered by default.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using ActiveUp.Net.Common;
2+
using NUnit.Framework;
3+
using System.IO;
4+
using System.Reflection;
5+
6+
namespace ActiveUp.Net.Tests.Common.RFC2184
7+
{
8+
public class Rfc2184ContentParameterParserTests
9+
{
10+
private static string _baseDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
11+
12+
[Test]
13+
public void shouldDecodeSimpleParameters()
14+
{
15+
var parameterParser = new Rfc2184ContentParameterParser();
16+
parameterParser.Add("URL=\"ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar\"");
17+
parameterParser.Add("title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A");
18+
parameterParser.Parse();
19+
Assert.AreEqual(2, parameterParser.Parameters.Count);
20+
Assert.AreEqual("ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar", parameterParser.Parameters["url"]);
21+
Assert.AreEqual("This%20is%20%2A%2A%2Afun%2A%2A%2A", parameterParser.Parameters["title"]);
22+
}
23+
24+
[Test]
25+
public void shouldDecodeMultiplelineParameters()
26+
{
27+
var parameterParser = new Rfc2184ContentParameterParser();
28+
parameterParser.Add("URL*0=\"ftp://\";");
29+
parameterParser.Add("URL*1=\"cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar\"");
30+
parameterParser.Add("title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A");
31+
parameterParser.Parse();
32+
Assert.AreEqual(2, parameterParser.Parameters.Count);
33+
Assert.AreEqual("ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar", parameterParser.Parameters["url"]);
34+
Assert.AreEqual("This%20is%20%2A%2A%2Afun%2A%2A%2A", parameterParser.Parameters["title"]);
35+
}
36+
37+
[Test]
38+
public void shouldDecodeMultiplelineParametersWithCharset()
39+
{
40+
var parameterParser = new Rfc2184ContentParameterParser();
41+
parameterParser.Add("URL*0=\"ftp://\";");
42+
parameterParser.Add("URL*1=\"cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar\"");
43+
parameterParser.Add("title*1*=us-ascii'en'This%20is%20even%20more%20");
44+
parameterParser.Add("title*2*=%2A%2A%2Afun%2A%2A%2A%20");
45+
parameterParser.Add("title*3=\"isn't it!\"");
46+
parameterParser.Parse();
47+
Assert.AreEqual(2, parameterParser.Parameters.Count);
48+
Assert.AreEqual("ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar", parameterParser.Parameters["url"]);
49+
Assert.AreEqual("This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn't it!", parameterParser.Parameters["title"]);
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)