Skip to content

Commit

Permalink
(GH-292) Improve attachment convenience methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Jericho committed Jul 6, 2019
1 parent aacbfdb commit a2a2393
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 16 deletions.
93 changes: 77 additions & 16 deletions Source/StrongGrid/Models/Attachment.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using HeyRed.Mime;
using Newtonsoft.Json;
using System;
using System.Buffers;
using System.IO;
using System.Text;

namespace StrongGrid.Models
{
Expand All @@ -14,6 +16,9 @@ public class Attachment
// Therefore it's safe to assume that a given attachment cannot be larger than 30MB
private const int MAX_ATTACHMENT_SIZE = 30 * 1024 * 1024;

// Reasonable 4kb buffer when reading from stream
private const int BUFFER_SIZE = 4096;

/// <summary>
/// Gets or sets the content.
/// </summary>
Expand Down Expand Up @@ -70,14 +75,11 @@ public class Attachment
/// <exception cref="Exception">File exceeds the size limit.</exception>
public static Attachment FromLocalFile(string filePath, string mimeType = null, string contentId = null)
{
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
var fileName = Path.GetFileName(filePath);
using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
if (fs.Length > MAX_ATTACHMENT_SIZE) throw new Exception("Attachment exceeds the size limit");

var content = new byte[fs.Length];
fs.Read(content, 0, Convert.ToInt32(fs.Length));

return FromBinary(content, Path.GetFileName(filePath), mimeType, contentId);
if (fileStream.Length > MAX_ATTACHMENT_SIZE) throw new Exception("File exceeds the size limit");
return FromStream(fileStream, fileName, mimeType, contentId);
}
}

