Line data Source code
1 : using System;
2 : using System.IO;
3 : using System.Linq;
4 : using System.Net.Http;
5 : using System.Net.Http.Headers;
6 : using System.Threading;
7 : using System.Threading.Tasks;
8 : using Cqrs.WebApi.Formatters.FormMultipart.Infrastructure;
9 :
10 : namespace Cqrs.WebApi.Formatters.FormMultipart.Converters
11 : {
12 : public class HttpContentToFormDataConverter
13 0 : {
14 0 : public async Task<FormData> Convert(HttpContent content)
15 : {
16 : if(content == null)
17 : throw new ArgumentNullException("content");
18 :
19 : //commented to provide more details about incorrectly formatted data from ReadAsMultipartAsync method
20 : /*if (!content.IsMimeMultipartContent())
21 : {
22 : throw new Exception("Unsupported Media Type");
23 : }*/
24 :
25 : //http://stackoverflow.com/questions/15201255/request-content-readasmultipartasync-never-returns
26 : MultipartMemoryStreamProvider multipartProvider = null;
27 :
28 : await Task.Factory
29 : .StartNewSafely(() =>
30 : {
31 : try
32 : {
33 : multipartProvider = content.ReadAsMultipartAsync().Result;
34 : }
35 : catch (AggregateException aggregateException)
36 : {
37 : if (aggregateException.InnerExceptions.Count != 1)
38 : throw;
39 : var exception = aggregateException.InnerExceptions.Single() as IOException;
40 : if (exception == null || exception.Message != @"Unexpected end of MIME multipart stream. MIME multipart message is not complete.")
41 : throw;
42 :
43 : Stream reqStream = content.ReadAsStreamAsync().Result;
44 : MemoryStream tempStream = new MemoryStream();
45 : reqStream.CopyTo(tempStream);
46 :
47 : tempStream.Seek(0, SeekOrigin.End);
48 : StreamWriter writer = new StreamWriter(tempStream);
49 : writer.WriteLine();
50 : writer.Flush();
51 : tempStream.Position = 0;
52 :
53 :
54 : StreamContent streamContent = new StreamContent(tempStream);
55 : foreach (var header in content.Headers)
56 : streamContent.Headers.Add(header.Key, header.Value);
57 :
58 : // Read the form data and return an async task.
59 : multipartProvider = streamContent.ReadAsMultipartAsync().Result;
60 : }
61 : },
62 : CancellationToken.None,
63 : TaskCreationOptions.LongRunning, // guarantees separate thread
64 : TaskScheduler.Default);
65 :
66 : var multipartFormData = await Convert(multipartProvider);
67 : return multipartFormData;
68 : }
69 :
70 0 : public async Task<FormData> Convert(MultipartMemoryStreamProvider multipartProvider)
71 : {
72 : var multipartFormData = new FormData();
73 :
74 : foreach (var file in multipartProvider.Contents.Where(x => IsFile(x.Headers.ContentDisposition)))
75 : {
76 : var name = UnquoteToken(file.Headers.ContentDisposition.Name);
77 : string fileName = FixFilename(file.Headers.ContentDisposition.FileName);
78 : string mediaType = file.Headers.ContentType.MediaType;
79 :
80 : using (var stream = await file.ReadAsStreamAsync())
81 : {
82 : byte[] buffer = ReadAllBytes(stream);
83 : if (buffer.Length > 0)
84 : {
85 : multipartFormData.Add(name, new HttpFile(fileName, mediaType, buffer));
86 : }
87 : }
88 : }
89 :
90 : foreach (var part in multipartProvider.Contents.Where(x => x.Headers.ContentDisposition.DispositionType == "form-data"
91 : && !IsFile(x.Headers.ContentDisposition)))
92 : {
93 : var name = UnquoteToken(part.Headers.ContentDisposition.Name);
94 : var data = await part.ReadAsStringAsync();
95 : multipartFormData.Add(name, data);
96 : }
97 :
98 : return multipartFormData;
99 : }
100 :
101 : private bool IsFile(ContentDispositionHeaderValue disposition)
102 : {
103 : return !string.IsNullOrEmpty(disposition.FileName);
104 : }
105 :
106 : /// <summary>
107 : /// Remove bounding quotes on a token if present
108 : /// </summary>
109 : private static string UnquoteToken(string token)
110 : {
111 : if (String.IsNullOrWhiteSpace(token))
112 : {
113 : return token;
114 : }
115 :
116 : if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1)
117 : {
118 : return token.Substring(1, token.Length - 2);
119 : }
120 :
121 : return token;
122 : }
123 :
124 : /// <summary>
125 : /// Amend filenames to remove surrounding quotes and remove path from IE
126 : /// </summary>
127 : private static string FixFilename(string originalFileName)
128 : {
129 : if (string.IsNullOrWhiteSpace(originalFileName))
130 : return string.Empty;
131 :
132 : var result = originalFileName.Trim();
133 :
134 : // remove leading and trailing quotes
135 : result = result.Trim('"');
136 :
137 : // remove full path versions
138 : if (result.Contains("\\"))
139 : result = Path.GetFileName(result);
140 :
141 : return result;
142 : }
143 :
144 : private byte[] ReadAllBytes(Stream input)
145 : {
146 : using (var stream = new MemoryStream())
147 : {
148 : input.CopyTo(stream);
149 : return stream.ToArray();
150 : }
151 : }
152 : }
153 : }
|