All checks were successful
Build and Publish / release (push) Successful in 4m31s
113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
// This package exists purely to override the net.Listener used
|
|
// by the application's http server. This is necessary for certain versions
|
|
// of firmware which errantly put an 0x0a (LF) following PASSKEY for
|
|
// AmbientWeather type http reporting.
|
|
//
|
|
// This needs to be fixed upstream by Ambient Weather and is a complete
|
|
// hack that should never be necessary. Without this, the http server
|
|
// will silently crank back an HTTP:400
|
|
package ambienthttp
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"regexp"
|
|
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
// Invalid Request Pattern
|
|
var badReqURI = regexp.MustCompile(`PASSKEY=[^\n&]{16,}$`)
|
|
|
|
// Listener encapsulates LFStrippingConn to perform
|
|
// infuriating strip of newline character present after PASSKEY
|
|
// sent errantly by specific versions of firmware sending updates
|
|
// in AmbientWeather protocol
|
|
type LFStrippingListener struct {
|
|
ctx context.Context
|
|
net.Listener
|
|
}
|
|
|
|
type LFStrippingConn struct {
|
|
ctx context.Context
|
|
reader io.Reader
|
|
net.Conn
|
|
}
|
|
|
|
func (l *LFStrippingListener) WrapConn(conn net.Conn) net.Conn {
|
|
buf := new(bytes.Buffer)
|
|
reader := io.TeeReader(conn, buf)
|
|
|
|
scanner := bufio.NewScanner(reader)
|
|
var newData []byte
|
|
for scanner.Scan() {
|
|
line := scanner.Bytes()
|
|
newData = append(newData, line...)
|
|
|
|
// Only restore newline if not a bad request
|
|
if !badReqURI.Match(line) {
|
|
newData = append(newData, '\n')
|
|
} else {
|
|
zerolog.Ctx(l.ctx).Trace().Bytes("line", line).
|
|
Msg("malformed request found, stripped 0x0a")
|
|
}
|
|
|
|
if len(line) == 0 {
|
|
break
|
|
}
|
|
}
|
|
if scanner.Err() != nil {
|
|
zerolog.Ctx(l.ctx).Err(scanner.Err()).Send()
|
|
}
|
|
|
|
zerolog.Ctx(l.ctx).Trace().
|
|
Int("numBytes", len(newData)).
|
|
Bytes("request", newData).
|
|
Msg("stripping conn complete")
|
|
|
|
// Use a multi-reader to prepend the modified request
|
|
finalReader := io.MultiReader(bytes.NewReader(newData), conn)
|
|
|
|
return &LFStrippingConn{
|
|
Conn: conn,
|
|
ctx: l.ctx,
|
|
reader: finalReader,
|
|
}
|
|
}
|
|
|
|
func NewAWNMutatingListener(ctx context.Context, listen string) net.Listener {
|
|
rawListener, err := net.Listen("tcp", listen)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Encapsulate the raw listener with ours
|
|
return &LFStrippingListener{
|
|
Listener: rawListener,
|
|
ctx: ctx,
|
|
}
|
|
}
|
|
|
|
func (l *LFStrippingListener) Accept() (net.Conn, error) {
|
|
conn, err := l.Listener.Accept()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return l.WrapConn(conn), nil
|
|
}
|
|
|
|
func (l *LFStrippingListener) Close() error {
|
|
return l.Listener.Close()
|
|
}
|
|
|
|
func (l *LFStrippingListener) Addr() net.Addr {
|
|
return l.Listener.Addr()
|
|
}
|
|
|
|
func (c *LFStrippingConn) Read(b []byte) (int, error) {
|
|
return c.reader.Read(b)
|
|
}
|