Expand All @@ -89,19 +91,38 @@ public static Attachment FromLocalFile(string filePath, string mimeType = null,
/// <param name="mimeType">Optional: MIME type of the attachment. If this parameter is null, the MIME type will be derived from the fileName extension.</param>
/// <param name="contentId">Optional: the unique identifier for this attachment IF AND ONLY IF the attachment should be embedded in the body of the email. This is useful, for example, if you want to embbed an image to be displayed in the HTML content. For standard attachment, this value should be null.</param>
/// <returns>The attachment.</returns>
/// <exception cref="Exception">File exceeds the size limit.</exception>
/// <exception cref="ArgumentNullException">contentStream is null.</exception>
/// <exception cref="ArgumentException">The content stream is not readable.</exception>
/// <exception cref="ArgumentException">The content is empty.</exception>
/// <exception cref="Exception">Content exceeds the size limit.</exception>
public static Attachment FromStream(Stream contentStream, string fileName, string mimeType = null, string contentId = null)
{
if (contentStream.Length > MAX_ATTACHMENT_SIZE) throw new Exception("Attachment exceeds the size limit");
if (contentStream == null) throw new ArgumentNullException(nameof(contentStream));
if (!contentStream.CanRead) throw new ArgumentException("The content stream is not readable", nameof(contentStream));

var content = (byte[])null;
using (var ms = new MemoryStream())
var content = new StringBuilder();
byte[] buffer = ArrayPool<byte>.Shared.Rent(BUFFER_SIZE);
try
{
contentStream.CopyTo(ms);
content = ms.ToArray();
var bytesRead = contentStream.Read(buffer, 0, BUFFER_SIZE);
while (bytesRead > 0)
{
content.Append(Convert.ToBase64String(buffer, 0, bytesRead));
if (content.Length > MAX_ATTACHMENT_SIZE) throw new Exception("Content exceeds the size limit");

bytesRead = contentStream.Read(buffer, 0, BUFFER_SIZE);
}
}
catch (Exception)
{
throw;
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}

return FromBinary(content, fileName, mimeType, contentId);
return FromBase64String(content.ToString(), fileName, mimeType, contentId);
}

/// <summary>
Expand All @@ -112,10 +133,50 @@ public static Attachment FromStream(Stream contentStream, string fileName, strin
/// <param name="mimeType">Optional: MIME type of the attachment. If this parameter is null, the MIME type will be derived from the fileName extension.</param>
/// <param name="contentId">Optional: the unique identifier for this attachment IF AND ONLY IF the attachment should be embedded in the body of the email. This is useful, for example, if you want to embbed an image to be displayed in the HTML content. For standard attachment, this value should be null.</param>
/// <returns>The attachment.</returns>
/// <exception cref="ArgumentNullException">content is null.</exception>
/// <exception cref="ArgumentException">The content is empty.</exception>
/// <exception cref="Exception">File exceeds the size limit.</exception>
public static Attachment FromBinary(byte[] content, string fileName, string mimeType = null, string contentId = null)
{
if (content.Length > MAX_ATTACHMENT_SIZE) throw new Exception("Attachment exceeds the size limit");
if (content == null) throw new ArgumentNullException(nameof(content));
if (content.Length == 0) throw new ArgumentException("The content is empty", nameof(content));

return FromBase64String(Convert.ToBase64String(content), fileName, mimeType, contentId);
}

/// <summary>
/// Convenience method that creates an attachment from a string.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="fileName">The name of the attachment.</param>
/// <param name="mimeType">Optional: MIME type of the attachment. If this parameter is null, the MIME type will be derived from the fileName extension.</param>
/// <param name="contentId">Optional: the unique identifier for this attachment IF AND ONLY IF the attachment should be embedded in the body of the email. This is useful, for example, if you want to embbed an image to be displayed in the HTML content. For standard attachment, this value should be null.</param>
/// <returns>The attachment.</returns>
/// <exception cref="ArgumentNullException">content is null.</exception>
/// <exception cref="ArgumentException">The content is empty.</exception>
/// <exception cref="Exception">File exceeds the size limit.</exception>
public static Attachment FromString(string content, string fileName, string mimeType = null, string contentId = null)
{
if (content == null) throw new ArgumentNullException(nameof(content));
if (content.Length == 0) throw new ArgumentException("The content is empty", nameof(content));
return FromBase64String(Convert.ToBase64String(Encoding.UTF8.GetBytes(content)), fileName, mimeType, contentId);
}

/// <summary>
/// Convenience method that creates an attachment from a base64 encoded string.
/// </summary>
/// <param name="content">The base64 encoded content.</param>
/// <param name="fileName">The name of the attachment.</param>
/// <param name="mimeType">Optional: MIME type of the attachment. If this parameter is null, the MIME type will be derived from the fileName extension.</param>
/// <param name="contentId">Optional: the unique identifier for this attachment IF AND ONLY IF the attachment should be embedded in the body of the email. This is useful, for example, if you want to embbed an image to be displayed in the HTML content. For standard attachment, this value should be null.</param>
/// <returns>The attachment.</returns>
/// <exception cref="ArgumentException">The content is empty.</exception>
/// <exception cref="Exception">Content exceeds the size limit.</exception>
public static Attachment FromBase64String(string content, string fileName, string mimeType = null, string contentId = null)
{
if (content == null) throw new ArgumentNullException(nameof(content));
if (content.Length == 0) throw new ArgumentException("The content is empty", nameof(content));
if (content.Length > MAX_ATTACHMENT_SIZE) throw new Exception("Content exceeds the size limit");

if (string.IsNullOrEmpty(mimeType))
{
Expand All @@ -124,7 +185,7 @@ public static Attachment FromBinary(byte[] content, string fileName, string mime

return new Attachment()
{
Content = Convert.ToBase64String(content),
Content = content,
ContentId = contentId,
Disposition = string.IsNullOrEmpty(contentId) ? "attachment" : "inline",
FileName = fileName,
Expand Down
1 change: 1 addition & 0 deletions Source/StrongGrid/StrongGrid.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<PackageReference Include="Newtonsoft.Json" Version="11.0.1" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="3.3.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="System.Buffers" Version="4.5.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>

Expand Down

0 comments on commit a2a2393

Please sign in to comment.