Skip to content

Commit

Permalink
unescape html output for human view
Browse files Browse the repository at this point in the history
  • Loading branch information
davidnewhall committed Jul 8, 2024
1 parent bc3bb27 commit 6ed8c3d
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 36 deletions.
16 changes: 8 additions & 8 deletions pkg/client/instance_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,10 @@ func testTCP(ctx context.Context, svc *services.Service) (string, int) {

res := svc.CheckOnly(ctx)
if res.State != services.StateOK {
return res.State.String() + " " + res.Output, http.StatusBadGateway
return res.State.String() + " " + res.Output.String(), http.StatusBadGateway
}

return "TCP Port is OPEN and reachable: " + res.Output, http.StatusOK
return "TCP Port is OPEN and reachable: " + res.Output.String(), http.StatusOK
}

func testHTTP(ctx context.Context, svc *services.Service) (string, int) {
Expand All @@ -355,11 +355,11 @@ func testHTTP(ctx context.Context, svc *services.Service) (string, int) {

res := svc.CheckOnly(ctx)
if res.State != services.StateOK {
return res.State.String() + " " + res.Output, http.StatusBadGateway
return res.State.String() + " " + res.Output.String(), http.StatusBadGateway
}

// add test
return "HTTP Response Code Acceptable! " + res.Output, http.StatusOK
return "HTTP Response Code Acceptable! " + res.Output.String(), http.StatusOK
}

func testProcess(ctx context.Context, svc *services.Service) (string, int) {
Expand All @@ -369,10 +369,10 @@ func testProcess(ctx context.Context, svc *services.Service) (string, int) {

res := svc.CheckOnly(ctx)
if res.State != services.StateOK {
return res.State.String() + " " + res.Output, http.StatusBadGateway
return res.State.String() + " " + res.Output.String(), http.StatusBadGateway
}

return "Process Tested OK: " + res.Output, http.StatusOK
return "Process Tested OK: " + res.Output.String(), http.StatusOK
}

func testPing(ctx context.Context, svc *services.Service) (string, int) {
Expand All @@ -382,10 +382,10 @@ func testPing(ctx context.Context, svc *services.Service) (string, int) {

res := svc.CheckOnly(ctx)
if res.State != services.StateOK {
return res.State.String() + " " + res.Output, http.StatusBadGateway
return res.State.String() + " " + res.Output.String(), http.StatusBadGateway
}

return "Ping Tested OK: " + res.Output, http.StatusOK
return "Ping Tested OK: " + res.Output.String(), http.StatusOK
}

func testPlex(ctx context.Context, app *apps.PlexConfig) (string, int) {
Expand Down
8 changes: 4 additions & 4 deletions pkg/services/check_ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (s *Service) checkPING() *result {
if err != nil {
return &result{
state: StateUnknown,
output: "invalid ping value: " + err.Error(),
output: &Output{str: "invalid ping value: " + err.Error()},
}
}

Expand All @@ -89,7 +89,7 @@ func (s *Service) checkPING() *result {
if err = pinger.Run(); err != nil { // blocks.
return &result{
state: StateCritical,
output: "error pinging service: " + err.Error(),
output: &Output{str: "error pinging service: " + err.Error()},
}
}

Expand All @@ -105,8 +105,8 @@ func (s *Service) checkPING() *result {

return &result{
state: state,
output: fmt.Sprintf("(%s) pkts sent:%d, rcvd:%d, loss:%.01f, max:%s, avg:%s",
output: &Output{str: fmt.Sprintf("(%s) pkts sent:%d, rcvd:%d, loss:%.01f, max:%s, avg:%s",
msg, stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss,
stats.MaxRtt.Round(time.Millisecond), stats.AvgRtt.Round(time.Millisecond)),
stats.MaxRtt.Round(time.Millisecond), stats.AvgRtt.Round(time.Millisecond))},
}
}
12 changes: 6 additions & 6 deletions pkg/services/check_proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (s *Service) checkProccess(ctx context.Context) *result {
if err != nil {
return &result{
state: StateUnknown,
output: "process list error: " + err.Error(),
output: &Output{str: "process list error: " + err.Error()},
}
}

Expand Down Expand Up @@ -106,8 +106,8 @@ func (s *Service) getProcessResults(ctx context.Context, processes []*process.Pr
if !procinfo.Created.IsZero() && s.svc.proc.restarts && time.Since(procinfo.Created) < s.Interval.Duration {
return &result{
state: StateCritical,
output: fmt.Sprintf("%s: process restarted since last check, age: %v, pid: %d, proc: %s",
s.Value, time.Since(procinfo.Created), proc.Pid, procinfo.CmdLine),
output: &Output{str: fmt.Sprintf("%s: process restarted since last check, age: %v, pid: %d, proc: %s",
s.Value, time.Since(procinfo.Created), proc.Pid, procinfo.CmdLine)},
}
}
}
Expand All @@ -128,17 +128,17 @@ func (s *Service) checkProcessCounts(pids []int32, ages []time.Time) *result {
case count < s.svc.proc.countMin: // not enough running!
return &result{
state: StateCritical,
output: fmt.Sprintf("%s: found %d processes; %s%s%s%s", s.Value, count, min, max, age, pid),
output: &Output{str: fmt.Sprintf("%s: found %d processes; %s%s%s%s", s.Value, count, min, max, age, pid)},
}
case s.svc.proc.running && count > 0: // running but should not be!
return &result{
state: StateCritical,
output: fmt.Sprintf("%s: found %d processes; expected: 0%s%s", s.Value, count, age, pid),
output: &Output{str: fmt.Sprintf("%s: found %d processes; expected: 0%s%s", s.Value, count, age, pid)},
}
default: // running within thresholds!
return &result{
state: StateOK,
output: fmt.Sprintf("%s: found %d processes; %s%s%s%s", s.Value, count, min, max, age, pid),
output: &Output{str: fmt.Sprintf("%s: found %d processes; %s%s%s%s", s.Value, count, min, max, age, pid)},
}
}
}
Expand Down
32 changes: 16 additions & 16 deletions pkg/services/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const (
)

type result struct {
output string
output *Output
state CheckState
}

Expand Down Expand Up @@ -185,15 +185,15 @@ func (s *Service) checkHTTPReq(ctx context.Context) (*http.Client, *http.Request
func (s *Service) checkHTTP(ctx context.Context) *result {
res := &result{
state: StateUnknown,
output: "unknown",
output: &Output{str: "unknown"},
}

ctx, cancel := context.WithTimeout(ctx, s.Timeout.Duration)
defer cancel()

client, req, err := s.checkHTTPReq(ctx)
if err != nil {
res.output = "creating request: " + RemoveSecrets(s.Value, err.Error())
res.output = &Output{str: "creating request: " + RemoveSecrets(s.Value, err.Error())}
return res
}

Expand All @@ -202,39 +202,39 @@ func (s *Service) checkHTTP(ctx context.Context) *result {

resp, err := client.Do(req)
if err != nil {
res.output = "making request: " + RemoveSecrets(s.Value, err.Error())
res.output = &Output{str: "making request: " + RemoveSecrets(s.Value, err.Error())}
return res
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
res.output = "reading body: " + RemoveSecrets(s.Value, err.Error())
res.output = &Output{str: "reading body: " + RemoveSecrets(s.Value, err.Error())}
return res
}

for _, code := range strings.Split(s.Expect, expectdelim) {
if strconv.Itoa(resp.StatusCode) == strings.TrimSpace(code) {
res.state = StateOK
res.output = resp.Status
res.output = &Output{str: resp.Status}

return res
}
}

// Reduce the size of the string before processing it to speed things up on large body outputs.
if len(res.output) > maxOutput+maxOutput {
res.output = res.output[:maxOutput+maxOutput]
if len(res.output.str) > maxOutput+maxOutput {
res.output.str = res.output.str[:maxOutput+maxOutput]
}

res.state = StateCritical
res.output = resp.Status + ": " + strings.TrimSpace(
html.EscapeString(strings.Join(strings.Fields(RemoveSecrets(s.Value, string(body))), " ")))
res.output = &Output{esc: true, str: resp.Status + ": " + strings.TrimSpace(
html.EscapeString(strings.Join(strings.Fields(RemoveSecrets(s.Value, string(body))), " ")))}

// Reduce the string to the final max length.
// We do it this way so all secrets are properly escaped before string splitting.
if len(res.output) > maxOutput {
res.output = res.output[:maxOutput]
if len(res.output.str) > maxOutput {
res.output.str = res.output.str[:maxOutput]
}

return res
Expand All @@ -259,21 +259,21 @@ func RemoveSecrets(appURL, message string) string {
func (s *Service) checkTCP() *result {
res := &result{
state: StateUnknown,
output: "unknown",
output: &Output{str: "unknown"},
}

switch conn, err := net.DialTimeout("tcp", s.Value, s.Timeout.Duration); {
case err != nil:
res.state = StateCritical
res.output = "connection error: " + err.Error()
res.output = &Output{str: "connection error: " + err.Error()}
case conn == nil:
res.state = StateUnknown
res.output = "connection failed, no specific error"
res.output = &Output{str: "connection failed, no specific error"}
default:
conn.Close()

res.state = StateOK
res.output = "connected to port " + strings.Split(s.Value, ":")[1] + " OK"
res.output = &Output{str: "connected to port " + strings.Split(s.Value, ":")[1] + " OK"}
}

return res
Expand Down
25 changes: 23 additions & 2 deletions pkg/services/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package services

import (
"fmt"
"html"
"sync"
"time"

Expand Down Expand Up @@ -87,7 +88,7 @@ type Results struct {
type CheckResult struct {
Name string `json:"name"` // "Radarr"
State CheckState `json:"state"` // 0 = OK, 1 = Warn, 2 = Crit, 3 = Unknown
Output string `json:"output"` // metadata message
Output *Output `json:"output"` // metadata message must never be nil.
Type CheckType `json:"type"` // http, tcp, ping
Time time.Time `json:"time"` // when it was checked, rounded to Microseconds
Since time.Time `json:"since"` // how long it has been in this state, rounded to Microseconds
Expand All @@ -112,7 +113,7 @@ type Service struct {
}

type service struct {
Output string `json:"output"`
Output *Output `json:"output"`
State CheckState `json:"state"`
Since time.Time `json:"since"`
LastCheck time.Time `json:"lastCheck"`
Expand All @@ -121,3 +122,23 @@ type service struct {
ping *pingExpect // only used for icmp/udp ping checks.
sync.RWMutex `json:"-"`
}

type Output struct {
str string // output string
esc bool // html escaped?
}

func (o *Output) String() string {
switch {
case o == nil:
return ""
case o.esc:
return html.UnescapeString(o.str)
default:
return o.str
}
}

func (o *Output) MarshalJSON() ([]byte, error) {
return []byte(`"` + o.String() + `"`), nil
}

0 comments on commit 6ed8c3d

Please sign in to comment.