Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
FormDataDecoder.cs
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Text;
5using System.Threading.Tasks;
8
10{
17 {
21 public const string ContentType = "multipart/form-data";
22
29 {
30 }
31
35 public string[] ContentTypes => new string[] { ContentType };
36
40 public string[] FileExtensions => new string[] { "formdata" };
41
48 public bool Decodes(string ContentType, out Grade Grade)
49 {
51 {
52 Grade = Grade.Excellent;
53 return true;
54 }
55 else
56 {
57 Grade = Grade.NotAtAll;
58 return false;
59 }
60 }
61
72 public async Task<object> DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair<string, string>[] Fields, Uri BaseUri)
73 {
74 Dictionary<string, object> Form = new Dictionary<string, object>();
75
76 await Decode(Data, Fields, Form, null, BaseUri);
77
78 return Form;
79 }
80
89 public static async Task Decode(byte[] Data, KeyValuePair<string, string>[] Fields, Dictionary<string, object> Form,
90 List<EmbeddedContent> List, Uri BaseUri)
91 {
92 string Boundary = null;
93
94 if (!(Fields is null))
95 {
96 foreach (KeyValuePair<string, string> P in Fields)
97 {
98 if (string.Compare(P.Key, "BOUNDARY", true) == 0)
99 {
100 Boundary = P.Value;
101 break;
102 }
103 }
104 }
105
106 if (string.IsNullOrEmpty(Boundary))
107 throw new Exception("No boundary defined.");
108
109 byte[] BoundaryBin = Encoding.ASCII.GetBytes(Boundary);
110 int Start = 0;
111 int i = 0;
112 int c = Data.Length;
113 int d = BoundaryBin.Length;
114 int j;
115 int Max = c - d;
116
117 while (i < Max)
118 {
119 for (j = 0; j < d; j++)
120 {
121 if (Data[i + j] != BoundaryBin[j])
122 break;
123 }
124
125 if (j == d)
126 {
127 await AddPart(Data, Start, i, false, Form, List, BaseUri);
128
129 i += d;
130 while (i < c && Data[i] <= 32)
131 i++;
132
133 Start = i;
134 }
135 else
136 i++;
137 }
138
139 if (Start < c)
140 await AddPart(Data, Start, c, true, Form, List, BaseUri);
141 }
142
143 private static async Task AddPart(byte[] Data, int Start, int i, bool Last,
144 Dictionary<string, object> Form, List<EmbeddedContent> List, Uri BaseUri)
145 {
146 int j, k, l, m;
147 int Max = i - 3;
148
149 for (j = Start; j < Max; j++)
150 {
151 if (Data[j] == '\r' && Data[j + 1] == '\n' && Data[j + 2] == '\r' && Data[j + 3] == '\n')
152 break;
153 }
154
155 if (j == Start)
156 return;
157
158 if (j < i)
159 {
160 k = 0;
161 if (i >= 2 && Data[i - 1] == '-' && Data[i - 2] == '-')
162 {
163 k = 2;
164
165 if (i >= 4 && Data[i - 1 - k] == '\n' && Data[i - 2 - k] == '\r')
166 k += 2;
167 }
168
169 int NrBytes = i - j - 4 - k;
170 if (NrBytes < 0 || (NrBytes == 0 && Last))
171 return;
172
173 string Header = Encoding.ASCII.GetString(Data, Start, j - Start);
174 string Key, Value;
175 byte[] Data2 = new byte[NrBytes];
176 EmbeddedContent EmbeddedContent = new EmbeddedContent()
177 {
179 Raw = Data2
180 };
181
182 Array.Copy(Data, j + 4, Data2, 0, NrBytes);
183
184 string[] Rows = Header.Split(CommonTypes.CRLF);
185 l = Rows.Length;
186 m = -1;
187
188 for (j = 0; j < l; j++)
189 {
190 Key = Rows[j];
191 if (!string.IsNullOrEmpty(Key))
192 {
193 if (char.IsWhiteSpace(Key[0]) && m >= 0)
194 {
195 Rows[m] += Key;
196 Rows[j] = string.Empty;
197 }
198 else
199 m = j;
200 }
201 }
202
203 foreach (string Row in Rows)
204 {
205 j = Row.IndexOf(':');
206 if (j < 0)
207 continue;
208
209 Key = Row.Substring(0, j).Trim();
210 Value = Row.Substring(j + 1).Trim();
211
212 switch (Key.ToUpper())
213 {
214 case "CONTENT-TYPE":
215 EmbeddedContent.ContentType = Value;
216 j = Value.IndexOf(';');
217 if (j >= 0)
218 {
219 ParseContentFields(Value.Substring(j + 1).Trim(), EmbeddedContent);
220 Value = Value.Substring(0, j).Trim();
221 }
222 break;
223
224 case "CONTENT-DISPOSITION":
225 j = Value.IndexOf(';');
226 if (j >= 0)
227 {
228 ParseContentFields(Value.Substring(j + 1).Trim(), EmbeddedContent);
229 Value = Value.Substring(0, j).Trim();
230 }
231
232 switch (Value.ToUpper())
233 {
234 case "INLINE":
235 EmbeddedContent.Disposition = ContentDisposition.Inline;
236 break;
237
238 case "ATTACHMENT":
239 EmbeddedContent.Disposition = ContentDisposition.Attachment;
240 break;
241
242 case "FORM-DATA":
243 EmbeddedContent.Disposition = ContentDisposition.FormData;
244 break;
245 }
246 break;
247
248 case "CONTENT-TRANSFER-ENCODING":
249 EmbeddedContent.TransferEncoding = Value;
250 break;
251
252 case "CONTENT-ID":
253 EmbeddedContent.ID = Value;
254 break;
255
256 case "CONTENT-DESCRIPTION":
257 EmbeddedContent.Description = Value;
258 break;
259 }
260 }
261
262 if (!string.IsNullOrEmpty(EmbeddedContent.TransferEncoding))
263 {
264 if (TryTransferDecode(Data2, EmbeddedContent.TransferEncoding, out Data2))
265 EmbeddedContent.TransferDecoded = Data2;
266 else
267 throw new Exception("Unrecognized Content-Transfer-Encoding: " + EmbeddedContent.TransferEncoding);
268 }
269
270 try
271 {
272 EmbeddedContent.Decoded = await InternetContent.DecodeAsync(EmbeddedContent.ContentType, Data2, BaseUri);
273 }
274 catch (Exception)
275 {
276 EmbeddedContent.Decoded = Data2;
277 }
278
279 if (!(Form is null))
280 {
281 Form[EmbeddedContent.Name] = EmbeddedContent.Decoded;
282 Form[EmbeddedContent.Name + "_Binary"] = Data2;
283
284 if (!string.IsNullOrEmpty(EmbeddedContent.ContentType))
285 Form[EmbeddedContent.Name + "_ContentType"] = EmbeddedContent.ContentType;
286
287 if (!string.IsNullOrEmpty(EmbeddedContent.FileName))
288 Form[EmbeddedContent.Name + "_FileName"] = EmbeddedContent.FileName;
289 }
290
291 List?.Add(EmbeddedContent);
292 }
293 }
294
295 private static void ParseContentFields(string s, EmbeddedContent EmbeddedContent)
296 {
297 foreach (KeyValuePair<string, string> Field in CommonTypes.ParseFieldValues(s))
298 {
299 switch (Field.Key.ToUpper())
300 {
301 case "NAME":
302 EmbeddedContent.Name = Field.Value;
303 break;
304
305 case "FILENAME":
306 EmbeddedContent.FileName = Field.Value;
307 break;
308
309 case "SIZE":
310 if (int.TryParse(Field.Value, out int i))
311 EmbeddedContent.Size = i;
312 break;
313
314 case "CREATION-DATE":
315 if (CommonTypes.TryParseRfc822(Field.Value, out DateTimeOffset DTO))
316 EmbeddedContent.CreationDate = DTO;
317 break;
318
319 case "MODIFICATION-DATE":
320 if (CommonTypes.TryParseRfc822(Field.Value, out DTO))
321 EmbeddedContent.ModificationDate = DTO;
322 break;
323 }
324 }
325 }
326
334 public static bool TryTransferDecode(byte[] Encoded, string TransferEncoding, out byte[] Decoded)
335 {
336 switch (TransferEncoding.ToUpper())
337 {
338 case "7BIT":
339 case "8BIT":
340 case "BINARY":
341 case "":
342 case null:
343 Decoded = Encoded;
344 return true;
345
346 case "BASE64":
347 string s = CommonTypes.GetString(Encoded, Encoding.ASCII);
348 Decoded = Convert.FromBase64String(s);
349 return true;
350
351 case "QUOTED-PRINTABLE":
352 MemoryStream ms = new MemoryStream();
353 byte b;
354 char ch;
355 int j, k;
356
357 for (j = 0, k = Encoded.Length; j < k; j++)
358 {
359 b = Encoded[j];
360
361 if (b == (byte)'=' && j + 2 < k)
362 {
363 ch = (char)Encoded[++j];
364
365 if (ch >= '0' && ch <= '9')
366 b = (byte)(ch - '0');
367 else if (ch >= 'a' && ch <= 'f')
368 b = (byte)(ch - 'a' + 10);
369 else if (ch >= 'A' && ch <= 'F')
370 b = (byte)(ch - 'A' + 10);
371 else if (ch == '\r')
372 {
373 if (Encoded[j + 1] == (byte)'\n')
374 j++;
375
376 continue;
377 }
378 else
379 {
380 ms.WriteByte((byte)'=');
381 ms.WriteByte((byte)ch);
382 continue;
383 }
384
385 b <<= 4;
386
387 ch = (char)Encoded[++j];
388
389 if (ch >= '0' && ch <= '9')
390 b |= (byte)(ch - '0');
391 else if (ch >= 'a' && ch <= 'f')
392 b |= (byte)(ch - 'a' + 10);
393 else if (ch >= 'A' && ch <= 'F')
394 b |= (byte)(ch - 'A' + 10);
395 }
396
397 ms.WriteByte(b);
398 }
399
400 Decoded = ms.ToArray();
401 ms.Dispose();
402 return true;
403
404 default:
405 Decoded = null;
406 return false;
407 }
408 }
409
416 public bool TryGetContentType(string FileExtension, out string ContentType)
417 {
418 if (string.Compare(FileExtension, "formdata", true) == 0)
419 {
421 return true;
422 }
423 else
424 {
425 ContentType = string.Empty;
426 return false;
427 }
428 }
429
436 public bool TryGetFileExtension(string ContentType, out string FileExtension)
437 {
438 switch (ContentType.ToLower())
439 {
441 FileExtension = "formdata";
442 return true;
443
444 default:
445 FileExtension = string.Empty;
446 return false;
447 }
448 }
449
455 public static async Task<KeyValuePair<byte[], string>> Encode(IEnumerable<EmbeddedContent> Content)
456 {
457 string Boundary = Guid.NewGuid().ToString();
458 string ContentType = FormDataDecoder.ContentType + "; boundary=\"" + Boundary + "\"";
459 return new KeyValuePair<byte[], string>(await Encode(Content, Boundary), ContentType);
460 }
461
468 public static async Task<byte[]> Encode(IEnumerable<EmbeddedContent> Content, string Boundary)
469 {
470 using (MemoryStream ms = new MemoryStream())
471 {
472 StringBuilder Header = new StringBuilder();
473 byte[] HeaderBin;
474
475 foreach (EmbeddedContent Alternative in Content)
476 {
477 await Alternative.AssertEncoded();
478
479 Header.Clear();
480 Header.Append("\r\n--");
481 Header.Append(Boundary);
482
483 if (!string.IsNullOrEmpty(Alternative.TransferEncoding))
484 {
485 Header.Append("\r\nContent-Transfer-Encoding: ");
486 Header.Append(Alternative.TransferEncoding);
487 }
488
489 Header.Append("\r\nContent-Type: ");
490 Header.Append(Alternative.ContentType);
491
492 if (!string.IsNullOrEmpty(Alternative.Name) && Alternative.Disposition != ContentDisposition.FormData)
493 {
494 Header.Append("; name=\"");
495 Header.Append(Alternative.Name.Replace("\"", "\\\""));
496 Header.Append("\"");
497 }
498
499 if (Alternative.Disposition != ContentDisposition.Unknown ||
500 !string.IsNullOrEmpty(Alternative.FileName))
501 {
502 Header.Append("\r\nContent-Disposition: ");
503
504 switch (Alternative.Disposition)
505 {
506 case ContentDisposition.FormData:
507 Header.Append("form-data");
508
509 if (!string.IsNullOrEmpty(Alternative.Name))
510 {
511 Header.Append("; name=\"");
512 Header.Append(Alternative.Name.Replace("\"", "\\\""));
513 Header.Append("\"");
514 }
515 break;
516
517 case ContentDisposition.Inline:
518 Header.Append("inline");
519 break;
520
521 case ContentDisposition.Attachment:
522 default:
523 Header.Append("attachment");
524 break;
525 }
526
527 if (!string.IsNullOrEmpty(Alternative.FileName))
528 {
529 Header.Append("; filename=\"");
530 Header.Append(Alternative.FileName.Replace("\"", "\\\""));
531 Header.Append("\"");
532 }
533 }
534
535 if (!string.IsNullOrEmpty(Alternative.ID))
536 {
537 Header.Append("\r\nContent-ID: ");
538 Header.Append(Alternative.ID);
539 }
540
541 if (!string.IsNullOrEmpty(Alternative.Description))
542 {
543 Header.Append("\r\nContent-Description: ");
544 Header.Append(Alternative.Description);
545 }
546
547 Header.Append("\r\n\r\n");
548
549 HeaderBin = Encoding.ASCII.GetBytes(Header.ToString());
550 ms.Write(HeaderBin, 0, HeaderBin.Length);
551 ms.Write(Alternative.Raw, 0, Alternative.Raw.Length);
552 }
553
554
555 Header.Clear();
556 Header.Append("\r\n--");
557 Header.Append(Boundary);
558 Header.Append("--");
559
560 HeaderBin = Encoding.ASCII.GetBytes(Header.ToString());
561 ms.Write(HeaderBin, 0, HeaderBin.Length);
562
563 return ms.ToArray();
564 }
565 }
566 }
567}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:17
static KeyValuePair< string, string >[] ParseFieldValues(string Value)
Parses a set of comma or semicolon-separated field values, optionaly delimited by ' or " characters.
Definition: CommonTypes.cs:472
static bool TryParseRfc822(string s, out DateTimeOffset Value)
Parses a date and time value encoded according to RFC 822, §5.
Definition: CommonTypes.cs:170
static string GetString(byte[] Data, Encoding DefaultEncoding)
Gets a string from its binary representation, taking any Byte Order Mark (BOM) into account.
Static class managing encoding and decoding of internet content.
static Task< object > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri)
Decodes an object.
Represents content embedded in other content.
string FileName
Filename of embedded object.
ContentDisposition Disposition
Disposition of embedded object.
string Name
Name of embedded object.
string Description
Content-Description of embedded object, if defined.
string ContentType
Content-Type of embedded object.
byte[] Raw
Raw, untrasnformed body of embedded object.
string TransferEncoding
Content Transfer Encoding of embedded object, if defined. Affects how Raw is transformed into Transfe...
string ID
Content-ID of embedded object, if defined.
bool TryGetContentType(string FileExtension, out string ContentType)
Tries to get the content type of an item, given its file extension.
string[] FileExtensions
Supported file extensions.
bool TryGetFileExtension(string ContentType, out string FileExtension)
Tries to get the file extension of an item, given its Content-Type.
static async Task Decode(byte[] Data, KeyValuePair< string, string >[] Fields, Dictionary< string, object > Form, List< EmbeddedContent > List, Uri BaseUri)
Decodes a multipart object
bool Decodes(string ContentType, out Grade Grade)
If the decoder decodes an object with a given content type.
static async Task< byte[]> Encode(IEnumerable< EmbeddedContent > Content, string Boundary)
Encodes multi-part content
static bool TryTransferDecode(byte[] Encoded, string TransferEncoding, out byte[] Decoded)
Tries to decode transfer-encoded binary data.
const string ContentType
multipart/form-data
async Task< object > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri)
Decodes an object.
string[] ContentTypes
Supported content types.
static async Task< KeyValuePair< byte[], string > > Encode(IEnumerable< EmbeddedContent > Content)
Encodes multi-part form data
Plain text encoder/decoder.
const string DefaultContentType
text/plain
Basic interface for Internet Content decoders. A class implementing this interface and having a defau...
ContentDisposition
Content disposition
Grade
Grade enumeration
Definition: Grade.cs:7