mirror of
https://github.com/webinstall/webi-installers.git
synced 2026-04-06 18:36:50 +00:00
feat(uadetect): add FromRequest for full agent detection
The user agent identifies itself through multiple signals — the User-Agent header and query parameters (?os, ?arch). FromRequest unifies both, with explicit query params taking precedence.
This commit is contained in:
@@ -1,15 +1,19 @@
|
||||
// Package uadetect identifies the requesting system's OS, CPU architecture,
|
||||
// and libc from its User-Agent string.
|
||||
// Package uadetect identifies the requesting agent's OS, CPU architecture,
|
||||
// and libc so the server can select the correct release artifact.
|
||||
//
|
||||
// Webi's bootstrap scripts send "$(uname -srm)" as the User-Agent, e.g.
|
||||
// "Darwin 23.1.0 arm64" or "Linux 6.1.0 x86_64". This package parses those
|
||||
// into [buildmeta.OS], [buildmeta.Arch], and [buildmeta.Libc] values so the
|
||||
// server can select the correct release artifact.
|
||||
// An agent identifies itself through multiple signals:
|
||||
// - The User-Agent header: Webi's bootstrap scripts send "$(uname -srm)",
|
||||
// e.g. "Darwin 23.1.0 arm64". Browsers, curl, and PowerShell send their
|
||||
// own UA strings.
|
||||
// - Query parameters: ?os=linux&arch=arm64 are an explicit declaration
|
||||
// that takes precedence over the header.
|
||||
//
|
||||
// Also handles non-uname agents like "PowerShell/7.3.0" and "MS AMD64".
|
||||
// Use [FromRequest] to detect from an HTTP request (preferred).
|
||||
// Use [Parse] to detect from a raw UA string.
|
||||
package uadetect
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/webinstall/webi-installers/internal/buildmeta"
|
||||
@@ -22,6 +26,27 @@ type Result struct {
|
||||
Libc buildmeta.Libc
|
||||
}
|
||||
|
||||
// FromRequest detects the agent's platform from an HTTP request.
|
||||
// Query parameters ?os and ?arch override the User-Agent header.
|
||||
func FromRequest(r *http.Request) Result {
|
||||
qOS := r.URL.Query().Get("os")
|
||||
qArch := r.URL.Query().Get("arch")
|
||||
|
||||
var ua string
|
||||
switch {
|
||||
case qOS != "" && qArch != "":
|
||||
ua = qOS + " " + qArch
|
||||
case qOS != "":
|
||||
ua = qOS
|
||||
case qArch != "":
|
||||
ua = qArch
|
||||
default:
|
||||
ua = r.Header.Get("User-Agent")
|
||||
}
|
||||
|
||||
return Parse(ua)
|
||||
}
|
||||
|
||||
// Parse extracts OS, arch, and libc from a User-Agent string.
|
||||
func Parse(ua string) Result {
|
||||
if ua == "-" {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package uadetect_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/webinstall/webi-installers/internal/buildmeta"
|
||||
@@ -103,6 +104,58 @@ func TestLibc(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ua string // User-Agent header
|
||||
query string // raw query string
|
||||
wantOS buildmeta.OS
|
||||
wantAr buildmeta.Arch
|
||||
}{
|
||||
{
|
||||
name: "UA header only",
|
||||
ua: "Darwin 23.1.0 arm64",
|
||||
wantOS: buildmeta.OSDarwin,
|
||||
wantAr: buildmeta.ArchARM64,
|
||||
},
|
||||
{
|
||||
name: "query params override UA",
|
||||
ua: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
|
||||
query: "os=linux&arch=aarch64",
|
||||
wantOS: buildmeta.OSLinux,
|
||||
wantAr: buildmeta.ArchARM64,
|
||||
},
|
||||
{
|
||||
name: "os param only",
|
||||
ua: "curl/8.1.2",
|
||||
query: "os=windows",
|
||||
wantOS: buildmeta.OSWindows,
|
||||
},
|
||||
{
|
||||
name: "arch param only",
|
||||
ua: "curl/8.1.2",
|
||||
query: "arch=arm64",
|
||||
wantAr: buildmeta.ArchARM64,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com/api?"+tt.query, nil)
|
||||
if tt.ua != "" {
|
||||
req.Header.Set("User-Agent", tt.ua)
|
||||
}
|
||||
got := uadetect.FromRequest(req)
|
||||
if tt.wantOS != "" && got.OS != tt.wantOS {
|
||||
t.Errorf("OS = %q, want %q", got.OS, tt.wantOS)
|
||||
}
|
||||
if tt.wantAr != "" && got.Arch != tt.wantAr {
|
||||
t.Errorf("Arch = %q, want %q", got.Arch, tt.wantAr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFullParse(t *testing.T) {
|
||||
r := uadetect.Parse("Darwin 23.1.0 arm64")
|
||||
if r.OS != buildmeta.OSDarwin {
|
||||
|
||||
Reference in New Issue
Block a user