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;
3using System.IO;
4using System.Text;
5using System.Threading.Tasks;
10
12{
19 {
23 public const string ContentType = "multipart/form-data";
24
31 {
32 }
33
37 public string[] ContentTypes => contentTypes;
38
39 private static readonly string[] contentTypes = new string[] { ContentType };
40
44 public string[] FileExtensions => new string[] { "formdata" };
45
52 public bool Decodes(string ContentType, out Grade Grade)
53 {
55 {
56 Grade = Grade.Excellent;
57 return true;
58 }
59 else
60 {
61 Grade = Grade.NotAtAll;
62 return false;
63 }
64 }
65
76 public async Task<ContentResponse> DecodeAsync(string ContentType, byte[] Data, Encoding Encoding,
77 KeyValuePair<string, string>[] Fields, Uri BaseUri, ICodecProgress Progress)
78 {
79 Dictionary<string, object> Form = new Dictionary<string, object>();
80
81 Exception Error = await Decode(Data, Fields, Form, null, BaseUri, Progress);
82
83 if (Error is null)
84 return new ContentResponse(ContentType, Form, Data);
85 else
86 return new ContentResponse(Error);
87 }
88
99 public static async Task<Exception> Decode(byte[] Data, KeyValuePair<string, string>[] Fields, Dictionary<string, object> Form,
100 ChunkedList<EmbeddedContent> List, Uri BaseUri, ICodecProgress Progress)
101 {
102 string Boundary = null;
103
104 if (!(Fields is null))
105 {
106 foreach (KeyValuePair<string, string> P in Fields)
107 {
108 if (string.Compare(P.Key, "BOUNDARY", true) == 0)
109 {
110 Boundary = P.Value;
111 break;
112 }
113 }
114 }
115
116 if (string.IsNullOrEmpty(Boundary))
117 return new Exception("No boundary defined.");
118
119 Exception Error;
120 byte[] BoundaryBin = Encoding.ASCII.GetBytes(Boundary);
121 int Start = 0;
122 int i = 0;
123 int c = Data.Length;
124 int d = BoundaryBin.Length;
125 int j;
126 int Max = c - d;
127
128 while (i < Max)
129 {
130 for (j = 0; j < d; j++)
131 {
132 if (Data[i + j] != BoundaryBin[j])
133 break;
134 }
135
136 if (j == d)
137 {
138 Error = await AddPart(Data, Start, i, false, Form, List, BaseUri, Progress);
139 if (!(Error is null))
140 return Error;
141
142 i += d;
143 while (i < c && Data[i] <= 32)
144 i++;
145
146 Start = i;
147 }
148 else
149 i++;
150 }
151
152 if (Start < c)
153 {
154 Error = await AddPart(Data, Start, c, true, Form, List, BaseUri, Progress);
155 if (!(Error is null))
156 return Error;
157 }
158
159 return null;
160 }
161
162 private static async Task<Exception> AddPart(byte[] Data, int Start, int i, bool Last,
163 Dictionary<string, object> Form, ChunkedList<EmbeddedContent> List, Uri BaseUri, ICodecProgress Progress)
164 {
165 int j, k, l, m;
166 int Max = i - 3;
167
168 for (j = Start; j < Max; j++)
169 {
170 if (Data[j] == '\r' && Data[j + 1] == '\n' && Data[j + 2] == '\r' && Data[j + 3] == '\n')
171 break;
172 }
173
174 if (j == Start)
175 return null;
176
177 if (j < i)
178 {
179 k = 0;
180 if (i >= 2 && Data[i - 1] == '-' && Data[i - 2] == '-')
181 {
182 k = 2;
183
184 if (i >= 4 && Data[i - 1 - k] == '\n' && Data[i - 2 - k] == '\r')
185 k += 2;
186 }
187
188 int NrBytes = i - j - 4 - k;
189 if (NrBytes < 0 || (NrBytes == 0 && Last))
190 return null;
191
192 string Header = Encoding.ASCII.GetString(Data, Start, j - Start);
193 string Key, Value;
194 byte[] Data2 = new byte[NrBytes];
195 EmbeddedContent EmbeddedContent = new EmbeddedContent()
196 {
198 Raw = Data2
199 };
200
201 Buffer.BlockCopy(Data, j + 4, Data2, 0, NrBytes);
202
203 string[] Rows = Header.Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries);
204 l = Rows.Length;
205 m = -1;
206
207 for (j = 0; j < l; j++)
208 {
209 Key = Rows[j];
210 if (!string.IsNullOrEmpty(Key))
211 {
212 if (char.IsWhiteSpace(Key[0]) && m >= 0)
213 {
214 Rows[m] += Key;
215 Rows[j] = string.Empty;
216 }
217 else
218 m = j;
219 }
220 }
221
222 foreach (string Row in Rows)
223 {
224 j = Row.IndexOf(':');
225 if (j < 0)
226 continue;
227
228 Key = Row.Substring(0, j).Trim();
229 Value = Row.Substring(j + 1).Trim();
230
231 switch (Key.ToUpper())
232 {
233 case "CONTENT-TYPE":
234 EmbeddedContent.ContentType = Value;
235 j = Value.IndexOf(';');
236 if (j >= 0)
237 {
238 ParseContentFields(Value.Substring(j + 1).Trim(), EmbeddedContent);
239 Value = Value.Substring(0, j).Trim();
240 }
241 break;
242
243 case "CONTENT-DISPOSITION":
244 j = Value.IndexOf(';');
245 if (j >= 0)
246 {
247 ParseContentFields(Value.Substring(j + 1).Trim(), EmbeddedContent);
248 Value = Value.Substring(0, j).Trim();
249 }
250
251 switch (Value.ToUpper())
252 {
253 case "INLINE":
254 EmbeddedContent.Disposition = ContentDisposition.Inline;
255 break;
256
257 case "ATTACHMENT":
258 EmbeddedContent.Disposition = ContentDisposition.Attachment;
259 break;
260
261 case "FORM-DATA":
262 EmbeddedContent.Disposition = ContentDisposition.FormData;
263 break;
264 }
265 break;
266
267 case "CONTENT-TRANSFER-ENCODING":
268 EmbeddedContent.TransferEncoding = Value;
269 break;
270
271 case "CONTENT-ID":
272 EmbeddedContent.ID = Value;
273 break;
274
275 case "CONTENT-DESCRIPTION":
276 EmbeddedContent.Description = Value;
277 break;
278 }
279 }
280
281 if (!string.IsNullOrEmpty(EmbeddedContent.TransferEncoding))
282 {
283 if (TryTransferDecode(Data2, EmbeddedContent.TransferEncoding, out Data2))
284 EmbeddedContent.TransferDecoded = Data2;
285 else
286 return new Exception("Unrecognized Content-Transfer-Encoding: " + EmbeddedContent.TransferEncoding);
287 }
288
289 try
290 {
291 ContentResponse Item = await InternetContent.DecodeAsync(EmbeddedContent.ContentType, Data2, BaseUri, Progress);
292 if (Item.HasError)
293 EmbeddedContent.Decoded = Data2;
294 else
295 EmbeddedContent.Decoded = Item.Decoded;
296 }
297 catch (Exception)
298 {
299 EmbeddedContent.Decoded = Data2;
300 }
301
302 if (!(Form is null))
303 {
304 Form[EmbeddedContent.Name] = EmbeddedContent.Decoded;
305 Form[EmbeddedContent.Name + "_Binary"] = Data2;
306
307 if (!string.IsNullOrEmpty(EmbeddedContent.ContentType))
308 Form[EmbeddedContent.Name + "_ContentType"] = EmbeddedContent.ContentType;
309
310 if (!string.IsNullOrEmpty(EmbeddedContent.FileName))
311 Form[EmbeddedContent.Name + "_FileName"] = EmbeddedContent.FileName;
312 }
313
314 List?.Add(EmbeddedContent);
315 }
316
317 return null;
318 }
319
320 private static void ParseContentFields(string s, EmbeddedContent EmbeddedContent)
321 {
322 foreach (KeyValuePair<string, string> Field in CommonTypes.ParseFieldValues(s))
323 {
324 switch (Field.Key.ToUpper())
325 {
326 case "NAME":
327 EmbeddedContent.Name = Field.Value;
328 break;
329
330 case "FILENAME":
331 EmbeddedContent.FileName = Field.Value;
332 break;
333
334 case "SIZE":
335 if (int.TryParse(Field.Value, out int i))
336 EmbeddedContent.Size = i;
337 break;
338
339 case "CREATION-DATE":
340 if (CommonTypes.TryParseRfc822(Field.Value, out DateTimeOffset DTO))
341 EmbeddedContent.CreationDate = DTO;
342 break;
343
344 case "MODIFICATION-DATE":
345 if (CommonTypes.TryParseRfc822(Field.Value, out DTO))
346 EmbeddedContent.ModificationDate = DTO;
347 break;
348 }
349 }
350 }
351
359 public static bool TryTransferDecode(byte[] Encoded, string TransferEncoding, out byte[] Decoded)
360 {
361 switch (TransferEncoding.ToUpper())
362 {
363 case "7BIT":
364 case "8BIT":
365 case "BINARY":
366 case "":
367 case null:
368 Decoded = Encoded;
369 return true;
370
371 case "BASE64":
372 string s = Strings.GetString(Encoded, Encoding.ASCII);
373 Decoded = Convert.FromBase64String(s);
374 return true;
375
376 case "QUOTED-PRINTABLE":
377 MemoryStream ms = new MemoryStream();
378 byte b;
379 char ch;
380 int j, k;
381
382 for (j = 0, k = Encoded.Length; j < k; j++)
383 {
384 b = Encoded[j];
385
386 if (b == (byte)'=' && j + 2 < k)
387 {
388 ch = (char)Encoded[++j];
389
390 if (ch >= '0' && ch <= '9')
391 b = (byte)(ch - '0');
392 else if (ch >= 'a' && ch <= 'f')
393 b = (byte)(ch - 'a' + 10);
394 else if (ch >= 'A' && ch <= 'F')
395 b = (byte)(ch - 'A' + 10);
396 else if (ch == '\r')
397 {
398 if (Encoded[j + 1] == (byte)'\n')
399 j++;
400
401 continue;
402 }
403 else
404 {
405 ms.WriteByte((byte)'=');
406 ms.WriteByte((byte)ch);
407 continue;
408 }
409
410 b <<= 4;
411
412 ch = (char)Encoded[++j];
413
414 if (ch >= '0' && ch <= '9')
415 b |= (byte)(ch - '0');
416 else if (ch >= 'a' && ch <= 'f')
417 b |= (byte)(ch - 'a' + 10);
418 else if (ch >= 'A' && ch <= 'F')
419 b |= (byte)(ch - 'A' + 10);
420 }
421
422 ms.WriteByte(b);
423 }
424
425 Decoded = ms.ToArray();
426 ms.Dispose();
427 return true;
428
429 default:
430 Decoded = null;
431 return false;
432 }
433 }
434
441 public bool TryGetContentType(string FileExtension, out string ContentType)
442 {
443 if (string.Compare(FileExtension, "formdata", true) == 0)
444 {
446 return true;
447 }
448 else
449 {
450 ContentType = string.Empty;
451 return false;
452 }
453 }
454
461 public bool TryGetFileExtension(string ContentType, out string FileExtension)
462 {
463 switch (ContentType.ToLower())
464 {
466 FileExtension = "formdata";
467 return true;
468
469 default:
470 FileExtension = string.Empty;
471 return false;
472 }
473 }
474
481 public static async Task<KeyValuePair<byte[], string>> Encode(IEnumerable<EmbeddedContent> Content,
482 ICodecProgress Progress)
483 {
484 string Boundary = Guid.NewGuid().ToString();
485 string ContentType = FormDataDecoder.ContentType + "; boundary=\"" + Boundary + "\"";
486 return new KeyValuePair<byte[], string>(await Encode(Content, Boundary, Progress), ContentType);
487 }
488
496 public static async Task<byte[]> Encode(IEnumerable<EmbeddedContent> Content, string Boundary,
497 ICodecProgress Progress)
498 {
499 using (MemoryStream ms = new MemoryStream())
500 {
501 StringBuilder Header = new StringBuilder();
502 byte[] HeaderBin;
503
504 foreach (EmbeddedContent Alternative in Content)
505 {
506 await Alternative.AssertEncoded();
507
508 Header.Clear();
509 Header.Append("\r\n--");
510 Header.Append(Boundary);
511
512 if (!string.IsNullOrEmpty(Alternative.TransferEncoding))
513 {
514 Header.Append("\r\nContent-Transfer-Encoding: ");
515 Header.Append(Alternative.TransferEncoding);
516 }
517
518 Header.Append("\r\nContent-Type: ");
519 Header.Append(Alternative.ContentType);
520
521 if (!string.IsNullOrEmpty(Alternative.Name) && Alternative.Disposition != ContentDisposition.FormData)
522 {
523 Header.Append("; name=\"");
524 Header.Append(Alternative.Name.Replace("\"", "\\\""));
525 Header.Append('"');
526 }
527
528 if (Alternative.Disposition != ContentDisposition.Unknown ||
529 !string.IsNullOrEmpty(Alternative.FileName))
530 {
531 Header.Append("\r\nContent-Disposition: ");
532
533 switch (Alternative.Disposition)
534 {
535 case ContentDisposition.FormData:
536 Header.Append("form-data");
537
538 if (!string.IsNullOrEmpty(Alternative.Name))
539 {
540 Header.Append("; name=\"");
541 Header.Append(Alternative.Name.Replace("\"", "\\\""));
542 Header.Append('"');
543 }
544 break;
545
546 case ContentDisposition.Inline:
547 Header.Append("inline");
548 break;
549
550 case ContentDisposition.Attachment:
551 default:
552 Header.Append("attachment");
553 break;
554 }
555
556 if (!string.IsNullOrEmpty(Alternative.FileName))
557 {
558 Header.Append("; filename=\"");
559 Header.Append(Alternative.FileName.Replace("\"", "\\\""));
560 Header.Append('"');
561 }
562 }
563
564 if (!string.IsNullOrEmpty(Alternative.ID))
565 {
566 Header.Append("\r\nContent-ID: ");
567 Header.Append(Alternative.ID);
568 }
569
570 if (!string.IsNullOrEmpty(Alternative.Description))
571 {
572 Header.Append("\r\nContent-Description: ");
573 Header.Append(Alternative.Description);
574 }
575
576 Header.Append("\r\n\r\n");
577
578 HeaderBin = Encoding.ASCII.GetBytes(Header.ToString());
579 ms.Write(HeaderBin, 0, HeaderBin.Length);
580 ms.Write(Alternative.Raw, 0, Alternative.Raw.Length);
581 }
582
583
584 Header.Clear();
585 Header.Append("\r\n--");
586 Header.Append(Boundary);
587 Header.Append("--");
588
589 HeaderBin = Encoding.ASCII.GetBytes(Header.ToString());
590 ms.Write(HeaderBin, 0, HeaderBin.Length);
591
592 return ms.ToArray();
593 }
594 }
595 }
596}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:19
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:474
static bool TryParseRfc822(string s, out DateTimeOffset Value)
Parses a date and time value encoded according to RFC 822, §5.
Definition: CommonTypes.cs:172
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > 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, untransformed 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.
static async Task< KeyValuePair< byte[], string > > Encode(IEnumerable< EmbeddedContent > Content, ICodecProgress Progress)
Encodes multi-part form data
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< Exception > Decode(byte[] Data, KeyValuePair< string, string >[] Fields, Dictionary< string, object > Form, ChunkedList< EmbeddedContent > List, Uri BaseUri, ICodecProgress Progress)
Decodes a multipart object
static async Task< byte[]> Encode(IEnumerable< EmbeddedContent > Content, string Boundary, ICodecProgress Progress)
Encodes multi-part content
bool Decodes(string ContentType, out Grade Grade)
If the decoder decodes an object with a given content type.
async Task< ContentResponse > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri, ICodecProgress Progress)
Decodes an object.
static bool TryTransferDecode(byte[] Encoded, string TransferEncoding, out byte[] Decoded)
Tries to decode transfer-encoded binary data.
const string ContentType
multipart/form-data
string[] ContentTypes
Supported content types.
Plain text encoder/decoder.
const string DefaultContentType
text/plain
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
Static class managing binary representations of strings.
Definition: Strings.cs:10
static string GetString(byte[] Data, int Offset, int Count, Encoding DefaultEncoding)
Gets a string from its binary representation, taking any Byte Order Mark (BOM) into account.
Definition: Strings.cs:148
Interface for reporting progress about an encoding or decoding.
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