Skip to content

Commit

Permalink
SMTP: Intermediate State ReceivedContent refactoring part 4 - Interme…
Browse files Browse the repository at this point in the history
…diate due to Feierabend, will continue and force-push tomorrow
  • Loading branch information
Lucas Hinderberger committed Jul 1, 2024
1 parent cfa9f61 commit 94769d4
Showing 1 changed file with 93 additions and 162 deletions.
255 changes: 93 additions & 162 deletions internal/smtp/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ 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" {
if pathParts[0] == "gui" && len(pathParts) == 1 {
h.handleGUI(w, r)
return
}
Expand All @@ -65,47 +65,88 @@ func (h *smtpHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

switch len(pathParts) {
case 1:
h.handleMessageMeta(w, r, idx)
msg, err := h.server.ReceivedMessage(idx)
if err != nil {
handlerutil.RespondWithErr(w, http.StatusNotFound, err)
return
}

if len(pathParts) == 1 {
h.handleMessageMeta(w, r, msg)
return
}
if len(pathParts) == 2 && pathParts[1] == "raw" {
h.handleMessageRaw(w, r, msg)
return
case 2:
switch pathParts[1] {
}

routeContentEndpoint(w, r, msg.Content(), pathParts[1:])
}

// routeContentEndpoint recursively finds a route for the remaining path parts
// based on the given ReceivedContent.
func (h *smtpHTTPHandler) routeContentEndpoint(
w http.ResponseWriter, r *http.Request, c *ReceivedContent, remainingPathParts []string,
) {
if len(pathParts) == 1 {
switch pathParts[0] {
case "body":
h.handleMessageBody(w, r, idx)
h.handleContentBody(w, r, c)
return
case "multipart":
h.handleMultipartIndex(w, r, idx)
h.handleMultipartIndex(w, r, c)
return
case "raw":
h.handleRawMessageData(w, r, idx)
}
}

if len(pathParts) > 1 && pathParts[0] == "multipart" {
if !c.IsMultipart() {
handlerutil.RespondWithErr(w, http.StatusNotFound, fmt.Errorf(
"multipart endpoint was requested for non-multipart content",
))
}

multiparts := c.Multiparts()

partIdx, err := strconv.Atoi(pathParts[1])
if err != nil {
handlerutil.RespondWithErr(
w, http.StatusBadRequest,
fmt.Errorf("could not parse multipart index: %w", err),
)
return
}
case 3, 4:
if pathParts[1] == "multipart" {
partIdx, err := strconv.Atoi(pathParts[2])
if err != nil {
handlerutil.RespondWithErr(
w, http.StatusBadRequest,
fmt.Errorf("could not parse multipart index: %w", err),
)
return
}

if len(pathParts) == 3 {
h.handleMultipartMeta(w, r, idx, partIdx)
return
} else if pathParts[3] == "body" {
h.handleMultipartBody(w, r, idx, partIdx)
return
}

if partIdx >= len(multiparts) {
handlerutil.RespondWithErr(w, http.StatusNotFound, fmt.Errorf(
"ReceivedContent does not contain multipart with index %d", partIdx,
))
return
}

part := multiparts[partIdx]

if len(pathParts) == 2 {
h.handleMultipartMeta(w, r, part)
return
}

return routeContentEndpoint(w, r, part.Content(), remainingPathParts[2:])
}

// If routing failed, return status 404.
w.WriteHeader(http.StatusNotFound)
}

func (h *smtpHTTPHandler) handleContentBody(w http.ResponseWriter, r *http.Request, c *ReceivedContent) {
contentType, ok := c.Headers()["Content-Type"]
if ok {
w.Header()["Content-Type"] = contentType
}

w.Write(c.Body())
}

func (h *smtpHTTPHandler) handleGUI(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")

Expand Down Expand Up @@ -140,59 +181,36 @@ func (h *smtpHTTPHandler) handleMessageIndex(w http.ResponseWriter, r *http.Requ
handlerutil.RespondWithJSON(w, http.StatusOK, out)
}

func (h *smtpHTTPHandler) handleMessageMeta(w http.ResponseWriter, r *http.Request, idx int) {
msg := h.retrieveMessage(w, idx)
if msg == nil {
return
}

content := msg.Content()

func (h *smtpHTTPHandler) handleMessageMeta(w http.ResponseWriter, r *http.Request, msg *ReceivedMessage) {
out := buildMessageBasicMeta(msg)
contentMeta := buildContentMeta(msg.Content())

out["body_size"] = len(content.Body())

headers := make(map[string]any)
for k, v := range content.Headers() {
headers[k] = v
for k, v := range contentMeta {
out[k] = v
}
out["headers"] = headers

handlerutil.RespondWithJSON(w, http.StatusOK, out)
}

func (h *smtpHTTPHandler) handleMessageBody(w http.ResponseWriter, r *http.Request, idx int) {
func (h *smtpHTTPHandler) handleMessageRaw(w http.ResponseWriter, r *http.Request, idx int) {
msg := h.retrieveMessage(w, idx)
if msg == nil {
return
}

content := msg.Content()

contentType, ok := content.Headers()["Content-Type"]
if ok {
w.Header()["Content-Type"] = contentType
}
w.Header().Set("Content-Type", "message/rfc822")

w.Write(content.Body())
w.Write(msg.RawMessageData())
}

func (h *smtpHTTPHandler) handleMultipartIndex(w http.ResponseWriter, r *http.Request, idx int) {
msg := h.retrieveMessage(w, idx)
if msg == nil {
return
}
if !ensureIsMultipart(w, msg) {
return
}

func (h *smtpHTTPHandler) handleMultipartIndex(w http.ResponseWriter, r *http.Request, c *ReceivedContent) {
headerSearchRgx, err := extractSearchRegex(w, r.URL.Query(), "header")
if err != nil {
handlerutil.RespondWithErr(w, http.StatusBadRequest, err)
return
}

multiparts := msg.Content().Multiparts()
multiparts := c.Multiparts()
if headerSearchRgx != nil {
multiparts = SearchByHeader(multiparts, headerSearchRgx)
}
Expand All @@ -211,88 +229,33 @@ func (h *smtpHTTPHandler) handleMultipartIndex(w http.ResponseWriter, r *http.Re
}

func (h *smtpHTTPHandler) handleMultipartMeta(
w http.ResponseWriter, r *http.Request, idx, partIdx int,
w http.ResponseWriter, r *http.Request, idx, part *ReceivedPart,
) {
msg := h.retrieveMessage(w, idx)
if msg == nil {
return
}
if !ensureIsMultipart(w, msg) {
return
}
part := retrievePart(w, msg, partIdx)
if part == nil {
return
}

handlerutil.RespondWithJSON(w, http.StatusOK, buildMultipartMeta(part))
}

func (h *smtpHTTPHandler) handleMultipartBody(
w http.ResponseWriter, r *http.Request, idx, partIdx int,
) {
msg := h.retrieveMessage(w, idx)
if msg == nil {
return
}

if !ensureIsMultipart(w, msg) {
return
}

part := retrievePart(w, msg, partIdx)
if part == nil {
return
}

content := part.Content()

contentType, ok := content.Headers()["Content-Type"]
if ok {
w.Header()["Content-Type"] = contentType
out := map[string]any{
"idx": part.Index(),
}

w.Write(content.Body())
}
contentMeta := buildContentMeta(part.Content())

func (h *smtpHTTPHandler) handleRawMessageData(w http.ResponseWriter, r *http.Request, idx int) {
msg := h.retrieveMessage(w, idx)
if msg == nil {
return
for k, v := range contentMeta {
out[k] = v
}

w.Header().Set("Content-Type", "message/rfc822")

w.Write(msg.RawMessageData())
handlerutil.RespondWithJSON(w, http.StatusOK, out)
}

// retrieveMessage tries to retrieve the ReceivedMessage with the given index.
// If found, returns the message. If not found, responds with Status 404
// and returns nil.
func (h *smtpHTTPHandler) retrieveMessage(w http.ResponseWriter, idx int) *ReceivedMessage {
msg, err := h.server.ReceivedMessage(idx)
if err != nil {
handlerutil.RespondWithErr(w, http.StatusNotFound, err)
return nil
func buildContentMeta(c *ReceivedContent) map[string]any {
out := map[string]any{
"body_size": len(c.Body()),
}

return msg
}

// retrievePart tries to retrieve the ReceivedPart with the given index.
// If found, returns the part. If not found, responds with Status 404
// and returns nil.
func retrievePart(w http.ResponseWriter, msg *ReceivedMessage, partIdx int) *ReceivedPart {
multiparts := msg.Content().Multiparts()

if partIdx >= len(multiparts) {
handlerutil.RespondWithErr(w, http.StatusNotFound, fmt.Errorf(
"ReceivedMessage does not contain multipart with index %d", partIdx,
))
return nil
headers := make(map[string]any)
for k, v := range c.Headers() {
headers[k] = v
}
out["headers"] = headers

return multiparts[partIdx]
return out
}

func buildMessageBasicMeta(msg *ReceivedMessage) map[string]any {
Expand Down Expand Up @@ -322,38 +285,6 @@ func buildMessageBasicMeta(msg *ReceivedMessage) map[string]any {
return out
}

func buildMultipartMeta(part *ReceivedPart) map[string]any {
content := part.Content()

out := map[string]any{
"idx": part.Index(),
"body_size": len(content.Body()),
}

headers := make(map[string]any)
for k, v := range content.Headers() {
headers[k] = v
}
out["headers"] = headers

return out
}

// ensureIsMultipart checks whether the referenced message is a multipart
// message, returns true and does nothing further if so, returns false after
// replying with Status 404 if not.
func ensureIsMultipart(w http.ResponseWriter, msg *ReceivedMessage) bool {
if msg.Content().IsMultipart() {
return true
}

handlerutil.RespondWithErr(w, http.StatusNotFound, fmt.Errorf(
"multipart information was requested for non-multipart message",
))

return false
}

// extractSearchRegex tries to extract a regular expression from the referenced
// query parameter. If no query parameter is given and otherwise no error has
// occurred, this function returns (nil, nil).
Expand Down

0 comments on commit 94769d4

Please sign in to comment.