Skip to content

Commit

Permalink
feat(modify): new modifier add_header
Browse files Browse the repository at this point in the history
  • Loading branch information
msessa committed Mar 2, 2025
1 parent 9cf2adb commit 877e9a0
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 0 deletions.
1 change: 1 addition & 0 deletions .mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ nav:
- SMTP modifiers:
- reference/modifiers/dkim.md
- reference/modifiers/envelope.md
- reference/modifiers/header.md
- Lookup tables (string translation):
- reference/table/static.md
- reference/table/regexp.md
Expand Down
23 changes: 23 additions & 0 deletions docs/reference/modifiers/header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Header Modifiers

## Adding a new header

`add_header` module modifies the message by adding an header.

Note: the header must be present in the message prior to the execution of the modifier.
If the header already exist it will result in an error.


Definition:

```
add_header <headerName> <headerValue>
```

Use examples:

```
modify {
add_header X-My-Header "header value"
}
```
94 changes: 94 additions & 0 deletions internal/modify/add_header.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2020 Max Mazurov <[email protected]>, Maddy Mail Server contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package modify

import (
"context"
"errors"
"fmt"

"github.com/emersion/go-message/textproto"
"github.com/foxcpp/maddy/framework/buffer"
"github.com/foxcpp/maddy/framework/config"
"github.com/foxcpp/maddy/framework/module"
)

// addHeader is a simple module that adds a header to the message
type addHeader struct {
modName string
instName string

headerName string
headerValue string
}

func NewAddHeader(modName, instName string) (module.Module, error) {
r := addHeader{
modName: modName,
instName: instName,
}

return &r, nil
}

func (m *addHeader) Configure(inlineArgs []string, cfg *config.Map) error {
if len(inlineArgs) != 2 {
return errors.New("modify.add_header: at least two arguments required")
}
m.headerName = inlineArgs[0]
m.headerValue = inlineArgs[1]
return nil
}

func (r addHeader) Name() string {
return r.modName
}

func (r addHeader) InstanceName() string {
return r.instName
}

func (r addHeader) ModStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.ModifierState, error) {
return r, nil
}

func (r addHeader) RewriteSender(ctx context.Context, mailFrom string) (string, error) {
return mailFrom, nil
}

func (r addHeader) RewriteRcpt(ctx context.Context, rcptTo string) ([]string, error) {
return []string{rcptTo}, nil
}

func (r addHeader) RewriteBody(ctx context.Context, h *textproto.Header, body buffer.Buffer) error {
if h.Has(r.headerName) {
return fmt.Errorf("cannot add header `%s` as it is already present", r.headerName)
}

h.Set(r.headerName, r.headerValue)
return nil
}

func (r addHeader) Close() error {
return nil
}

func init() {
module.Register("modify.add_header", NewAddHeader)
}
90 changes: 90 additions & 0 deletions internal/modify/add_header_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2020 Max Mazurov <[email protected]>, Maddy Mail Server contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package modify

import (
"bytes"
"context"
"fmt"
"strings"
"testing"

"github.com/emersion/go-message/textproto"
"github.com/foxcpp/maddy/framework/buffer"
"github.com/foxcpp/maddy/framework/config"
"github.com/foxcpp/maddy/framework/module"
)

func TestAddHeader(t *testing.T) {
test := func(headerName string, headerValue string, duplicateHeader bool) {
t.Helper()

mod, err := NewAddHeader("modify.add_header", "")
if err != nil {
t.Fatal(err)
}
m := mod.(*addHeader)
if err := m.Configure([]string{headerName, headerValue}, config.NewMap(nil, config.Node{})); err != nil {
t.Fatal(err)
}

state, err := m.ModStateForMsg(context.Background(), &module.MsgMetadata{})
if err != nil {
t.Fatal(err)
}

testHdr := textproto.Header{}
testHdr.Add("From", "<hello@hello>")
testHdr.Add("Subject", "heya")
testHdr.Add("To", "<heya@heya>")
body := []byte("hello there\r\n")

// modify.dkim expects RewriteSender to be called to get envelope sender
// (see module.Modifier docs)

err = state.RewriteBody(context.Background(), &testHdr, buffer.MemoryBuffer{Slice: body})
if err != nil {
if duplicateHeader && strings.Contains(err.Error(), "already present") {
return
}
t.Fatal(err)
}
if duplicateHeader && err == nil {
t.Fatalf("expected error on duplicate header")
}

var fullBody bytes.Buffer
if err := textproto.WriteHeader(&fullBody, testHdr); err != nil {
t.Fatal(err)
}
if _, err := fullBody.Write(body); err != nil {
t.Fatal(err)
}

if !strings.Contains(fmt.Sprintf("%s", &fullBody), fmt.Sprintf("%s: %s", headerName, headerValue)) {
t.Fatalf("new header not found in message")
}
}

test("Something", "Somevalue", false)
test("X-Testing", "Somevalue", false)
// Test setting a header that is already present
test("To", "Somevalue", true)
test("To", "Somevalue", true)
}

0 comments on commit 877e9a0

Please sign in to comment.