onebeyond/onebeyond-studio-file-storage

View on GitHub
src/OneBeyond.Studio.FileStorage.Domain/Validations/MimeTypeValidationStrategy.cs

Summary

Maintainability
A
1 hr
Test Coverage
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using EnsureThat;
using OneBeyond.Studio.FileStorage.Domain.Options;

namespace OneBeyond.Studio.FileStorage.Domain.Validations;

internal abstract class MimeTypeValidationStrategy
{
    protected MimeTypeValidationStrategy(MimeTypeValidationOptions mimeTypeValidationOptions)
    {
        EnsureArg.IsNotNull(mimeTypeValidationOptions, nameof(mimeTypeValidationOptions));
        EnsureArg.IsNotNull(mimeTypeValidationOptions.MimeTypeSignatures, nameof(mimeTypeValidationOptions.MimeTypeSignatures));
        EnsureArg.IsTrue(
            mimeTypeValidationOptions.MimeTypeSignatures
                .SelectMany((mimeTypeOptions) => mimeTypeOptions.Signatures.Select((signature) => signature.Length))
                .All((signatureLength) => signatureLength % 2 == 0) == true,
            nameof(mimeTypeValidationOptions.MimeTypeSignatures),
            (options) => options.WithMessage("Mime type signature length is not multiple of 2"));

        MimeTypeValidationOptions = mimeTypeValidationOptions;
    }

    protected MimeTypeValidationOptions MimeTypeValidationOptions { get; }

    public abstract bool IsFileAllowed(Stream content, string mimeType);

    public abstract bool IsFileAllowed(byte[] content, string mimeType);

    protected bool IsFileCoveredByOptions(Stream content, string mimeType)
    {
        return IsFileCoveredByOptions(
            (signatureFromContentBytes, signatureFromContentLength) =>
            {
                signatureFromContentLength = content.Read(
                    signatureFromContentBytes,
                    0,
                    signatureFromContentLength);
                content.Seek(0, SeekOrigin.Begin);
                return signatureFromContentLength;
            },
            mimeType);
    }

    protected bool IsFileCoveredByOptions(byte[] content, string mimeType)
    {
        return IsFileCoveredByOptions(
            (signatureFromContentBytes, signatureFromContentLength) =>
            {
                signatureFromContentLength = Math.Min(signatureFromContentLength, content.Length);
                Array.Copy(content, 0, signatureFromContentBytes, 0, signatureFromContentLength);
                return signatureFromContentLength;
            },
            mimeType);
    }

    private bool IsFileCoveredByOptions(Func<byte[], int, int> getContentSignature, string mimeType)
    {
        var mimeTypeSignatures = ListSignaturesByMimeType(mimeType);

        if (!mimeTypeSignatures.Any())
        {
            return false;
        }

        var mimeTypeSignatureValues = mimeTypeSignatures
            .SelectMany((mimeTypeSignature) => mimeTypeSignature)
            .ToArray();

        if (mimeTypeSignatureValues.Length == 0
            || mimeTypeSignatureValues.Any((signature) => signature.Length == 0))
        {
            return true;
        }

        var signatureMaxLength = mimeTypeSignatureValues.Max((signature) => signature.Length) / 2;

        var signatureFromContentBytes = new byte[signatureMaxLength];
        var signatureFromContentLength = getContentSignature(
            signatureFromContentBytes,
            signatureFromContentBytes.Length);
        var signatureFromContent = BitConverter.ToString(
                signatureFromContentBytes,
                0,
                signatureFromContentLength)
            .Replace("-", string.Empty);

        return mimeTypeSignatureValues
            .Any((signature) =>
                signature.Length <= signatureFromContent.Length
                && signatureFromContent.StartsWith(signature, StringComparison.OrdinalIgnoreCase));
    }

    private IEnumerable<IReadOnlyCollection<string>> ListSignaturesByMimeType(string mimeType)
        => MimeTypeValidationOptions.MimeTypeSignatures
            .Where((mimeTypeSignature) =>
                string.Equals(mimeTypeSignature.MimeType, mimeType, StringComparison.OrdinalIgnoreCase))
            .Select((mimeTypeSignature) =>
                mimeTypeSignature.Signatures);
}