mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-03 06:53:32 +00:00
Merge pull request #157 from Sea-Haven-Industries/fix/ab/sh-381-mobile-video-mime
fix(work-orders): accept mobile media whose declared MIME is foreign (SH-381)
This commit is contained in:
commit
cd4d30af4b
2 changed files with 42 additions and 12 deletions
|
|
@ -32,17 +32,18 @@ namespace SeaHaven.Services.Helpers
|
||||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".docx" }
|
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".docx" }
|
||||||
};
|
};
|
||||||
|
|
||||||
// A browser fills the multipart part's Content-Type from File.type, which mobile
|
// The multipart part's Content-Type comes from the browser's File.type, which is
|
||||||
// browsers leave empty (or the client sends octet-stream) when the OS cannot
|
// unreliable on mobile: it is left empty or sent as application/octet-stream when the
|
||||||
// classify a picked file. Only then is the type resolved from the extension; the
|
// OS cannot classify a picked file, and is sometimes a foreign-but-plausible video
|
||||||
// category rule, extension pairing, and magic-byte signature still decide.
|
// type the OS attaches to a supported container (e.g. video/3gpp for an .mp4,
|
||||||
private static readonly HashSet<string> UndeterminedContentTypes =
|
// video/x-quicktime for a .mov). An allowlisted declared type stays authoritative and
|
||||||
new(StringComparer.OrdinalIgnoreCase) { string.Empty, "application/octet-stream" };
|
// pairs against its own extension; anything else falls back to the extension. The
|
||||||
|
// extension pairing and magic-byte signature below still decide, so this never widens
|
||||||
|
// the accepted set of files.
|
||||||
private static string? ResolveContentType(string declaredType, string extension)
|
private static string? ResolveContentType(string declaredType, string extension)
|
||||||
{
|
{
|
||||||
if (!UndeterminedContentTypes.Contains(declaredType))
|
if (AllowedContentTypes.Contains(declaredType))
|
||||||
return AllowedContentTypes.Contains(declaredType) ? declaredType : null;
|
return declaredType;
|
||||||
|
|
||||||
foreach (var (contentType, extensions) in ExtensionsByContentType)
|
foreach (var (contentType, extensions) in ExtensionsByContentType)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2084,11 +2084,40 @@ public class WorkOrderMediaFileRulesTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsAllowed_DeclaredMimeMismatchingExtension_StillRejected()
|
public void IsAllowed_AllowlistedDeclaredMimeMismatchingExtension_StillRejected()
|
||||||
{
|
{
|
||||||
// A concrete declared type is authoritative; only an undetermined one falls back to the extension.
|
// An allowlisted declared type stays authoritative and pairs against its own
|
||||||
|
// extension, so declaring video/mp4 for a .mov (or the reverse) is still a spoof.
|
||||||
Assert.False(WorkOrderMediaFileRules.IsAllowed(FormFile(RealMov, "clip.mov", "video/mp4")));
|
Assert.False(WorkOrderMediaFileRules.IsAllowed(FormFile(RealMov, "clip.mov", "video/mp4")));
|
||||||
Assert.False(WorkOrderMediaFileRules.IsAllowed(FormFile(RealJpeg, "photo.jpg", "image/heic")));
|
Assert.False(WorkOrderMediaFileRules.IsAllowed(FormFile(RealMp4, "clip.mp4", "video/quicktime")));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
// SH-381: mobile browsers attach a foreign-but-plausible type to a supported container.
|
||||||
|
// The extension plus magic bytes must decide, not the unreliable declared MIME.
|
||||||
|
[InlineData("clip.mp4", "video/3gpp")]
|
||||||
|
[InlineData("VID_0001.MP4", "video/3gpp")]
|
||||||
|
[InlineData("IMG_1587.MOV", "video/x-quicktime")]
|
||||||
|
[InlineData("IMG_1587.mov", "video/mpeg")]
|
||||||
|
[InlineData("photo.jpg", "image/heic")]
|
||||||
|
public void IsAllowed_ForeignDeclaredMime_ResolvesFromExtensionAndSignature(
|
||||||
|
string fileName,
|
||||||
|
string contentType)
|
||||||
|
{
|
||||||
|
Assert.True(WorkOrderMediaFileRules.IsAllowed(
|
||||||
|
FormFile(BytesFor(fileName), fileName, contentType), WorkOrderMediaCategory.Before));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsAllowed_ForeignDeclaredMime_StillRequiresMatchingSignatureAndExtension()
|
||||||
|
{
|
||||||
|
// Falling back to the extension does not weaken the gate: the bytes must still match
|
||||||
|
// the resolved type, the extension must still be supported, and a resolved document
|
||||||
|
// stays out of photo/video categories.
|
||||||
|
Assert.False(WorkOrderMediaFileRules.IsAllowed(FormFile(RealJpeg, "clip.mp4", "video/3gpp")));
|
||||||
|
Assert.False(WorkOrderMediaFileRules.IsAllowed(FormFile(RealMp4, "clip.exe", "video/3gpp")));
|
||||||
|
Assert.False(WorkOrderMediaFileRules.IsAllowed(
|
||||||
|
FormFile(RealPdf, "report.pdf", "application/x-unknown"), WorkOrderMediaCategory.Before));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] BytesFor(string fileName)
|
private static byte[] BytesFor(string fileName)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue