Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="..\WindowsDevicePortalWrapper\HttpRest\HttpMultipartFileContent.cs">
<Link>WDPMockImplementations\HttpMultipartFileContent.cs</Link>
</Compile>
<Compile Include="BaseTests.cs" />
<Compile Include="Core\AppFileExplorerTests.cs" />
<Compile Include="Core\EtwTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,25 @@ public partial class DevicePortal
/// <param name="requestStream">Optional stream containing data for the request body.</param>
/// <param name="requestStreamContentType">The type of that request body data.</param>
/// <returns>Task tracking the completion of the POST request</returns>
private async Task<Stream> PostAsync(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does removing async here create any issues with calling code?

private Task<Stream> PostAsync(
Uri uri,
Stream requestStream = null,
string requestStreamContentType = null)
{
StreamContent requestContent = null;
MemoryStream dataStream = null;

if (requestStream != null)
{
requestContent = new StreamContent(requestStream);
requestContent.Headers.Remove(ContentTypeHeaderName);
requestContent.Headers.TryAddWithoutValidation(ContentTypeHeaderName, requestStreamContentType);
}

return PostAsync(uri, requestContent);
}
private async Task<Stream> PostAsync(
Uri uri,
HttpContent requestContent)
{
MemoryStream dataStream = null;
WebRequestHandler requestSettings = new WebRequestHandler();
requestSettings.UseDefaultCredentials = false;
requestSettings.Credentials = this.deviceConnection.Credentials;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#if WINDOWS_UWP
using Windows.Foundation;
using Windows.Security.Credentials;
using Windows.Storage.Streams;
using Windows.Web.Http;
using Windows.Web.Http.Filters;
using Windows.Web.Http.Headers;
Expand Down Expand Up @@ -110,7 +111,7 @@ public async Task InstallApplicationAsync(
}
}
}

// Create the API endpoint and generate a unique boundary string.
Uri uri;
string boundaryString;
Expand All @@ -119,59 +120,17 @@ public async Task InstallApplicationAsync(
out uri,
out boundaryString);

using (MemoryStream dataStream = new MemoryStream())
{
byte[] data;

// Copy the application package.
installPhaseDescription = string.Format("Copying: {0}", packageFile.Name);
this.SendAppInstallStatus(
ApplicationInstallStatus.InProgress,
ApplicationInstallPhase.CopyingFile,
installPhaseDescription);
data = Encoding.ASCII.GetBytes(string.Format("--{0}\r\n", boundaryString));
dataStream.Write(data, 0, data.Length);
CopyFileToRequestStream(packageFile, dataStream);

// Copy dependency files, if any.
foreach (string dependencyFile in dependencyFileNames)
{
FileInfo fi = new FileInfo(dependencyFile);
installPhaseDescription = string.Format("Copying: {0}", fi.Name);
this.SendAppInstallStatus(
ApplicationInstallStatus.InProgress,
ApplicationInstallPhase.CopyingFile,
installPhaseDescription);
data = Encoding.ASCII.GetBytes(string.Format("\r\n--{0}\r\n", boundaryString));
dataStream.Write(data, 0, data.Length);
CopyFileToRequestStream(fi, dataStream);
}

// Copy the certificate file, if provided.
if (!string.IsNullOrEmpty(certificateFileName))
{
FileInfo fi = new FileInfo(certificateFileName);
installPhaseDescription = string.Format("Copying: {0}", fi.Name);
this.SendAppInstallStatus(
ApplicationInstallStatus.InProgress,
ApplicationInstallPhase.CopyingFile,
installPhaseDescription);
data = Encoding.ASCII.GetBytes(string.Format("\r\n--{0}\r\n", boundaryString));
dataStream.Write(data, 0, data.Length);
CopyFileToRequestStream(fi, dataStream);
}

// Close the installation request data.
data = Encoding.ASCII.GetBytes(string.Format("\r\n--{0}--\r\n", boundaryString));
dataStream.Write(data, 0, data.Length);

dataStream.Position = 0;

string contentType = string.Format("multipart/form-data; boundary={0}", boundaryString);
installPhaseDescription = string.Format("Copying: {0}", packageFile.Name);
this.SendAppInstallStatus(
ApplicationInstallStatus.InProgress,
ApplicationInstallPhase.CopyingFile,
installPhaseDescription);

// Make the HTTP request.
await this.PostAsync(uri, dataStream, contentType);
}
var content = new HttpMultipartFileContent();
content.Add(packageFile.FullName);
content.AddRange(dependencyFileNames);
content.Add(certificateFileName);
await this.PostAsync(uri, content);

