Skip to content

Commit

Permalink
SMTP: Adding postMessage-Endpoint and necessary prerequisites
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas Hinderberger committed Jul 4, 2024
1 parent 822e78f commit 307fcff
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 2 deletions.
69 changes: 67 additions & 2 deletions internal/smtp/http.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package smtp

import (
"bytes"
_ "embed"
"encoding/json"
"fmt"
"html/template"
"io"
"net/http"
"net/mail"
"path"
"regexp"
"strconv"
"strings"
"time"

"github.com/Masterminds/sprig/v3"
"github.com/programmfabrik/apitest/internal/handlerutil"
Expand Down Expand Up @@ -61,9 +65,12 @@ func (h *smtpHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// We now know that pathParts must have at least length 1, since empty path
// was already handled above.

if pathParts[0] == "gui" {
switch pathParts[0] {
case "gui":
h.routeGUIEndpoint(w, r, pathParts)
} else {
case "postmessage":
h.handlePostMessage(w, r)
default:
h.routeMessageEndpoint(w, r, pathParts)
}
}
Expand Down Expand Up @@ -289,6 +296,64 @@ func (h *smtpHTTPHandler) handleMultipartMeta(
handlerutil.RespondWithJSON(w, http.StatusOK, buildMultipartMeta(part))
}

func (h *smtpHTTPHandler) handlePostMessage(w http.ResponseWriter, r *http.Request) {
maxMessageSize := h.server.maxMessageSize
if maxMessageSize == 0 {
maxMessageSize = DefaultMaxMessageSize
}

if r.Method != http.MethodPost {
handlerutil.RespondWithErr(
w, http.StatusMethodNotAllowed,
fmt.Errorf("postmessage only accepts POST requests"),
)
return
}

rawMessageData, err := io.ReadAll(io.LimitReader(r.Body, maxMessageSize))
if err != nil {
handlerutil.RespondWithErr(
w, http.StatusBadRequest,
fmt.Errorf("error reading body: %w", err),
)
return
}

// Ensure line endings are CRLF
rawMessageData = bytes.ReplaceAll(rawMessageData, []byte("\r\n"), []byte("\n"))
rawMessageData = bytes.ReplaceAll(rawMessageData, []byte("\n"), []byte("\r\n"))

parsedRfcMsg, err := mail.ReadMessage(bytes.NewReader(rawMessageData))
if err != nil {
handlerutil.RespondWithErr(
w, http.StatusBadRequest,
fmt.Errorf("postmessage could not parse message: %w", err),
)
return
}

from := parsedRfcMsg.Header.Get("From")
rcptTo := parsedRfcMsg.Header["To"]
receivedAt := time.Now()

msg, err := NewReceivedMessageFromParsed(
0, // the index will be overriden by Server.AppendMessage below
from, rcptTo, rawMessageData, receivedAt, maxMessageSize,
parsedRfcMsg,
)
if err != nil {
handlerutil.RespondWithErr(
w, http.StatusBadRequest,
fmt.Errorf("postmessage could not build ReceivedMessage: %w", err),
)
return
}

h.server.AppendMessage(msg)

w.WriteHeader(http.StatusOK)
}

// retrieveMessage attempts to retrieve the message referenced by the given index (still in string
// form at this point). If the index could not be read or the message could not be retrieved,
// an according error message will be returned via HTTP.
Expand Down
13 changes: 13 additions & 0 deletions internal/smtp/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ func NewReceivedMessage(
return nil, fmt.Errorf("could not parse message: %w", err)
}

return NewReceivedMessageFromParsed(
index, from, rcptTo, rawMessageData, receivedAt, maxMessageSize, parsedMsg,
)
}

// NewReceivedMessageFromParsed creates a ReceivedMessage from an already parsed email.
//
// See the documentation of NewReceivedMessage for more details.
func NewReceivedMessageFromParsed(
index int,
from string, rcptTo []string, rawMessageData []byte, receivedAt time.Time,
maxMessageSize int64, parsedMsg *mail.Message,
) (*ReceivedMessage, error) {
content, err := NewReceivedContent(parsedMsg.Header, parsedMsg.Body, maxMessageSize)
if err != nil {
return nil, fmt.Errorf("could not parse content: %w", err)
Expand Down
12 changes: 12 additions & 0 deletions internal/smtp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ func NewServer(addr string, maxMessageSize int64) *Server {
return server
}

// AppendMessage adds a custom message to the Server's storage.
//
// The index of the provided message will be updated to the index at which
// it was actually inserted into the Server's storage.
func (s *Server) AppendMessage(msg *ReceivedMessage) {
s.mutex.Lock()
defer s.mutex.Unlock()

msg.index = len(s.receivedMessages)
s.receivedMessages = append(s.receivedMessages, msg)
}

// ListenAndServe runs the SMTP server. It will not return until the server is
// shut down or otherwise aborts.
func (s *Server) ListenAndServe() error {
Expand Down

0 comments on commit 307fcff

Please sign in to comment.