mirror of
https://github.com/bitwarden/server.git
synced 2025-04-07 14:08:13 -05:00

* Extract Import-Api endpoints into separate controller Moved ciphers/import and ciphers/import-organization into new ImportController Paths have been kept intact for now (no changes on clients needed) Moved request-models used for import into tools-subfolder * Update CODEOWNERS for team-tools-dev * Move HibpController (reports) to tools * Moving files related to Send * Moving files related to ReferenceEvent * Removed unneeded newline
151 lines
5.9 KiB
C#
151 lines
5.9 KiB
C#
using System.Text.Json;
|
|
using Bit.Api.Tools.Models.Request;
|
|
using Microsoft.AspNetCore.Http.Features;
|
|
using Microsoft.AspNetCore.WebUtilities;
|
|
using Microsoft.Extensions.Primitives;
|
|
using Microsoft.Net.Http.Headers;
|
|
|
|
namespace Bit.Api.Utilities;
|
|
|
|
public static class MultipartFormDataHelper
|
|
{
|
|
private static readonly FormOptions _defaultFormOptions = new FormOptions();
|
|
|
|
public static async Task GetFileAsync(this HttpRequest request, Func<Stream, string, string, Task> callback)
|
|
{
|
|
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
|
|
_defaultFormOptions.MultipartBoundaryLengthLimit);
|
|
var reader = new MultipartReader(boundary, request.Body);
|
|
|
|
var firstSection = await reader.ReadNextSectionAsync();
|
|
if (firstSection != null)
|
|
{
|
|
if (ContentDispositionHeaderValue.TryParse(firstSection.ContentDisposition, out var firstContent))
|
|
{
|
|
if (HasFileContentDisposition(firstContent))
|
|
{
|
|
// Old style with just data
|
|
var fileName = HeaderUtilities.RemoveQuotes(firstContent.FileName).ToString();
|
|
using (firstSection.Body)
|
|
{
|
|
await callback(firstSection.Body, fileName, null);
|
|
}
|
|
}
|
|
else if (HasDispositionName(firstContent, "key"))
|
|
{
|
|
// New style with key, then data
|
|
string key = null;
|
|
using (var sr = new StreamReader(firstSection.Body))
|
|
{
|
|
key = await sr.ReadToEndAsync();
|
|
}
|
|
|
|
var secondSection = await reader.ReadNextSectionAsync();
|
|
if (secondSection != null)
|
|
{
|
|
if (ContentDispositionHeaderValue.TryParse(secondSection.ContentDisposition,
|
|
out var secondContent) && HasFileContentDisposition(secondContent))
|
|
{
|
|
var fileName = HeaderUtilities.RemoveQuotes(secondContent.FileName).ToString();
|
|
using (secondSection.Body)
|
|
{
|
|
await callback(secondSection.Body, fileName, key);
|
|
}
|
|
}
|
|
|
|
secondSection = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
firstSection = null;
|
|
}
|
|
}
|
|
|
|
public static async Task GetSendFileAsync(this HttpRequest request, Func<Stream, string,
|
|
SendRequestModel, Task> callback)
|
|
{
|
|
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
|
|
_defaultFormOptions.MultipartBoundaryLengthLimit);
|
|
var reader = new MultipartReader(boundary, request.Body);
|
|
|
|
var firstSection = await reader.ReadNextSectionAsync();
|
|
if (firstSection != null)
|
|
{
|
|
if (ContentDispositionHeaderValue.TryParse(firstSection.ContentDisposition, out _))
|
|
{
|
|
var secondSection = await reader.ReadNextSectionAsync();
|
|
if (secondSection != null)
|
|
{
|
|
if (ContentDispositionHeaderValue.TryParse(secondSection.ContentDisposition,
|
|
out var secondContent) && HasFileContentDisposition(secondContent))
|
|
{
|
|
var fileName = HeaderUtilities.RemoveQuotes(secondContent.FileName).ToString();
|
|
using (secondSection.Body)
|
|
{
|
|
var model = await JsonSerializer.DeserializeAsync<SendRequestModel>(firstSection.Body);
|
|
await callback(secondSection.Body, fileName, model);
|
|
}
|
|
}
|
|
|
|
secondSection = null;
|
|
}
|
|
|
|
}
|
|
|
|
firstSection = null;
|
|
}
|
|
}
|
|
|
|
public static async Task GetFileAsync(this HttpRequest request, Func<Stream, Task> callback)
|
|
{
|
|
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
|
|
_defaultFormOptions.MultipartBoundaryLengthLimit);
|
|
var reader = new MultipartReader(boundary, request.Body);
|
|
|
|
var dataSection = await reader.ReadNextSectionAsync();
|
|
if (dataSection != null)
|
|
{
|
|
if (ContentDispositionHeaderValue.TryParse(dataSection.ContentDisposition, out var dataContent)
|
|
&& HasFileContentDisposition(dataContent))
|
|
{
|
|
using (dataSection.Body)
|
|
{
|
|
await callback(dataSection.Body);
|
|
}
|
|
}
|
|
dataSection = null;
|
|
}
|
|
}
|
|
|
|
|
|
private static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
|
|
{
|
|
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary);
|
|
if (StringSegment.IsNullOrEmpty(boundary))
|
|
{
|
|
throw new InvalidDataException("Missing content-type boundary.");
|
|
}
|
|
|
|
if (boundary.Length > lengthLimit)
|
|
{
|
|
throw new InvalidDataException($"Multipart boundary length limit {lengthLimit} exceeded.");
|
|
}
|
|
|
|
return boundary.ToString();
|
|
}
|
|
|
|
private static bool HasFileContentDisposition(ContentDispositionHeaderValue content)
|
|
{
|
|
// Content-Disposition: form-data; name="data"; filename="Misc 002.jpg"
|
|
return content != null && content.DispositionType.Equals("form-data") &&
|
|
(!StringSegment.IsNullOrEmpty(content.FileName) || !StringSegment.IsNullOrEmpty(content.FileNameStar));
|
|
}
|
|
|
|
private static bool HasDispositionName(ContentDispositionHeaderValue content, string name)
|
|
{
|
|
// Content-Disposition: form-data; name="key";
|
|
return content != null && content.DispositionType.Equals("form-data") && content.Name == name;
|
|
}
|
|
}
|