diff --git a/docker/initdb.d/02-data.sql b/docker/initdb.d/02-data.sql index b1fe555..c058e97 100644 --- a/docker/initdb.d/02-data.sql +++ b/docker/initdb.d/02-data.sql @@ -27,6 +27,9 @@ INSERT INTO `mirror_aliases` (`id`, `alias`, `related_product`) VALUES (13,'fire INSERT INTO `mirror_aliases` (`id`, `alias`, `related_product`) VALUES (14,'firefox-esr115-latest-ssl','Firefox-115.16.1esr-SSL'); INSERT INTO `mirror_aliases` (`id`, `alias`, `related_product`) VALUES (15,'firefox-msi-latest-ssl','Firefox-131.0.3-msi-SSL'); INSERT INTO `mirror_aliases` (`id`, `alias`, `related_product`) VALUES (16,'firefox-beta-msi-latest-ssl','Firefox-132.0b9-msi-SSL'); +INSERT INTO `mirror_aliases` (`id`, `alias`, `related_product`) VALUES (17,'firefox-esr115-pkg-latest-ssl','Firefox-115.16.1esr-pkg-SSL'); +INSERT INTO `mirror_aliases` (`id`, `alias`, `related_product`) VALUES (18,'firefox-esr-pkg-latest-ssl','Firefox-128.3.1esr-pkg-SSL'); +INSERT INTO `mirror_aliases` (`id`, `alias`, `related_product`) VALUES (19,'firefox-pkg-latest-ssl','Firefox-133.0-pkg-SSL'); /*!40000 ALTER TABLE `mirror_aliases` ENABLE KEYS */; UNLOCK TABLES; @@ -139,6 +142,11 @@ INSERT INTO `mirror_locations` (`path`, `product_id`, `os_id`, `id`) VALUES ('/f INSERT INTO `mirror_locations` (`path`, `product_id`, `os_id`, `id`) VALUES ('/firefox/nightly/latest-mozilla-central-l10n/firefox-135.0a1.:lang.linux-aarch64.tar.xz',6,6,79); INSERT INTO `mirror_locations` (`path`, `product_id`, `os_id`, `id`) VALUES ('/firefox/nightly/latest-mozilla-central-l10n/firefox-135.0a1.:lang.linux-aarch64.tar.xz',7,6,80); +INSERT INTO `mirror_locations` (`path`, `product_id`, `os_id`, `id`) VALUES ('/firefox/releases/115.16.1esr/mac/:lang/Firefox%20115.16.1esr.dmg',20,2,81); +INSERT INTO `mirror_locations` (`path`, `product_id`, `os_id`, `id`) VALUES ('/firefox/releases/115.16.1esr/mac/:lang/Firefox%20115.16.1esr.pkg',29,2,82); +INSERT INTO `mirror_locations` (`path`, `product_id`, `os_id`, `id`) VALUES ('/firefox/releases/128.3.1esr/mac/:lang/Firefox%20128.3.1esr.dmg',24,2,83); +INSERT INTO `mirror_locations` (`path`, `product_id`, `os_id`, `id`) VALUES ('/firefox/releases/128.3.1esr/mac/:lang/Firefox%20128.3.1esr.pkg',30,2,84); +INSERT INTO `mirror_locations` (`path`, `product_id`, `os_id`, `id`) VALUES ('/firefox/releases/133.0/mac/:lang/Firefox%20133.0.pkg',31,2,85); /*!40000 ALTER TABLE `mirror_locations` ENABLE KEYS */; UNLOCK TABLES; @@ -194,6 +202,9 @@ INSERT INTO `mirror_products` (`count`, `name`, `checknow`, `priority`, `active` INSERT INTO `mirror_products` (`count`, `name`, `checknow`, `priority`, `active`, `id`, `ssl_only`) VALUES (0,'Firefox-nightly-msi-latest-SSL',1,1,1,26,1); INSERT INTO `mirror_products` (`count`, `name`, `checknow`, `priority`, `active`, `id`, `ssl_only`) VALUES (0,'Firefox-115.16.1esr-msi-SSL',1,1,1,27,1); INSERT INTO `mirror_products` (`count`, `name`, `checknow`, `priority`, `active`, `id`, `ssl_only`) VALUES (0,'Thunderbird-131.0.1-SSL',1,1,1,28,1); +INSERT INTO `mirror_products` (`count`, `name`, `checknow`, `priority`, `active`, `id`, `ssl_only`) VALUES (0,'Firefox-115.16.1esr-pkg-SSL',1,1,1,29,1); +INSERT INTO `mirror_products` (`count`, `name`, `checknow`, `priority`, `active`, `id`, `ssl_only`) VALUES (0,'Firefox-128.3.1esr-pkg-SSL',1,1,1,30,1); +INSERT INTO `mirror_products` (`count`, `name`, `checknow`, `priority`, `active`, `id`, `ssl_only`) VALUES (0,'Firefox-133.0-pkg-SSL',1,1,1,31,1); /*!40000 ALTER TABLE `mirror_products` ENABLE KEYS */; UNLOCK TABLES; diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf index d279988..5e3f0fc 100644 --- a/docker/nginx/default.conf +++ b/docker/nginx/default.conf @@ -7,10 +7,10 @@ upstream upstream_bouncer { map $http_user_agent $ua_bucket { default "other"; - "~*Windows (NT 5\.1|XP|NT 5\.2|NT 6\.0)" "winxp"; "NSIS InetBgDL (Mozilla)" "pre2024stub"; "~*Windows NT 6\.(1|2|3).+?(Win64|WOW64)" "win7x64"; "~*Windows NT 6\.(1|2|3)" "win7"; + "~*Macintosh; Intel Mac OS X 10[\._]1(2|3|4)" "oldmacos"; } map $http_referer $referer_bucket { diff --git a/handlers.go b/handlers.go index b9bb25c..459c6dd 100644 --- a/handlers.go +++ b/handlers.go @@ -31,12 +31,19 @@ var ( fxPartnerAlias = regexp.MustCompile(`^partner-firefox-release-([^-]*)-(.*)-latest$`) // detects x64 clients win64Regex = regexp.MustCompile(`Win64|WOW64`) + // detects macOS 10.12 to 10.14 clients. Note that we can't keep doing UA-based + // detection forever... https://bugzilla.mozilla.org/show_bug.cgi?id=1679929 + osxRegexForESR115 = regexp.MustCompile(`Macintosh; Intel Mac OS X 10[\._]1(2|3|4)`) ) -func isUserAgentOnlyCompatibleWithESR115(userAgent string) bool { +func isWindowsUserAgentOnlyCompatibleWithESR115(userAgent string) bool { return windowsRegexForESR115.MatchString(userAgent) } +func isMacOSUserAgentOnlyCompatibleWithESR115(userAgent string) bool { + return osxRegexForESR115.MatchString(userAgent) +} + func hasMozorgReferrer(referrer string) bool { return mozorgRegex.MatchString(referrer) } @@ -45,6 +52,31 @@ func isWin64UserAgent(userAgent string) bool { return win64Regex.MatchString(userAgent) } +func shouldReturnESR115(product, os, referrer, userAgent string) bool { + // We want to return ESR115 when the product is for Firefox + if !strings.HasPrefix(product, "firefox-") || + // and the request doesn't come from mozilla.org + hasMozorgReferrer(referrer) || + // and the product is _not_ a partial or complete update (MAR files) + strings.Contains(product, "-partial") || + strings.Contains(product, "-complete") { + return false + } + + if strings.HasPrefix(os, "win") { + // When the requested OS is Windows, we only return ESR115 for non-MSI + // builds, and only if the User-Agent says it's a Windows 7/8/8.1. + return !strings.Contains(product, "-msi") && isWindowsUserAgentOnlyCompatibleWithESR115(userAgent) + } else if os == "osx" { + // When the requested OS is macOS, we only return ESR115 for non-pkg + // builds, and only if the User-Agent says it's a macOS 10.12/10.13/10.14 + // client. + return !strings.Contains(product, "-pkg") && isMacOSUserAgentOnlyCompatibleWithESR115(userAgent) + } + + return false +} + // isPre2024StubUserAgent is used to detect stub installers that pin the // "DigiCert SHA2 Assured ID Code Signing CA" intermediate. func isPre2024StubUserAgent(userAgent string) bool { @@ -308,22 +340,8 @@ func (b *BouncerHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - // We want to return ESR115 when... the product is for Firefox - shouldReturnESR115 := strings.HasPrefix(reqParams.Product, "firefox-") && - // and the product is _not_ an MSI build - !strings.Contains(reqParams.Product, "-msi") && - // and the product is _not_ a partial or complete update (MAR files) - !strings.Contains(reqParams.Product, "-partial") && - !strings.Contains(reqParams.Product, "-complete") && - // and the OS param specifies windows - strings.HasPrefix(reqParams.OS, "win") && - // and the User-Agent says it's a Windows 7/8/8.1 client - isUserAgentOnlyCompatibleWithESR115(req.UserAgent()) && - // and the request doesn't come from mozilla.org - !hasMozorgReferrer(reqParams.Referer) - // Send the latest compatible ESR product if we detect that this is the best option for the client. - if shouldReturnESR115 { + if shouldReturnESR115(reqParams.Product, reqParams.OS, reqParams.Referer, req.UserAgent()) { // Override the OS if we detect a x64 client that attempts to get a stub installer. if strings.Contains(reqParams.Product, "-stub") && isWin64UserAgent(req.UserAgent()) { reqParams.OS = "win64" diff --git a/handlers_test.go b/handlers_test.go index 05e56cc..d5203bc 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -313,23 +313,67 @@ func TestBouncerHandlerPre2024(t *testing.T) { } } -func TestIsUserAgentOnlyCompatibleWithESR115(t *testing.T) { +func TestIsWindowsUserAgentOnlyCompatibleWithESR115(t *testing.T) { uas := []struct { - UA string - IsWin7 bool + UA string + IsCompatible bool }{ - {"Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko", true}, // IE 64bits Win7 - {"Opera/9.80 (Windows NT 6.1; U; en) Presto/2.7.62 Version/11.01", true}, // Opera 11 Win7 - {"Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", false}, // IE XP - {"Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)", false}, // IE Vista - {"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36 Edg/100.0.1185.36", true}, // Edge Win7 - {"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.71 Safari/537.36", true}, // Chrome Win8 - {"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:124.0) Gecko/20100101 Firefox/124.0", true}, // Firefox Win8.1 - {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36", false}, // Safari Win10 - {"Mozilla/5.0 (Windows NT 611; WOW64; Trident/7.0; rv:11.0) like Gecko", false}, // Bogus + // IE 64bits Win7 + {"Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko", true}, + // Opera 11 Win7 + {"Opera/9.80 (Windows NT 6.1; U; en) Presto/2.7.62 Version/11.01", true}, + // IE XP + {"Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", false}, + // IE Vista + {"Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)", false}, + // Edge Win7 + {"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36 Edg/100.0.1185.36", true}, + // Chrome Win8 + {"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.71 Safari/537.36", true}, + // Firefox Win8.1 + {"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:124.0) Gecko/20100101 Firefox/124.0", true}, + // Safari Win10 + {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36", false}, + // Bogus + {"Mozilla/5.0 (Windows NT 611; WOW64; Trident/7.0; rv:11.0) like Gecko", false}, + // macOS 10.12 + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:109.0) Gecko/20100101 Firefox/115.0", false}, } for _, ua := range uas { - assert.Equal(t, ua.IsWin7, isUserAgentOnlyCompatibleWithESR115(ua.UA), "ua: %v", ua.UA) + assert.Equal(t, ua.IsCompatible, isWindowsUserAgentOnlyCompatibleWithESR115(ua.UA), "ua: %v", ua.UA) + } +} + +func TestIsMacOSUserAgentOnlyCompatibleWithESR115(t *testing.T) { + uas := []struct { + UA string + IsCompatible bool + }{ + // macOS versions < 10.12 + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/601.7.8 (KHTML, like Gecko) Version/9.1.3 Safari/537.86.7", false}, + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", false}, + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9) AppleWebKit/537.71 (KHTML, like Gecko) Version/7.0 Safari/537.71", false}, + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0", false}, + // macOS 10.12 + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:109.0) Gecko/20100101 Firefox/115.0", true}, + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", true}, + // macOS 10.13 + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15", true}, + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:109.0) Gecko/20100101 Firefox/115.0", true}, + // macOS 10.14 + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Safari/605.1.15. As: Safari 12 on macOS (Mojave).", true}, + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0", true}, + // Firefox on iPhone + {"Mozilla/5.0 (iPhone; CPU iPhone OS 12_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/18.2b15817 Mobile/15E148 Safari/605.1.15", false}, + // macOS 10.15 + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15", false}, + // Bogus + {"Mozilla/5.0 (Windows NT 611; WOW64; Trident/7.0; rv:11.0) like Gecko", false}, + // Firefox Win8.1 + {"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:124.0) Gecko/20100101 Firefox/124.0", false}, + } + for _, ua := range uas { + assert.Equal(t, ua.IsCompatible, isMacOSUserAgentOnlyCompatibleWithESR115(ua.UA), "ua: %v", ua.UA) } } @@ -398,6 +442,7 @@ func TestBouncerHandlerForWindowsOnlyCompatibleWithESR115(t *testing.T) { "http://test/?product=firefox-nightly-latest-ssl&os=win&lang=en-US", "http://test/?product=firefox-ssl-latest&os=win&lang=en-US", "http://test/?product=firefox-unknown&os=win&lang=en-US", + "http://test/?product=firefox-esr-latest-ssl&os=win&lang=en-US", } { expectedLocation := "//download-installer.cdn.mozilla.net/pub/firefox/releases/115.16.1esr/win32/en-US/Firefox%20Setup%20115.16.1esr.exe" @@ -419,6 +464,7 @@ func TestBouncerHandlerForWindowsOnlyCompatibleWithESR115(t *testing.T) { "http://test/?product=firefox-nightly-latest-ssl&os=win64&lang=en-US", "http://test/?product=firefox-ssl-latest&os=win64&lang=en-US", "http://test/?product=firefox-unknown&os=win64&lang=en-US", + "http://test/?product=firefox-esr-latest-ssl&os=win64&lang=en-US", } { expectedLocation := "//download-installer.cdn.mozilla.net/pub/firefox/releases/115.16.1esr/win64/en-US/Firefox%20Setup%20115.16.1esr.exe" @@ -435,23 +481,21 @@ func TestBouncerHandlerForWindowsOnlyCompatibleWithESR115(t *testing.T) { // This is for MSI builds. expectedLocation := "https://download-installer.cdn.mozilla.net/pub/firefox/releases/131.0.3/win64/en-US/Firefox%20Setup%20131.0.3.msi" - w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "http://test/?product=firefox-msi-latest-ssl&os=win64&lang=en-US", nil) - req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:124.0) Gecko/20100101 Firefox/124.0") - + req.Header.Set("User-Agent", tc.userAgent) bouncerHandler.ServeHTTP(w, req) - assert.Equal(t, 302, w.Code) assert.Equal(t, expectedLocation, w.Result().Header.Get("Location")) // This is for unrelated products. for _, url := range []string{ "http://test/?product=unknown&os=win&lang=en-US", - "http://test/?product=notfirefox-nightly-latest-ssl&os=win&lang=en-US", - "http://test/?product=thunderbird-something-latest-ssl&os=win&lang=en-US", + "http://test/?product=notfirefox-latest-ssl&os=win&lang=en-US", + "http://test/?product=thunderbird-ssl&os=win&lang=en-US", "http://test/?product=firefox-115.17.0esr-complete&os=win&lang=en-US", "http://test/?product=firefox-115.17.0esr-partial-115.16.1esr&os=win&lang=en-US", + "http://test/?product=firefox-latest-ssl&os=linux&lang=en-US", } { w := httptest.NewRecorder() req, _ := http.NewRequest("GET", url, nil) @@ -461,6 +505,26 @@ func TestBouncerHandlerForWindowsOnlyCompatibleWithESR115(t *testing.T) { assert.Equal(t, 404, w.Code, "userAgent: %v, url: %v", tc.userAgent, url) } + + // This is for a macOS (DMG) build - THe ESR115 override is per OS, which + // is why a win7/8/8.1 client won't receive ESR115 when it requests a build + // with `os=osx`. + expectedLocation = "https://download-installer.cdn.mozilla.net/pub/firefox/releases/128.3.1esr/mac/fr/Firefox%20128.3.1esr.dmg" + w = httptest.NewRecorder() + req, _ = http.NewRequest("GET", "http://test/?product=firefox-esr-latest-ssl&os=osx&lang=fr", nil) + req.Header.Set("User-Agent", tc.userAgent) + bouncerHandler.ServeHTTP(w, req) + assert.Equal(t, 302, w.Code) + assert.Equal(t, expectedLocation, w.Result().Header.Get("Location")) + + // This is for a macOS (pkg) build - Same as above. + expectedLocation = "https://download-installer.cdn.mozilla.net/pub/firefox/releases/128.3.1esr/mac/fr/Firefox%20128.3.1esr.pkg" + w = httptest.NewRecorder() + req, _ = http.NewRequest("GET", "http://test/?product=firefox-esr-pkg-latest-ssl&os=osx&lang=fr", nil) + req.Header.Set("User-Agent", tc.userAgent) + bouncerHandler.ServeHTTP(w, req) + assert.Equal(t, 302, w.Code) + assert.Equal(t, expectedLocation, w.Result().Header.Get("Location")) } } @@ -497,3 +561,126 @@ func TestHealthHandler(t *testing.T) { assert.Equal(t, 200, w.Code) assert.Equal(t, `{"db":true,"healthy":true}`, w.Body.String()) } + +func TestBouncerHandlerForMacOSOnlyCompatibleWithESR115(t *testing.T) { + for _, tc := range []struct { + userAgent string + }{ + // macOS 10.12 + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:109.0) Gecko/20100101 Firefox/115.0"}, + // macOS 10.13 + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15"}, + // macOS 10.14 + {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Safari/605.1.15. As: Safari 12 on macOS (Mojave)."}, + } { + for _, url := range []string{ + "http://test/?product=firefox-beta&os=osx&lang=en-US", + "http://test/?product=firefox-devedition&os=osx&lang=en-US", + "http://test/?product=firefox-nightly-latest-ssl&os=osx&lang=en-US", + "http://test/?product=firefox-ssl-latest&os=osx&lang=en-US", + "http://test/?product=firefox-unknown&os=osx&lang=en-US", + "http://test/?product=firefox-esr-latest-ssl&os=osx&lang=en-US", + } { + expectedLocation := "//download-installer.cdn.mozilla.net/pub/firefox/releases/115.16.1esr/mac/en-US/Firefox%20115.16.1esr.dmg" + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("User-Agent", tc.userAgent) + + bouncerHandler.ServeHTTP(w, req) + + assert.Equal(t, 302, w.Code, "userAgent: %v, url: %v", tc.userAgent, url) + // We don't need to assert the scheme. + assert.True(t, strings.HasSuffix(w.Result().Header.Get("Location"), expectedLocation), "userAgent: %v, url: %v", tc.userAgent, url) + } + + // ESR115 -pkg products + for _, url := range []string{ + "http://test/?product=firefox-esr115-pkg-latest-ssl&os=osx&lang=en-US", + "http://test/?product=firefox-115.16.1esr-pkg-ssl&os=osx&lang=en-US", + } { + expectedLocation := "//download-installer.cdn.mozilla.net/pub/firefox/releases/115.16.1esr/mac/en-US/Firefox%20115.16.1esr.pkg" + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("User-Agent", tc.userAgent) + + bouncerHandler.ServeHTTP(w, req) + + assert.Equal(t, 302, w.Code, "userAgent: %v, url: %v", tc.userAgent, url) + // We don't need to assert the scheme. + assert.True(t, strings.HasSuffix(w.Result().Header.Get("Location"), expectedLocation), "userAgent: %v, url: %v", tc.userAgent, url) + } + + // Latest ESR -pkg products + for _, url := range []string{ + "http://test/?product=firefox-esr-pkg-latest-ssl&os=osx&lang=en-US", + "http://test/?product=firefox-128.3.1esr-pkg-ssl&os=osx&lang=en-US", + } { + expectedLocation := "//download-installer.cdn.mozilla.net/pub/firefox/releases/128.3.1esr/mac/en-US/Firefox%20128.3.1esr.pkg" + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("User-Agent", tc.userAgent) + + bouncerHandler.ServeHTTP(w, req) + + assert.Equal(t, 302, w.Code, "userAgent: %v, url: %v", tc.userAgent, url) + // We don't need to assert the scheme. + assert.True(t, strings.HasSuffix(w.Result().Header.Get("Location"), expectedLocation), "userAgent: %v, url: %v", tc.userAgent, url) + } + + // Latest -pkg product + expectedLocation := "//download-installer.cdn.mozilla.net/pub/firefox/releases/133.0/mac/fr/Firefox%20133.0.pkg" + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "http://test/?product=firefox-pkg-latest-ssl&os=osx&lang=fr", nil) + req.Header.Set("User-Agent", tc.userAgent) + bouncerHandler.ServeHTTP(w, req) + assert.Equal(t, 302, w.Code, "userAgent: %v", tc.userAgent) + // We don't need to assert the scheme. + assert.True(t, strings.HasSuffix(w.Result().Header.Get("Location"), expectedLocation), "userAgent: %v", tc.userAgent) + + // This is for unrelated products. + for _, url := range []string{ + "http://test/?product=unknown&os=osx&lang=en-US", + "http://test/?product=notfirefox-latest-ssl&os=osx&lang=en-US", + "http://test/?product=thunderbird-ssl&os=osx&lang=en-US", + "http://test/?product=firefox-115.17.0esr-complete&os=osx&lang=en-US", + "http://test/?product=firefox-115.17.0esr-partial-115.16.1esr&os=osx&lang=en-US", + "http://test/?product=firefox-latest-ssl&os=linux&lang=en-US", + } { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("User-Agent", tc.userAgent) + + bouncerHandler.ServeHTTP(w, req) + + assert.Equal(t, 404, w.Code, "userAgent: %v, url: %v", tc.userAgent, url) + } + + // This is for a windows build - THe ESR115 override is per OS, which is + // why a macOS 10.12/10.13/10.14 client won't receive ESR115 when it + // requests a build with `os=win`. + expectedLocation = "https://download-installer.cdn.mozilla.net/pub/firefox/releases/128.3.1esr/win32/fr/Firefox%20Setup%20128.3.1esr.exe" + w = httptest.NewRecorder() + req, _ = http.NewRequest("GET", "http://test/?product=firefox-esr-latest-ssl&os=win&lang=fr", nil) + req.Header.Set("User-Agent", tc.userAgent) + bouncerHandler.ServeHTTP(w, req) + assert.Equal(t, 302, w.Code, "userAgent: %v", tc.userAgent) + assert.Equal(t, expectedLocation, w.Result().Header.Get("Location")) + } +} + +func TestBouncerHandlerForMacOSOnlyCompatibleWithESR115WithMozorgReferrer(t *testing.T) { + expectedLocation := "http://download.cdn.mozilla.net/pub/firefox/releases/39.0/mac/en-US/Firefox%2039.0.dmg" + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "http://test/?product=firefox-latest&os=osx&lang=en-US", nil) + req.Header.Set("Referer", "https://www.mozilla.org/") + req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:109.0) Gecko/20100101 Firefox/115.0") + + bouncerHandler.ServeHTTP(w, req) + + assert.Equal(t, 302, w.Code) + assert.Equal(t, expectedLocation, w.Result().Header.Get("Location")) +}