From 502f9f757ce3becdb27d0fdd132524f779a75935 Mon Sep 17 00:00:00 2001 From: Jaben Cargman Date: Sun, 5 Apr 2020 18:42:29 -0700 Subject: [PATCH 01/39] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0430cfdc..117c3821 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ The Simple Desktop Email Helper [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jaben/Papercut?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/Jaben) +*Note*: **Papercut** is now **Papercut SMTP**. ## The problem If you ever send emails from an application or web site during development, you're familiar with the fear of an email being released into the wild. Are you positive none of the 'test' emails are addressed to colleagues or worse, customers? Of course, you can setting up and maintain a test email server for development -- but that's a chore. Plus, the delay when you are waiting to view new test emails can radically slow your development cycle. From 9f4f7763f61238a73c0be7921339cba44ef7f790 Mon Sep 17 00:00:00 2001 From: Jaben Cargman Date: Sun, 5 Apr 2020 18:43:28 -0700 Subject: [PATCH 02/39] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 117c3821..2065cd36 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ The Simple Desktop Email Helper ## The problem If you ever send emails from an application or web site during development, you're familiar with the fear of an email being released into the wild. Are you positive none of the 'test' emails are addressed to colleagues or worse, customers? Of course, you can setting up and maintain a test email server for development -- but that's a chore. Plus, the delay when you are waiting to view new test emails can radically slow your development cycle. -## Papercut to the rescue! -Papercut is a 2-in-1 quick email viewer AND built-in SMTP server (designed to receive messages only). Not only does it not enforce any restrictions how you prepare your email, but it allows you to view the whole email-chilada: body, html, headers, attachment down right down to the naughty raw encoded bits. Papercut can be configured to run on startup and sit quietly (minimized in the tray) only providing a notification when a new message has arrived. +## Papercut SMTP to the rescue! +Papercut SMTP is a 2-in-1 quick email viewer AND built-in SMTP server (designed to receive messages only). Not only does it not enforce any restrictions how you prepare your email, but it allows you to view the whole email-chilada: body, html, headers, attachment down right down to the naughty raw encoded bits. Papercut can be configured to run on startup and sit quietly (minimized in the tray) only providing a notification when a new message has arrived. ## Download Now -#### [Latest Release](https://github.com/ChangemakerStudios/Papercut/releases) +#### [Latest Release](https://github.com/ChangemakerStudios/Papercut-SMTP/releases) Download Papercut.Setup.exe installer. ## Features @@ -29,8 +29,8 @@ Download Papercut.Setup.exe installer. #### New in Version 4.4: Logging View ![In Version 4.4: Logging View](https://changemakerstudios.us/images/Papercut/Papercut-Log.png) -## Papercut Background Service -Papercut has an optional "always on" service to receive emails even when the client is not running. It's installed by default with [Papercut.Setup.exe](https://github.com/ChangemakerStudios/Papercut/releases) -- but can also be installed separately by downloading [Papercut.Service.zip](https://github.com/ChangemakerStudios/Papercut/releases), unzipping and [following the service installation instructions](https://github.com/ChangemakerStudios/Papercut/tree/develop/src/Papercut.Service). +## Papercut SMTP Background Service +Papercut SMTP has an optional "always on" service to receive emails even when the client is not running. It's installed by default with [Papercut.Setup.exe](https://github.com/ChangemakerStudios/Papercut/releases) -- but can also be installed separately by downloading [Papercut.Service.zip](https://github.com/ChangemakerStudios/Papercut/releases), unzipping and [following the service installation instructions](https://github.com/ChangemakerStudios/Papercut/tree/develop/src/Papercut.Service). ## License -Papercut is Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). +Papercut SMTP is Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). From 16d857af564522939db3f1aeca5d7cbb4cc5bb10 Mon Sep 17 00:00:00 2001 From: Jaben Cargman Date: Tue, 12 May 2020 23:30:32 -0700 Subject: [PATCH 03/39] Fixes #162 Won't auto-select latest when new messages are coming in. --- src/Papercut.UI/ViewModels/MainViewModel.cs | 16 +++++++++------- .../ViewModels/MessageListViewModel.cs | 8 ++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Papercut.UI/ViewModels/MainViewModel.cs b/src/Papercut.UI/ViewModels/MainViewModel.cs index 518976df..15e9ab2a 100644 --- a/src/Papercut.UI/ViewModels/MainViewModel.cs +++ b/src/Papercut.UI/ViewModels/MainViewModel.cs @@ -144,7 +144,8 @@ public string WindowTitle string GetVersion() { - var productVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion; + var productVersion = + FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion; return productVersion.Split('+').FirstOrDefault(); } @@ -279,6 +280,7 @@ public IEnumerable RenderLogEventParts(LogEvent e) { yield return $@"
Exception: {e.Exception.Message.Linkify()}"; } + yield return @""; } @@ -361,7 +363,7 @@ void SetupObservables() public void GoToSite() { - Process.Start("https://github.com/ChangemakerStudios/Papercut"); + Process.Start("https://github.com/ChangemakerStudios/Papercut-SMTP"); } public void ShowRulesConfiguration() @@ -420,7 +422,7 @@ public void ForwardSelected() { if (MessageListViewModel.SelectedMessage == null) return; - var forwardViewModel = new ForwardViewModel { FromSetting = true }; + var forwardViewModel = new ForwardViewModel {FromSetting = true}; bool? result = _viewModelWindowManager.ShowDialog(forwardViewModel); if (result == null || !result.Value) return; @@ -437,10 +439,10 @@ public void ForwardSelected() progressDialog.SetIndeterminate(); var forwardRule = new ForwardRule - { - FromEmail = forwardViewModel.From, - ToEmail = forwardViewModel.To - }; + { + FromEmail = forwardViewModel.From, + ToEmail = forwardViewModel.To + }; forwardRule.PopulateServerFromUri(forwardViewModel.Server); diff --git a/src/Papercut.UI/ViewModels/MessageListViewModel.cs b/src/Papercut.UI/ViewModels/MessageListViewModel.cs index 7cc01a66..8a3f6372 100644 --- a/src/Papercut.UI/ViewModels/MessageListViewModel.cs +++ b/src/Papercut.UI/ViewModels/MessageListViewModel.cs @@ -224,12 +224,12 @@ void AddNewMessage(MessageEntry entry) $"From: {message.From.ToString().Truncate(50)}\r\nSubject: {message.Subject.Truncate(50)}", ToolTipIcon.Info)); - // Add it to the list box - ClearSelected(); - PopSelectedIndex(); - entry.IsSelected = true; + Messages.Add(new MimeMessageEntry(entry, _mimeMessageLoader)); + + // handle selection if nothing is selected + ValidateSelected(); }, e => { From e8b28ef97ad7f6587ab11781b1fb82a967cdece9 Mon Sep 17 00:00:00 2001 From: Jaben Cargman Date: Wed, 13 May 2020 00:10:11 -0700 Subject: [PATCH 04/39] #163 Done Added DeleteMessage route and delete message function to UI. --- .../Controllers/MessagesController.cs | 62 ++- src/Papercut.App.WebApi/RouteConfig.cs | 7 +- src/Papercut.App.WebApi/assets/index.html | 381 +++++++++------ .../assets/js/controllers.js | 449 +++++++++--------- 4 files changed, 528 insertions(+), 371 deletions(-) diff --git a/src/Papercut.App.WebApi/Controllers/MessagesController.cs b/src/Papercut.App.WebApi/Controllers/MessagesController.cs index a1a7520e..b43abfb9 100644 --- a/src/Papercut.App.WebApi/Controllers/MessagesController.cs +++ b/src/Papercut.App.WebApi/Controllers/MessagesController.cs @@ -18,6 +18,7 @@ namespace Papercut.App.WebApi.Controllers { using System; + using System.CodeDom; using System.Collections.Generic; using System.IO; using System.Linq; @@ -27,6 +28,7 @@ namespace Papercut.App.WebApi.Controllers using System.Net.Mime; using System.Threading.Tasks; using System.Web.Http; + using System.Web.Http.Results; using MimeKit; @@ -36,15 +38,20 @@ namespace Papercut.App.WebApi.Controllers using Papercut.Message; using Papercut.Message.Helpers; + using Serilog; + using Serilog.Context; + public class MessagesController : ApiController { readonly MessageRepository _messageRepository; readonly MimeMessageLoader _messageLoader; + private readonly ILogger _logger; - public MessagesController(MessageRepository messageRepository, MimeMessageLoader messageLoader) + public MessagesController(MessageRepository messageRepository, MimeMessageLoader messageLoader, ILogger logger) { this._messageRepository = messageRepository; this._messageLoader = messageLoader; + _logger = logger; } [HttpGet] @@ -74,30 +81,65 @@ public HttpResponseMessage DeleteAll() this._messageRepository.LoadMessages() .ForEach(msg => { - try - { - this._messageRepository.DeleteMessage(msg); - } - catch + using (LogContext.PushProperty("MessageEntry", msg, true)) { - // ignored + try + { + this._messageRepository.DeleteMessage(msg); + } + catch (Exception ex) when (LogFailure(ex, "Failure Deleting Message")) + { + } } }); return this.Request.CreateResponse(HttpStatusCode.OK); } + [HttpDelete] + public IHttpActionResult DeleteMessage(string id) + { + var msg = this._messageRepository.LoadMessages() + .FirstOrDefault(m => m.Name == id); + + if (msg == null) + { + return this.NotFound(); + } + + using (LogContext.PushProperty("MessageEntry", msg, true)) + { + try + { + this._messageRepository.DeleteMessage(msg); + } + catch (Exception ex) when (LogFailure(ex, "Failure Deleting Message")) + { + } + } + + return this.Ok(); + } + + private bool LogFailure(Exception ex, string message) + { + this._logger.Error(ex, message); + + return true; + } + [HttpGet] - public async Task Get(string id) + public async Task Get(string id) { var messageEntry = this._messageRepository.LoadMessages().FirstOrDefault(msg => msg.Name == id); if (messageEntry == null) { - return this.Request.CreateResponse(HttpStatusCode.NotFound); + return this.NotFound(); } var dto = MimeMessageEntry.DetailDto.CreateFrom(new MimeMessageEntry(messageEntry, await this._messageLoader.GetAsync(messageEntry))); - return this.Request.CreateResponse(HttpStatusCode.OK, dto); + + return this.Ok(dto); } [HttpGet] diff --git a/src/Papercut.App.WebApi/RouteConfig.cs b/src/Papercut.App.WebApi/RouteConfig.cs index db5007db..9bd30ee4 100644 --- a/src/Papercut.App.WebApi/RouteConfig.cs +++ b/src/Papercut.App.WebApi/RouteConfig.cs @@ -42,6 +42,11 @@ public static void Init(HttpConfiguration config, ILifetimeScope scope) new { controller = "Messages", action = "DeleteAll" }, new { HttpMethod = new HttpMethodConstraint(HttpMethod.Delete) }); + config.Routes.MapHttpRoute("delete message", + "api/messages/{id}", + new {controller = "Messages", action = "DeleteMessage"}, + new {HttpMethod = new HttpMethodConstraint(HttpMethod.Delete)}); + config.Routes.MapHttpRoute("load message detail", "api/messages/{id}", new {controller = "Messages", action = "Get"}); @@ -54,7 +59,7 @@ public static void Init(HttpConfiguration config, ILifetimeScope scope) "api/messages/{messageId}/sections/{index}", new {controller = "Messages", action = "DownloadSection" }); - config.Routes.MapHttpRoute("download raw message palyload", + config.Routes.MapHttpRoute("download raw message payload", "api/messages/{messageId}/raw", new { controller = "Messages", action = "DownloadRaw" }); diff --git a/src/Papercut.App.WebApi/assets/index.html b/src/Papercut.App.WebApi/assets/index.html index f62fc4ae..3aa031fa 100644 --- a/src/Papercut.App.WebApi/assets/index.html +++ b/src/Papercut.App.WebApi/assets/index.html @@ -1,8 +1,8 @@  - + - Papercut - + Papercut SMTP + @@ -19,7 +19,11 @@ @@ -27,8 +31,11 @@
- + - - - - {{ startMessages + 1 }}-{{ startMessages + countMessages }} - of - {{ totalMessages }} - + + + + {{ startMessages + 1 }}-{{ startMessages + countMessages + }} + of + {{ totalMessages }} + - + - - -
+ + +
-
-
-
- {{ message.Subject }} -
-
-
-
- {{ getMoment(message.CreatedAt).fromNow() }} -
-
- {{ message.Size }} -
-
-
-
-
+
+
+
+ {{ message.Subject }} +
+
+
+
+ {{ getMoment(message.CreatedAt).fromNow() }} +
+
+ {{ message.Size }} +
+
+
+
+
-
- -
+
+ -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
From{{preview.From[0].Name}}({{ preview.From[0].Address }})
To - -
CC - -
BCC - -
Date - {{preview.Date}} -
Subject{{ preview.Subject }}
-
-
+ +
-
- - Download raw message - - +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
From + {{preview.From[0].Name}}({{ preview.From[0].Address }}) +
To + +
CC + +
BCC + +
Date + {{preview.Date}} +
Subject{{ preview.Subject }}
+
+
-
- -
-
-
    -
  • - {{header.Name}}: {{header.Value}} +
    + + + Download raw message + + -
    -
    - - - - - - - - - - - - - -
    MEDIA TYPEFILENAMESAVE
    {{section.MediaType}}{{section.FileName}}
    -
    -