// Poll the status until complete.
ApplicationInstallStatus status = ApplicationInstallStatus.InProgress;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage.Streams;
using Windows.Web.Http;
using Windows.Web.Http.Headers;

namespace Microsoft.Tools.WindowsDevicePortal
{
/// <summary>
/// This class mimicks <see cref="HttpMultipartContent"/>, with two main differences
/// 1. Simplifies posting files by taking file names instead of managing streams.
/// 2. Does not quote the boundaries, due to a bug in the device portal:
/// https://insider.windows.com/FeedbackHub/fb?contextid=519&feedbackid=19a5af49-38f4-409a-b464-e66f80679545&form=1
/// </summary>
internal sealed class HttpMultipartFileContent : IHttpContent
{
private List<string> items = new List<string>();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throughout, please add comment blocks similar to the other classes in the code :)

private string boundaryString;

public HttpMultipartFileContent() : this(Guid.NewGuid().ToString()) { }

public HttpMultipartFileContent(string boundary)
{
boundaryString = boundary;
Headers.ContentType = new HttpMediaTypeHeaderValue(string.Format("multipart/form-data; boundary={0}", boundaryString));
}

public void Add(string filename)
{
if (filename != null)
items.Add(filename);
}

public void AddRange(IEnumerable<string> filenames)
{
if (filenames != null)
items.AddRange(filenames);
}

public HttpContentHeaderCollection Headers { get; } = new HttpContentHeaderCollection();

IAsyncOperationWithProgress<ulong, ulong> IHttpContent.BufferAllAsync()
{
throw new NotImplementedException();
}

void IDisposable.Dispose()
{
items.Clear();
}

IAsyncOperationWithProgress<IBuffer, ulong> IHttpContent.ReadAsBufferAsync()
{
throw new NotImplementedException();
}

IAsyncOperationWithProgress<IInputStream, ulong> IHttpContent.ReadAsInputStreamAsync()
{
throw new NotImplementedException();
}

IAsyncOperationWithProgress<string, ulong> IHttpContent.ReadAsStringAsync()
{
throw new NotImplementedException();
}

bool IHttpContent.TryComputeLength(out ulong length)
{
length = 0;
var boundaryLength = Encoding.ASCII.GetBytes(string.Format("--{0}\r\n", boundaryString)).Length;
foreach (var item in items)
{
var headerdata = GetFileHeader(new FileInfo(item));
length += (ulong)(boundaryLength + headerdata.Length + new FileInfo(item).Length + 2);
}
length += (ulong)(boundaryLength + 2);
return true;
}

IAsyncOperationWithProgress<ulong, ulong> IHttpContent.WriteToStreamAsync(IOutputStream outputStream)
{
return System.Runtime.InteropServices.WindowsRuntime.AsyncInfo.Run<ulong, ulong>((token, progress) =>
{
return WriteToStreamAsyncTask(outputStream, (ulong p) => progress.Report(p));
});
}

private async Task<ulong> WriteToStreamAsyncTask(IOutputStream outputStream, Action<ulong> progress)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue #228 will necessitate reworking this for the UWP version of WDPW.

{
ulong bytesWritten = 0;
var outStream = outputStream.AsStreamForWrite();
var boundary = Encoding.ASCII.GetBytes($"--{boundaryString}\r\n");
var newline = Encoding.ASCII.GetBytes("\r\n");
foreach (var item in items)
{
outStream.Write(boundary, 0, boundary.Length);
bytesWritten += (ulong)boundary.Length;
var headerdata = GetFileHeader(new FileInfo(item));
outStream.Write(headerdata, 0, headerdata.Length);
bytesWritten += (ulong)headerdata.Length;
using (var file = File.OpenRead(item))
{
await file.CopyToAsync(outStream);
bytesWritten += (ulong)file.Position;
}
outStream.Write(newline, 0, newline.Length);
bytesWritten += (ulong)newline.Length;
await outStream.FlushAsync();
progress(bytesWritten);
}
// Close the installation request data.
boundary = Encoding.ASCII.GetBytes($"--{boundaryString}--\r\n");
outStream.Write(boundary, 0, boundary.Length);
await outStream.FlushAsync();
bytesWritten += (ulong)boundary.Length;
return bytesWritten;
}
private static byte[] GetFileHeader(FileInfo info)
{
string contentType = "application/octet-stream";
if (info.Extension.ToLower() == ".cer")
contentType = "application/x-x509-ca-cert";

return Encoding.ASCII.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{0}\"\r\nContent-Type: {1}\r\n\r\n", info.Name, contentType));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,26 @@ public partial class DevicePortal
/// <param name="requestStreamContentType">The type of that request body data.</param>
/// <returns>Task tracking the completion of the POST request</returns>
#pragma warning disable 1998
private async Task<Stream> PostAsync(
private Task<Stream> PostAsync(
Uri uri,
Stream requestStream = null,
string requestStreamContentType = null)
{
HttpStreamContent requestContent = null;
IBuffer dataBuffer = null;


if (requestStream != null)
{
requestContent = new HttpStreamContent(requestStream.AsInputStream());
requestContent.Headers.Remove(ContentTypeHeaderName);
requestContent.Headers.TryAppendWithoutValidation(ContentTypeHeaderName, requestStreamContentType);
}

return PostAsync(uri, requestContent);
}
private async Task<Stream> PostAsync(
Uri uri,
IHttpContent requestContent)
{
IBuffer dataBuffer = null;
HttpBaseProtocolFilter httpFilter = new HttpBaseProtocolFilter();
httpFilter.AllowUI = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
<Compile Include="CertificateHandling.cs" />
<Compile Include="Core\AppDeployment.cs" />
<Compile Include="DefaultDevicePortalConnection.cs" />
<Compile Include="HttpRest\HttpMultipartFileContent.cs" />
<Compile Include="HttpRest\RestPost.cs" />
<Compile Include="HttpRest\RestPut.cs" />
<Compile Include="HttpRest\RestDelete.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Globalization;
using System.Diagnostics;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.Tools.WindowsDevicePortal
{
/// <summary>
/// This class mimicks <see cref="HttpMultipartContent"/>, with two main differences
/// 1. Simplifies posting files by taking file names instead of managing streams.
/// 2. Does not quote the boundaries, due to a bug in the device portal:
/// https://insider.windows.com/FeedbackHub/fb?contextid=519&feedbackid=19a5af49-38f4-409a-b464-e66f80679545&form=1
/// </summary>
internal sealed class HttpMultipartFileContent : HttpContent
{
private List<string> items = new List<string>();
private string boundaryString;

public HttpMultipartFileContent() : this(Guid.NewGuid().ToString()) { }

public HttpMultipartFileContent(string boundary)
{
boundaryString = boundary;
Headers.TryAddWithoutValidation("Content-Type", string.Format("multipart/form-data; boundary={0}", boundaryString));
}

public void Add(string filename)
{
if (filename != null)
items.Add(filename);
}

public void AddRange(IEnumerable<string> filenames)
{
if (filenames != null)
items.AddRange(filenames);
}

protected override async Task SerializeToStreamAsync(Stream outStream, TransportContext context)
{
var boundary = Encoding.ASCII.GetBytes($"--{boundaryString}\r\n");
var newline = Encoding.ASCII.GetBytes("\r\n");
foreach (var item in items)
{
outStream.Write(boundary, 0, boundary.Length);
var headerdata = GetFileHeader(new FileInfo(item));
outStream.Write(headerdata, 0, headerdata.Length);

using (var file = File.OpenRead(item))
{
await file.CopyToAsync(outStream);
}
outStream.Write(newline, 0, newline.Length);
await outStream.FlushAsync();
}
// Close the installation request data.
boundary = Encoding.ASCII.GetBytes($"--{boundaryString}--\r\n");
outStream.Write(boundary, 0, boundary.Length);
await outStream.FlushAsync();
}

protected override bool TryComputeLength(out long length)
{
length = 0;
var boundaryLength = Encoding.ASCII.GetBytes(string.Format("--{0}\r\n", boundaryString)).Length;
foreach (var item in items)
{
var headerdata = GetFileHeader(new FileInfo(item));
length += boundaryLength + headerdata.Length + new FileInfo(item).Length + 2;
}
length += (boundaryLength + 2);
return true;
}
private static byte[] GetFileHeader(FileInfo info)
{
string contentType = "application/octet-stream";
if (info.Extension.ToLower() == ".cer")
contentType = "application/x-x509-ca-cert";

return Encoding.ASCII.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{0}\"\r\nContent-Type: {1}\r\n\r\n", info.Name, contentType));
}

}
}
Loading