diff --git a/.mkdocs.yml b/.mkdocs.yml index 3e90eec1..0e8ca1dd 100644 --- a/.mkdocs.yml +++ b/.mkdocs.yml @@ -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 diff --git a/docs/reference/modifiers/header.md b/docs/reference/modifiers/header.md new file mode 100644 index 00000000..58b54104 --- /dev/null +++ b/docs/reference/modifiers/header.md @@ -0,0 +1,21 @@ +# Header Modifiers + +## Adding a new header + +`add_header` module modifies the message by adding an header. + +Note: Adding a header with an existing key will create multiple entries for that key in the message. + +Definition: + +``` +add_header +``` + +Use examples: + +``` +modify { + add_header X-My-Header "header value" +} +``` diff --git a/internal/modify/add_header.go b/internal/modify/add_header.go new file mode 100644 index 00000000..0492bcd0 --- /dev/null +++ b/internal/modify/add_header.go @@ -0,0 +1,89 @@ +/* +Maddy Mail Server - Composable all-in-one email server. +Copyright © 2019-2020 Max Mazurov , 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 . +*/ + +package modify + +import ( + "context" + "errors" + + "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 { + h.Add(r.headerName, r.headerValue) + return nil +} + +func (r addHeader) Close() error { + return nil +} + +func init() { + module.Register("modify.add_header", NewAddHeader) +} diff --git a/internal/modify/add_header_test.go b/internal/modify/add_header_test.go new file mode 100644 index 00000000..48ca7351 --- /dev/null +++ b/internal/modify/add_header_test.go @@ -0,0 +1,82 @@ +/* +Maddy Mail Server - Composable all-in-one email server. +Copyright © 2019-2020 Max Mazurov , 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 . +*/ + +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, expectedValues []string) { + 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", "") + testHdr.Add("Subject", "heya") + testHdr.Add("To", "") + body := []byte("hello there\r\n") + + err = state.RewriteBody(context.Background(), &testHdr, buffer.MemoryBuffer{Slice: body}) + if err != nil { + t.Fatal(err) + } + 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) + } + + for _, wantedValue := range expectedValues { + if !strings.Contains(fullBody.String(), fmt.Sprintf("%s: %s", headerName, wantedValue)) { + t.Fatalf("new header not found in message") + } + } + + } + + test("Something", "Somevalue", []string{"Somevalue"}) + test("X-Testing", "Somevalue", []string{"Somevalue"}) + // Test setting a header that is already present should result two entries + test("To", "Somevalue", []string{"", "Somevalue"}) +}