Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/content/configuration/http_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,10 @@ A few environment variables will be available to construct the url and the body:
- `PROFILE_COMMAND`: backup, check, forget, etc.

Additionally, for the `send-after-fail` hooks, these environment variables will be available:
- `ERROR` containing the latest error message
- `ERROR_COMMANDLINE` containing the command line that failed
- `ERROR` containing the latest error message (URL Encoded)
- `ERROR_COMMANDLINE` containing the command line that failed (URL Encoded)
- `ERROR_EXIT_CODE` containing the exit code of the command line that failed
- `ERROR_STDERR` containing any message that the failed command sent to the standard error (stderr)
- `ERROR_STDERR` containing any message that the failed command sent to the standard error (stderr) (URL Encoded)

The `send-finally` hooks are also getting the environment of `send-after-fail` when any previous operation has failed (except any `send` operation).

Expand Down
42 changes: 36 additions & 6 deletions monitor/hook/sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"io"
"net/http"
urlpkg "net/url"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -74,8 +75,8 @@ func (s *Sender) Send(cfg config.SendMonitoringSection, ctx Context) error {
if cfg.URL.Value() == "" {
return errors.New("URL field is empty")
}
url := resolve(cfg.URL.Value(), ctx)
publicUrl := resolve(cfg.URL.String(), ctx)
url := resolveURL(cfg.URL.Value(), ctx)
publicUrl := resolveURL(cfg.URL.String(), ctx)
method := cfg.Method
if method == "" {
method = http.MethodGet
Expand All @@ -93,7 +94,7 @@ func (s *Sender) Send(cfg config.SendMonitoringSection, ctx Context) error {
bodyReader = bytes.NewBufferString(body)
}
if cfg.Body != "" {
body = resolve(cfg.Body, ctx)
body = resolveBody(cfg.Body, ctx)
bodyReader = bytes.NewBufferString(body)
}

Expand Down Expand Up @@ -202,8 +203,8 @@ func getRootCAs(certificates []string) *x509.CertPool {
return caCertPool
}

func resolve(body string, ctx Context) string {
body = os.Expand(body, func(s string) string {
func resolveBody(body string, ctx Context) string {
return os.Expand(body, func(s string) string {
switch s {
case constants.EnvProfileName:
return ctx.ProfileName
Expand All @@ -230,7 +231,36 @@ func resolve(body string, ctx Context) string {
return os.Getenv(s)
}
})
return body
}

func resolveURL(url string, ctx Context) string {
return os.Expand(url, func(s string) string {
switch s {
case constants.EnvProfileName:
return ctx.ProfileName

case constants.EnvProfileCommand:
return ctx.ProfileCommand

case constants.EnvError:
return urlpkg.QueryEscape(ctx.Error.Message)

case constants.EnvErrorCommandLine:
return urlpkg.QueryEscape(ctx.Error.CommandLine)

case constants.EnvErrorExitCode:
return ctx.Error.ExitCode

case constants.EnvErrorStderr:
return urlpkg.QueryEscape(ctx.Error.Stderr)

case "$":
return "$" // allow to escape "$" as "$$"

default:
return os.Getenv(s)
}
})
}

func loadBodyTemplate(filename string, ctx Context) (string, error) {
Expand Down
45 changes: 45 additions & 0 deletions monitor/hook/sender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/creativeprojects/resticprofile/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/creativeprojects/resticprofile/constants"
)

func TestSend(t *testing.T) {
Expand Down Expand Up @@ -273,6 +274,50 @@ func TestConfidentialURL(t *testing.T) {
assert.Equal(t, 1, calls)
}

// TODO: check if env vars have changed
func TestURLEncoding(t *testing.T) {
ctx := Context{
ProfileName: "unused",
ProfileCommand: "unused",
Error: ErrorContext{
Message: "some/error/message",
CommandLine: "some < tricky || command & line",
ExitCode: "1",
Stderr: "some\nmultiline\nerror\nwith strange &/~!^., characters",
},
Stdout: "unused",
}

calls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()

assert.Equal(t, ctx.Error.Message, query.Get("message"))
assert.Equal(t, ctx.Error.CommandLine, query.Get("command_line"))
assert.Equal(t, ctx.Error.ExitCode, query.Get("exit_code"))
assert.Equal(t, ctx.Error.Stderr, query.Get("stderr"))

calls++
}))
defer server.Close()

serverURL := fmt.Sprintf(
"%s/?message=$%s&command_line=$%s&exit_code=$%s&stderr=$%s",
server.URL,
constants.EnvError,
constants.EnvErrorCommandLine,
constants.EnvErrorExitCode,
constants.EnvErrorStderr,
)

sender := NewSender(nil, "", 300*time.Millisecond, false)
err := sender.Send(config.SendMonitoringSection{
URL: config.NewConfidentialValue(serverURL),
}, ctx)
assert.NoError(t, err)
assert.Equal(t, 1, calls)
}

func TestConfidentialHeader(t *testing.T) {
clog.SetTestLog(t)
defer clog.CloseTestLog()
Expand Down
Loading