-
-
- +
  • + Plain text +
  • +
  • + Headers +
  • +
  • + Sections +
  • + +
    + +
    +
    +
      +
    • + {{header.Name}}: + {{header.Value}} +
    • +
    +
    +
    + + + + + + + + + + + + + +
    MEDIA TYPEFILENAMESAVE
    {{section.MediaType}}{{section.FileName}} + +
    +
    +
    +
    +
    diff --git a/src/Papercut.App.WebApi/assets/js/controllers.js b/src/Papercut.App.WebApi/assets/js/controllers.js index 3e4883b3..b2504453 100644 --- a/src/Papercut.App.WebApi/assets/js/controllers.js +++ b/src/Papercut.App.WebApi/assets/js/controllers.js @@ -3,225 +3,232 @@ var papercutApp = angular.module('papercutApp', []); papercutApp.controller('MailCtrl', function ($scope, $http, $sce, $timeout, $interval) { - $scope.events = { - eventDone: 0, - eventFailed: 0, - eventCount: 0, - eventsPending: {} - }; - - - $scope.cache = {}; - $scope.itemsPerPage = 50; - $scope.startIndex = 0; - $scope.startMessages = 0; - $scope.countMessages = 0; - $scope.totalMessages = 0; - $scope.messages = []; - $scope.preview = null; - - if(typeof(Storage) !== "undefined") { - $scope.itemsPerPage = parseInt(localStorage.getItem("itemsPerPage"), 10) - if(!$scope.itemsPerPage) { - $scope.itemsPerPage = 50; - localStorage.setItem("itemsPerPage", 50) - } - } - - - - - $scope.getMoment = function (a) { - return moment.utc(a, 'YYYY-MM-DDTHH:mm:ss.SSSZ').local(); - }; - - $scope.backToInbox = function () { - $scope.preview = null; - }; - $scope.backToInboxFirst = function () { - $scope.preview = null; - $scope.startIndex = 0; - $scope.startMessages = 0; - $scope.refresh(); - }; - - $scope.refresh = function () { - var e = startEvent("Loading messages", null, "glyphicon-download"); - var url = 'api/messages'; - if ($scope.startIndex > 0) { - url += "?start=" + $scope.startIndex + "&limit=" + $scope.itemsPerPage; - } else { - url += "?limit=" + $scope.itemsPerPage; - } - - $http.get(url).then(function (resp) { - $scope.messages = resp.data.Messages; - $scope.totalMessages = resp.data.TotalMessageCount; - $scope.countMessages = resp.data.Messages.length; - $scope.startMessages = $scope.startIndex; - e.done(); - }); - }; - - $scope.refresh(); - $interval(function () { - if ($scope.startIndex == 0) { - $scope.refresh(); - } - }, 8000); - - - $scope.showUpdated = function (i) { - $scope.itemsPerPage = parseInt(i, 10); - if (typeof (Storage) !== "undefined") { - localStorage.setItem("itemsPerPage", $scope.itemsPerPage); - } - - $scope.startIndex = 0; - $scope.refresh(); - }; - - $scope.showNewer = function () { - $scope.startIndex -= $scope.itemsPerPage; - if ($scope.startIndex < 0) { - $scope.startIndex = 0; - } - $scope.refresh(); - }; - - $scope.showOlder = function () { - $scope.startIndex += $scope.itemsPerPage; - $scope.refresh(); - }; - - $scope.deleteAll = function () { - if(!window.confirm('Are you sure to delete all messages?')){ - return; - } - - $http.delete('api/messages').finally(function () { - $scope.refresh(); - }); - }; - - $scope.selectMessage = function (message) { - if ($scope.cache[message.Id]) { - $scope.preview = $scope.cache[message.Id]; - } else { - $scope.preview = message; - var e = startEvent("Loading message", message.Id, "glyphicon-download-alt"); - $http.get('api/messages/' + message.Id).then(function (resp) { - $scope.cache[message.Id] = resp.data; - - resp.data.previewHTML = $sce.trustAsHtml(resp.data.HtmlBody); - $scope.preview = resp.data; - - var dateHeader = resp.data.Headers.find(function(h){return h.Name === 'Date'}); - $scope.preview.Date = dateHeader === null ? null : dateHeader.Value; - e.done(); - }); - } - }; - - $scope.formatMessagePlain = function (message) { - var body = message.TextBody || ''; - var escaped = $scope.escapeHtml(body); - var formatted = escaped.replace(/(https?:\/\/)([-[\]A-Za-z0-9._~:/?#@!$()*+,;=%]|&|')+/g, '$&'); - return $sce.trustAsHtml(formatted); - }; - - - $scope.escapeHtml = function(html) { - var entityMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' + $scope.events = { + eventDone: 0, + eventFailed: 0, + eventCount: 0, + eventsPending: {} }; - return html.replace(/[&<>"']/g, function (s) { - return entityMap[s]; - }); - } - - $scope.hasHTML = function(message) { - return !!message.HtmlBody; - } - - $scope.hasText = function (message) { - return !!message.TextBody; - } - - $scope.date = function(timestamp) { - return (new Date(timestamp)).toString(); - }; - - - function startEvent(name, args, glyphicon) { - var eID = guid(); - //console.log("Starting event '" + name + "' with id '" + eID + "'") - var e = { - id: eID, - name: name, - started: new Date(), - complete: false, - failed: false, - args: args, - glyphicon: glyphicon, - getClass: function () { - // FIXME bit nasty - if (this.failed) { - return "bg-danger" - } - if (this.complete) { - return "bg-success" - } - return "bg-warning"; // pending - }, - done: function () { - var e = this; - e.complete = true; - $scope.events.eventDone++; - if (this.failed) { - // console.log("Failed event '" + e.name + "' with id '" + eID + "'") - } else { - // console.log("Completed event '" + e.name + "' with id '" + eID + "'") - $timeout(function () { - e.remove(); - }, 10000); - } - }, - fail: function () { - $scope.events.eventFailed++; - this.failed = true; - this.done(); - }, - remove: function () { - // console.log("Deleted event '" + e.name + "' with id '" + eID + "'") - if (e.failed) { - $scope.events.eventFailed--; - } - delete $scope.events.eventsPending[eID]; - $scope.events.eventDone--; - $scope.events.eventCount--; - return false; - } - }; - $scope.events.eventsPending[eID] = e; - $scope.events.eventCount++; - return e; - } - - - function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); - } + + + $scope.cache = {}; + $scope.itemsPerPage = 50; + $scope.startIndex = 0; + $scope.startMessages = 0; + $scope.countMessages = 0; + $scope.totalMessages = 0; + $scope.messages = []; + $scope.preview = null; + + if (typeof (Storage) !== "undefined") { + $scope.itemsPerPage = parseInt(localStorage.getItem("itemsPerPage"), 10) + if (!$scope.itemsPerPage) { + $scope.itemsPerPage = 50; + localStorage.setItem("itemsPerPage", 50) + } + } + + $scope.getMoment = function (a) { + return moment.utc(a, 'YYYY-MM-DDTHH:mm:ss.SSSZ').local(); + }; + + $scope.backToInbox = function () { + $scope.preview = null; + }; + + $scope.backToInboxFirst = function () { + $scope.preview = null; + $scope.startIndex = 0; + $scope.startMessages = 0; + $scope.refresh(); + }; + + $scope.refresh = function () { + var e = startEvent("Loading messages", null, "glyphicon-download"); + var url = 'api/messages'; + + if ($scope.startIndex > 0) { + url += "?start=" + $scope.startIndex + "&limit=" + $scope.itemsPerPage; + } else { + url += "?limit=" + $scope.itemsPerPage; + } + + return $http.get(url).then(function (resp) { + $scope.messages = resp.data.Messages; + $scope.totalMessages = resp.data.TotalMessageCount; + $scope.countMessages = resp.data.Messages.length; + $scope.startMessages = $scope.startIndex; + e.done(); + }); + }; + + $scope.refresh(); + $interval(function () { + if ($scope.startIndex == 0) { + $scope.refresh(); + } + }, 8000); + + + $scope.showUpdated = function (i) { + $scope.itemsPerPage = parseInt(i, 10); + if (typeof (Storage) !== "undefined") { + localStorage.setItem("itemsPerPage", $scope.itemsPerPage); + } + + $scope.startIndex = 0; + $scope.refresh(); + }; + + $scope.showNewer = function () { + $scope.startIndex -= $scope.itemsPerPage; + if ($scope.startIndex < 0) { + $scope.startIndex = 0; + } + $scope.refresh(); + }; + + $scope.showOlder = function () { + $scope.startIndex += $scope.itemsPerPage; + $scope.refresh(); + }; + + $scope.deleteAll = function () { + if (!window.confirm('Are you sure to delete all messages?')) { + return; + } + + $http.delete('api/messages').finally(function () { + $scope.refresh(); + }); + }; + + $scope.deleteMessage = function(message) { + $http.delete('/api/messages/' + message.Id).finally(function () { + $scope.refresh().finally(function() { + $scope.backToInbox(); + }); + }); + } + + $scope.selectMessage = function (message) { + if ($scope.cache[message.Id]) { + $scope.preview = $scope.cache[message.Id]; + } else { + $scope.preview = message; + var e = startEvent("Loading message", message.Id, "glyphicon-download-alt"); + $http.get('api/messages/' + message.Id).then(function (resp) { + $scope.cache[message.Id] = resp.data; + + resp.data.previewHTML = $sce.trustAsHtml(resp.data.HtmlBody); + $scope.preview = resp.data; + + var dateHeader = resp.data.Headers.find(function (h) { return h.Name === 'Date' }); + $scope.preview.Date = dateHeader === null ? null : dateHeader.Value; + e.done(); + }); + } + }; + + $scope.formatMessagePlain = function (message) { + var body = message.TextBody || ''; + var escaped = $scope.escapeHtml(body); + var formatted = escaped.replace(/(https?:\/\/)([-[\]A-Za-z0-9._~:/?#@!$()*+,;=%]|&|')+/g, '$&'); + return $sce.trustAsHtml(formatted); + }; + + + $scope.escapeHtml = function (html) { + var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return html.replace(/[&<>"']/g, function (s) { + return entityMap[s]; + }); + } + + $scope.hasHTML = function (message) { + return !!message.HtmlBody; + } + + $scope.hasText = function (message) { + return !!message.TextBody; + } + + $scope.date = function (timestamp) { + return (new Date(timestamp)).toString(); + }; + + + function startEvent(name, args, glyphicon) { + var eID = guid(); + //console.log("Starting event '" + name + "' with id '" + eID + "'") + var e = { + id: eID, + name: name, + started: new Date(), + complete: false, + failed: false, + args: args, + glyphicon: glyphicon, + getClass: function () { + // FIXME bit nasty + if (this.failed) { + return "bg-danger" + } + if (this.complete) { + return "bg-success" + } + return "bg-warning"; // pending + }, + done: function () { + var e = this; + e.complete = true; + $scope.events.eventDone++; + if (this.failed) { + // console.log("Failed event '" + e.name + "' with id '" + eID + "'") + } else { + // console.log("Completed event '" + e.name + "' with id '" + eID + "'") + $timeout(function () { + e.remove(); + }, 10000); + } + }, + fail: function () { + $scope.events.eventFailed++; + this.failed = true; + this.done(); + }, + remove: function () { + // console.log("Deleted event '" + e.name + "' with id '" + eID + "'") + if (e.failed) { + $scope.events.eventFailed--; + } + delete $scope.events.eventsPending[eID]; + $scope.events.eventDone--; + $scope.events.eventCount--; + return false; + } + }; + $scope.events.eventsPending[eID] = e; + $scope.events.eventCount++; + return e; + } + + + function guid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + } }); @@ -251,7 +258,7 @@ papercutApp.directive('bodyHtml', ['$sce', '$timeout', function ($sce, $timeout) htmlContent = replaceContentLinks(htmlContent, messageId); var body = $(element).contents().find('body'); - body.empty().append( htmlContent ); + body.empty().append(htmlContent); $timeout(function () { element.css('height', $(body[0].ownerDocument.documentElement).height() + 100); @@ -265,8 +272,8 @@ papercutApp.directive('bodyHtml', ['$sce', '$timeout', function ($sce, $timeout) var links = /((src|href)\s*=["']?\s*)javascript:/gi; return html.replace(tagStarts, '
    ') - .replace(links, '$1'); + .replace(tagEnds, '
    ') + .replace(links, '$1'); } function replaceContentLinks(html, messageId) { From a87e1ee5428da14e6a7170e794639af3b068f086 Mon Sep 17 00:00:00 2001 From: jeldert Date: Thu, 14 May 2020 14:45:56 +0200 Subject: [PATCH 05/39] Update GitHub url of Papercut-SMTP --- src/Papercut.UI/Views/MainView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Papercut.UI/Views/MainView.xaml b/src/Papercut.UI/Views/MainView.xaml index 42fde960..277e63e0 100644 --- a/src/Papercut.UI/Views/MainView.xaml +++ b/src/Papercut.UI/Views/MainView.xaml @@ -172,7 +172,7 @@