Files
3f5b3cdd98 feat: add configurable SMTP HELO hostname (#2146)
* feat: add configurable SMTP HELO hostname

Allow the SMTP HELO/EHLO hostname to be configured separately from
the SMTP server hostname.

This is useful when the SMTP server requires clients to identify
themselves with a fully qualified hostname different from the server
address.

* chore: remove vendored dependency changes

* Bump go-pkgz/notify to v1.4.0 and document SMTP_HELO_HOST

The HELOHost field lands in go-pkgz/notify v1.4.0, so the branch needs the
bump to compile; v1.3.0 in master has no such field. The example module is
tidied alongside, as any change to backend/go.mod requires.

Documents the parameter in the parameters table and, separately, in the email
setup page: what it does, that leaving it unset keeps the previous `localhost`
greeting, and the case it exists for, a relay refusing the greeting under
Postfix `reject_non_fqdn_helo_hostname`.

Also records the current limit: verification emails for email authentication
go through go-pkgz/auth's own sender, which has no equivalent setting, so the
greeting there is unchanged.

* Bump go-pkgz/auth to v2.2.0 and apply SMTP_HELO_HOST to verification email

The verification email sender had no way to set the greeting, so a relay that
refuses the HELO would accept notifications and still reject sign-in emails.
EmailParams gains HELOHost in go-pkgz/auth v2.2.0, so the same SMTP_HELO_HOST
now drives both paths.

The example module is tidied alongside, as any change to backend/go.mod
requires.

---------

Co-authored-by: oli <someone@somewhere.tld>
Co-authored-by: Dmitry Verkhoturov <paskal.07@gmail.com>
2026-08-19 02:52:39 -05:00

68 lines
2.0 KiB
Go

package stats
import (
"math"
"sort"
)
// Interp calculates the one-dimensional piecewise-linear interpolant to a
// function with given discrete data points (xp, fp), evaluated at each x.
// Values of x below xp[0] return fp[0] and values above xp[len(xp)-1] return
// fp[len(xp)-1], so no extrapolation is performed. Unlike numpy's interp,
// which silently returns nonsense for unsorted coordinates, xp must be
// strictly increasing or ErrBounds is returned. An empty x or xp returns
// ErrEmptyInput and xp and fp of different lengths return ErrSize.
// A NaN in xp returns ErrBounds and a NaN in x gives a NaN in the output.
func Interp(x, xp, fp Float64Data) ([]float64, error) {
if x.Len() == 0 || xp.Len() == 0 {
return nil, ErrEmptyInput
}
if xp.Len() != fp.Len() {
return nil, ErrSize
}
// NaN loses every comparison, so the ordering check can't catch it
for i := 0; i < xp.Len(); i++ {
if math.IsNaN(xp[i]) || (i > 0 && xp[i] <= xp[i-1]) {
return nil, ErrBounds
}
}
output := make([]float64, x.Len())
for i, xv := range x {
switch {
case math.IsNaN(xv):
output[i] = math.NaN()
case xv <= xp[0]:
output[i] = fp[0]
case xv >= xp[xp.Len()-1]:
output[i] = fp[fp.Len()-1]
default:
// The first index with xp[j] >= xv, which the clamping
// above guarantees is within [1, len(xp)-1]
j := sort.SearchFloat64s(xp, xv)
if xv == xp[j] {
// An exact knot hit returns fp[j] exactly, with no
// interpolation arithmetic that could lose precision
output[i] = fp[j]
continue
}
t := (xv - xp[j-1]) / (xp[j] - xp[j-1])
if math.IsInf(xp[j]-xp[j-1], 1) {
// The knot spacing overflows float64, so halve each
// term before dividing; halving is exact for the huge
// values that make an overflowing difference possible
t = (xv/2 - xp[j-1]/2) / (xp[j]/2 - xp[j-1]/2)
}
// The symmetric form stays finite for any finite fp where
// fp[j]-fp[j-1] would overflow, since 0 < t < 1 here
output[i] = fp[j-1]*(1-t) + fp[j]*t
}
}
return output, nil
}