Sorry, the diff of this file is too big to display
@@ -7,32 +7,48 @@ /* global angular, app, prompt */ | ||
| app.controller('ItemCtrl', [ | ||
| '$scope', '$rootScope', '$routeParams', '$location', 'Email', '$http', '$cookies', | ||
| function ($scope, $rootScope, $routeParams, $location, Email, $http, $cookies) { | ||
| const iframe = null | ||
| app.controller("ItemCtrl", [ | ||
| "$scope", | ||
| "$rootScope", | ||
| "$routeParams", | ||
| "$location", | ||
| "Email", | ||
| "$http", | ||
| "$cookies", | ||
| function ( | ||
| $scope, | ||
| $rootScope, | ||
| $routeParams, | ||
| $location, | ||
| Email, | ||
| $http, | ||
| $cookies | ||
| ) { | ||
| // Get the item data by route parameter | ||
| const getItem = function () { | ||
| Email.get({ id: $routeParams.itemId }, function (email) { | ||
| $scope.item = new Email(email) | ||
| Email.get( | ||
| { id: $routeParams.itemId }, | ||
| function (email) { | ||
| $scope.item = new Email(email); | ||
| if ($scope.item.html) { | ||
| $scope.item.iframeUrl = 'email/' + $scope.item.id + '/html' | ||
| prepIframe() | ||
| $scope.panelVisibility = 'html' | ||
| } else { | ||
| $scope.htmlView = 'disabled' | ||
| $scope.panelVisibility = 'plain' | ||
| if ($scope.item.html) { | ||
| $scope.item.iframeUrl = "email/" + $scope.item.id + "/html"; | ||
| prepIframe(); | ||
| $scope.panelVisibility = "html"; | ||
| } else { | ||
| $scope.htmlView = "disabled"; | ||
| $scope.panelVisibility = "plain"; | ||
| } | ||
| }, | ||
| function () { | ||
| console.error("404: Email not found"); | ||
| $location.path("/"); | ||
| } | ||
| }, function () { | ||
| console.error('404: Email not found') | ||
| $location.path('/') | ||
| }) | ||
| } | ||
| ); | ||
| }; | ||
| // Get email source | ||
| const getSource = function () { | ||
| if (typeof $scope.rawEmail === 'undefined') { | ||
| $scope.rawEmail = 'email/' + $scope.item.id + '/source' | ||
| if (typeof $scope.rawEmail === "undefined") { | ||
| $scope.rawEmail = "email/" + $scope.item.id + "/source"; | ||
| } | ||
| } | ||
| }; | ||
@@ -43,38 +59,46 @@ // Prepares the iframe for interaction | ||
| setTimeout(function () { | ||
| const [iframe] = document.getElementsByTagName('iframe') | ||
| const [head] = iframe.contentDocument.getElementsByTagName('head') | ||
| const baseEl = iframe.contentDocument.createElement('base') | ||
| const [iframe] = document.getElementsByTagName("iframe"); | ||
| const [head] = iframe.contentDocument.getElementsByTagName("head"); | ||
| const baseEl = iframe.contentDocument.createElement("base"); | ||
| // Append <base target="_blank" /> to <head> in the iframe so all links open in new window | ||
| baseEl.setAttribute('target', '_blank') | ||
| baseEl.setAttribute("target", "_blank"); | ||
| if (head) head.appendChild(baseEl) | ||
| if (head) head.appendChild(baseEl); | ||
| replaceMediaQueries() | ||
| fixIframeHeight() | ||
| replaceMediaQueries(iframe); | ||
| fixIframeHeight(iframe); | ||
| addHideDropdownHandler(iframe.contentDocument.getElementsByTagName('body')[0]) | ||
| }, 500) | ||
| } | ||
| addHideDropdownHandler( | ||
| iframe.contentDocument.getElementsByTagName("body")[0] | ||
| ); | ||
| }, 500); | ||
| }; | ||
| // Updates the iframe height so it matches it's content | ||
| // This prevents the iframe from having scrollbars | ||
| const fixIframeHeight = function () { | ||
| const body = iframe.contentDocument.getElementsByTagName('body')[0] | ||
| const newHeight = body.scrollHeight | ||
| const fixIframeHeight = function (iframe) { | ||
| const body = iframe.contentDocument.getElementsByTagName("body")[0]; | ||
| const newHeight = body.scrollHeight; | ||
| iframe.height = newHeight | ||
| } | ||
| iframe.height = newHeight; | ||
| }; | ||
| // Updates all media query rules to use 'width' instead of device width | ||
| const replaceMediaQueries = function () { | ||
| angular.forEach(iframe.contentDocument.styleSheets, function (styleSheet) { | ||
| angular.forEach(styleSheet.cssRules, function (rule) { | ||
| if (rule.media && rule.media.mediaText) { | ||
| // TODO -- Add future warning if email doesn't use '[max|min]-device-width' media queries | ||
| rule.media.mediaText = rule.media.mediaText.replace('device-width', 'width') | ||
| } | ||
| }) | ||
| }) | ||
| } | ||
| const replaceMediaQueries = function (iframe) { | ||
| angular.forEach( | ||
| iframe.contentDocument.styleSheets, | ||
| function (styleSheet) { | ||
| angular.forEach(styleSheet.cssRules, function (rule) { | ||
| if (rule.media && rule.media.mediaText) { | ||
| // TODO -- Add future warning if email doesn't use '[max|min]-device-width' media queries | ||
| rule.media.mediaText = rule.media.mediaText.replace( | ||
| "device-width", | ||
| "width" | ||
| ); | ||
| } | ||
| }); | ||
| } | ||
| ); | ||
| }; | ||
@@ -84,23 +108,26 @@ // NOTE: This is kind of a hack to get these dropdowns working. Should be revisited in the future | ||
| $scope.toggleDropdown = function ($event, dropdownName) { | ||
| $event.stopPropagation() | ||
| $scope.dropdownOpen = dropdownName === $scope.dropdownOpen ? '' : dropdownName | ||
| } | ||
| $event.stopPropagation(); | ||
| $scope.dropdownOpen = | ||
| dropdownName === $scope.dropdownOpen ? "" : dropdownName; | ||
| }; | ||
| function hideDropdown (e) { | ||
| function hideDropdown(e) { | ||
| $scope.$apply(function () { | ||
| $scope.dropdownOpen = '' | ||
| }) | ||
| $scope.dropdownOpen = ""; | ||
| }); | ||
| } | ||
| function addHideDropdownHandler (element) { | ||
| angular.element(element) | ||
| .off('click', hideDropdown) | ||
| .on('click', hideDropdown) | ||
| function addHideDropdownHandler(element) { | ||
| angular | ||
| .element(element) | ||
| .off("click", hideDropdown) | ||
| .on("click", hideDropdown); | ||
| } | ||
| addHideDropdownHandler(window) | ||
| addHideDropdownHandler(window); | ||
| function validateEmail (email) { | ||
| const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ | ||
| return re.test(email) | ||
| function validateEmail(email) { | ||
| const re = | ||
| /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; | ||
| return re.test(email); | ||
| } | ||
@@ -110,35 +137,40 @@ | ||
| $scope.show = function (type) { | ||
| if ((type === 'html' || type === 'attachments') && !$scope.item[type]) return | ||
| if (type === 'source') getSource() | ||
| if ((type === "html" || type === "attachments") && !$scope.item[type]) | ||
| return; | ||
| if (type === "source") getSource(); | ||
| $scope.panelVisibility = type | ||
| } | ||
| $scope.panelVisibility = type; | ||
| }; | ||
| // Sends a DELETE request to the server | ||
| $scope.delete = function (item) { | ||
| Email.delete({ id: item.id }) | ||
| } | ||
| Email.delete({ id: item.id }); | ||
| }; | ||
| // Updates iframe to have a width of newSize, i.e. '320px' | ||
| $scope.resize = function (newSize) { | ||
| iframe.style.width = newSize || '100%' | ||
| fixIframeHeight() | ||
| $scope.iframeSize = newSize | ||
| } | ||
| const [iframe] = document.getElementsByTagName("iframe"); | ||
| iframe.style.width = newSize || "100%"; | ||
| fixIframeHeight(); | ||
| $scope.iframeSize = newSize; | ||
| }; | ||
| // Relay email to | ||
| $scope.relayTo = function (item) { | ||
| const lastRelayTo = $cookies.relayTo | ||
| const lastRelayTo = $cookies.relayTo; | ||
| const relayTo = prompt('Please enter email address to relay', lastRelayTo) | ||
| const relayTo = prompt( | ||
| "Please enter email address to relay", | ||
| lastRelayTo | ||
| ); | ||
| if (relayTo) { | ||
| if (validateEmail(relayTo)) { | ||
| $scope.relay(item, relayTo) | ||
| $cookies.relayTo = relayTo | ||
| $scope.relay(item, relayTo); | ||
| $cookies.relayTo = relayTo; | ||
| } else { | ||
| window.alert('The specified email address is not correct.') | ||
| window.alert("The specified email address is not correct."); | ||
| } | ||
| } | ||
| } | ||
| }; | ||
@@ -149,6 +181,6 @@ // Relay email | ||
| window.alert( | ||
| 'Relay feature has not been configured.\n' + | ||
| 'Run maildev --help for configuration info.' | ||
| ) | ||
| return | ||
| "Relay feature has not been configured.\n" + | ||
| "Run maildev --help for configuration info." | ||
| ); | ||
| return; | ||
| } | ||
@@ -158,24 +190,31 @@ | ||
| window.confirm( | ||
| 'Are you sure you want to REALLY SEND email to ' + | ||
| (relayTo || item.to.map(function (to) { return to.address }).join()) + ' through ' + | ||
| $rootScope.config.outgoingHost + '?' | ||
| "Are you sure you want to REALLY SEND email to " + | ||
| (relayTo || | ||
| item.to | ||
| .map(function (to) { | ||
| return to.address; | ||
| }) | ||
| .join()) + | ||
| " through " + | ||
| $rootScope.config.outgoingHost + | ||
| "?" | ||
| ) | ||
| ) { | ||
| $http({ | ||
| method: 'POST', | ||
| url: 'email/' + item.id + '/relay' + (relayTo ? '/' + relayTo : '') | ||
| method: "POST", | ||
| url: "email/" + item.id + "/relay" + (relayTo ? "/" + relayTo : ""), | ||
| }) | ||
| .success(function (data, status) { | ||
| console.log('Relay result: ', data, status) | ||
| window.alert('Relay successful') | ||
| console.log("Relay result: ", data, status); | ||
| window.alert("Relay successful"); | ||
| }) | ||
| .error(function (data) { | ||
| window.alert('Relay failed: ' + data.error) | ||
| }) | ||
| window.alert("Relay failed: " + data.error); | ||
| }); | ||
| } | ||
| } | ||
| }; | ||
| // Initialize the view by getting the email | ||
| getItem() | ||
| } | ||
| ]) | ||
| getItem(); | ||
| }, | ||
| ]); |
@@ -6,3 +6,3 @@ version: '3' | ||
| maildev: | ||
| image: soulteary/maildev | ||
| image: maildev/maildev | ||
| restart: always | ||
@@ -9,0 +9,0 @@ environment: |
+10
-4
@@ -19,4 +19,4 @@ 'use strict' | ||
| const outgoing = require('./outgoing') | ||
| const TurndownService = require('turndown') | ||
| const marked = require('marked') | ||
| const createDOMPurify = require('dompurify') | ||
| const { JSDOM } = require('jsdom') | ||
@@ -272,4 +272,10 @@ const store = [] | ||
| if (email.html) { | ||
| const turndownService = new TurndownService() | ||
| email.html = marked.parse(turndownService.turndown(email.html)) | ||
| // sanitize html | ||
| const window = new JSDOM('').window | ||
| const DOMPurify = createDOMPurify(window) | ||
| email.html = DOMPurify.sanitize(email.html, { | ||
| WHOLE_DOCUMENT: true, // preserve html,head,body elements | ||
| SANITIZE_DOM: false, // ignore DOM cloberring to preserve form id/name attributes | ||
| ADD_TAGS: ['link'] // allow link element to preserve external style sheets | ||
| }) | ||
| } | ||
@@ -276,0 +282,0 @@ done(null, email) |
+5
-7
| { | ||
| "name": "maildev", | ||
| "description": "SMTP Server and Web Interface for reading and testing emails during development", | ||
| "version": "2.0.2", | ||
| "private": false, | ||
| "version": "2.0.3", | ||
| "keywords": [ | ||
@@ -48,5 +47,5 @@ "email", | ||
| "css-watch": "node-sass -wr --output-style compressed -o app/styles assets/styles/style.scss", | ||
| "docker-build": "docker build -t soulteary/maildev:$npm_package_version . && docker tag soulteary/maildev:$npm_package_version soulteary/maildev:latest", | ||
| "docker-run": "docker run --rm -p 1080:1080 -p 1025:1025 soulteary/maildev:$npm_package_version", | ||
| "docker-push": "docker push soulteary/maildev:$npm_package_version && docker push soulteary/maildev:latest", | ||
| "docker-build": "./scripts/dockerBuild.sh", | ||
| "docker-run": "docker run --rm -p 1080:1080 -p 1025:1025 maildev/maildev:$npm_package_version", | ||
| "docker-push": "./scripts/dockerPush.sh", | ||
| "update-readme": "node ./scripts/updateUsageREADME.js" | ||
@@ -65,5 +64,5 @@ }, | ||
| "cors": "^2.8.5", | ||
| "dompurify": "^2.3.6", | ||
| "express": "^4.17.3", | ||
| "iconv-lite": "0.5.0", | ||
| "marked": "^4.0.12", | ||
| "mime": "2.4.4", | ||
@@ -74,3 +73,2 @@ "nodemailer": "^6.7.2", | ||
| "socket.io": "4.4.1", | ||
| "turndown": "^7.1.1", | ||
| "uue": "3.1.2" | ||
@@ -77,0 +75,0 @@ }, |
+13
-13
@@ -5,3 +5,3 @@ # MailDev | ||
| **MailDev** is a simple way to test your project's generated emails during development with an easy to use web interface that runs on your machine. Built on top of [Node.js](http://www.nodejs.org). | ||
| **MailDev** is a simple way to test your project's generated email during development, with an easy to use web interface that runs on your machine built on top of [Node.js](http://www.nodejs.org). | ||
@@ -13,7 +13,7 @@  | ||
| If you want to use MailDev with [Docker](https://www.docker.com/), you can use the | ||
| [**soulteary/maildev** image on Docker Hub](https://hub.docker.com/r/soulteary/maildev). | ||
| [**maildev/maildev** image on Docker Hub](https://hub.docker.com/r/maildev/maildev). | ||
| For a guide for usage with Docker, | ||
| [checkout the docs](https://github.com/maildev/maildev/blob/master/docs/docker.md). | ||
| $ docker run -p 1080:1080 -p 1025:1025 soulteary/maildev | ||
| $ docker run -p 1080:1080 -p 1025:1025 maildev/maildev | ||
@@ -28,5 +28,5 @@ ## Usage | ||
| | -------------------------------- | -------------------------- | ----------------------------------------------------------------------------------------- | | ||
| | `-s, --smtp <port>` | `MAILDEV_SMTP_PORT` | SMTP port to catch emails | | ||
| | `-s, --smtp <port>` | `MAILDEV_SMTP_PORT` | SMTP port to catch mail | | ||
| | `-w, --web <port>` | `MAILDEV_WEB_PORT` | Port to run the Web GUI | | ||
| | `--mail-directory <path>` | `MAILDEV_MAIL_DIRECTORY` | Directory for persisting mails | | ||
| | `--mail-directory <path>` | `MAILDEV_MAIL_DIRECTORY` | Directory for persisting mail | | ||
| | `--https` | `MAILDEV_HTTPS` | Switch from http to https protocol | | ||
@@ -36,11 +36,11 @@ | `--https-key <file>` | `MAILDEV_HTTPS_KEY` | The file path to the ssl private key | | ||
| | `--ip <ip address>` | `MAILDEV_IP` | IP Address to bind SMTP service to | | ||
| | `--outgoing-host <host>` | `MAILDEV_OUTGOING_HOST` | SMTP host for outgoing emails | | ||
| | `--outgoing-port <port>` | `MAILDEV_OUTGOING_PORT` | SMTP port for outgoing emails | | ||
| | `--outgoing-user <user>` | `MAILDEV_OUTGOING_USER` | SMTP user for outgoing emails | | ||
| | `--outgoing-pass <password>` | `MAILDEV_OUTGOING_PASS` | SMTP password for outgoing emails | | ||
| | `--outgoing-secure` | `MAILDEV_OUTGOING_SECURE` | Use SMTP SSL for outgoing emails | | ||
| | `--outgoing-host <host>` | `MAILDEV_OUTGOING_HOST` | SMTP host for outgoing mail | | ||
| | `--outgoing-port <port>` | `MAILDEV_OUTGOING_PORT` | SMTP port for outgoing mail | | ||
| | `--outgoing-user <user>` | `MAILDEV_OUTGOING_USER` | SMTP user for outgoing mail | | ||
| | `--outgoing-pass <password>` | `MAILDEV_OUTGOING_PASS` | SMTP password for outgoing mail | | ||
| | `--outgoing-secure` | `MAILDEV_OUTGOING_SECURE` | Use SMTP SSL for outgoing mail | | ||
| | `--auto-relay [email]` | `MAILDEV_AUTO_RELAY` | Use auto-relay mode. Optional relay email address | | ||
| | `--auto-relay-rules <file>` | `MAILDEV_AUTO_RELAY_RULES` | Filter rules for auto relay mode | | ||
| | `--incoming-user <user>` | `MAILDEV_INCOMING_USER` | SMTP user for incoming emails | | ||
| | `--incoming-pass <pass>` | `MAILDEV_INCOMING_PASS` | SMTP password for incoming emails | | ||
| | `--incoming-user <user>` | `MAILDEV_INCOMING_USER` | SMTP user for incoming mail | | ||
| | `--incoming-pass <pass>` | `MAILDEV_INCOMING_PASS` | SMTP password for incoming mail | | ||
| | `--web-ip <ip address>` | `MAILDEV_WEB_IP` | IP Address to bind HTTP service to, defaults to --ip | | ||
@@ -55,3 +55,3 @@ | `--web-user <user>` | `MAILDEV_WEB_USER` | HTTP user for GUI | | ||
| | `--silent` | | | | ||
| | `--log-mail-contents` | | Log a JSON representation of each incoming mail | | ||
| | `--log-mail-contents` | | Log a JSON representation of each incoming mail | | ||
@@ -58,0 +58,0 @@ ## API |
Sorry, the diff of this file is too big to display
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 3 instances in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 3 instances in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
5309707
15.78%14
-6.67%83
1.22%47409
69.06%1
Infinity%+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed