mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 15:23:12 +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" }
|
||||
};
|
||||
|
||||
// A browser fills the multipart part's Content-Type from File.type, which mobile
|
||||
// browsers leave empty (or the client sends octet-stream) when the OS cannot
|
||||
// classify a picked file. Only then is the type resolved from the extension; the
|
||||
// category rule, extension pairing, and magic-byte signature still decide.
|
||||
private static readonly HashSet<string> UndeterminedContentTypes =
|
||||
new(StringComparer.OrdinalIgnoreCase) { string.Empty, "application/octet-stream" };
|
||||
|
||||
// The multipart part's Content-Type comes from the browser's File.type, which is
|
||||
// unreliable on mobile: it is left empty or sent as application/octet-stream when the
|
||||
// OS cannot classify a picked file, and is sometimes a foreign-but-plausible video
|
||||
// type the OS attaches to a supported container (e.g. video/3gpp for an .mp4,
|
||||
// video/x-quicktime for a .mov). An allowlisted declared type stays authoritative and
|
||||
// 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)
|
||||
{
|
||||
if (!UndeterminedContentTypes.Contains(declaredType))
|
||||
return AllowedContentTypes.Contains(declaredType) ? declaredType : null;
|
||||
if (AllowedContentTypes.Contains(declaredType))
|
||||
return declaredType;
|
||||
|
||||
foreach (var (contentType, extensions) in ExtensionsByContentType)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2084,11 +2084,40 @@ public class WorkOrderMediaFileRulesTests
|
|||
}
|
||||
|
||||
[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(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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue