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