From 37d8d8ba13373163e84700ead76d34b7f058a3b7 Mon Sep 17 00:00:00 2001 From: Benny Date: Sat, 5 Dec 2020 16:42:18 +0800 Subject: [PATCH] iOS 14 support, bump to 0.3.1 (#63) Use `accept` header first, then `user-agent` header. Add complete test case. This should resolve part of #61 --- README.md | 3 ++- config.go | 2 +- helper.go | 32 +++++++++++++++++++++--- helper_test.go | 54 +++++++++++++++++++++++++++++++++++++++- router.go | 9 ++++--- scripts/webp_server.spec | 2 +- 6 files changed, 90 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 4fb7d24..c5f3f5b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ It will convert `jpg,jpeg,png` files by default, this can be customized by editi > e.g When you visit `https://your.website/pics/tsuki.jpg`,it will serve as `image/webp` format without changing the URL. > -> For Safari and Opera users, the original image will be used. +> ~~For Safari and Opera users, the original image will be used.~~ +> We've now support Safari/Chrome/Firefox on iOS 14/iPadOS 14 ## Simple Usage Steps diff --git a/config.go b/config.go index 649a2de..3cb0dee 100644 --- a/config.go +++ b/config.go @@ -21,7 +21,7 @@ var ( prefetch, proxyMode bool config Config - version = "0.3.0" + version = "0.3.1" ) const ( diff --git a/helper.go b/helper.go index 001f40f..d2c393e 100644 --- a/helper.go +++ b/helper.go @@ -143,13 +143,26 @@ func getCompressionRate(RawImagePath string, webpAbsPath string) string { return fmt.Sprintf(`%.2f`, compressionRate) } -func goOrigin(UA string) bool { +func goOrigin(header, ua string) bool { + // We'll first check accept headers, if accept headers is false, we'll then go to UA part + if headerOrigin(header) && uaOrigin(ua) { + return true + } else { + return false + } +} + +func uaOrigin(ua string) bool { + // iOS 14 and iPadOS 14 supports webp, the identification token is iPhone OS 14_2_1 and CPU OS 14_2 // for more information, please check test case - if strings.Contains(UA, "Firefox") || strings.Contains(UA, "Chrome") { + if strings.Contains(ua, "iPhone OS 14") || strings.Contains(ua, "CPU OS 14") { + // this is iOS 14/iPadOS 14 + return false + } else if strings.Contains(ua, "Firefox") || strings.Contains(ua, "Chrome") { // Chrome or firefox on macOS Windows - } else if strings.Contains(UA, "Android") || strings.Contains(UA, "Linux") { + } else if strings.Contains(ua, "Android") || strings.Contains(ua, "Linux") { // on Android and Linux - } else if strings.Contains(UA, "FxiOS") || strings.Contains(UA, "CriOS") { + } else if strings.Contains(ua, "FxiOS") || strings.Contains(ua, "CriOS") { //firefox and Chrome on iOS return true } else { @@ -157,3 +170,14 @@ func goOrigin(UA string) bool { } return false } + +func headerOrigin(header string) bool { + // Webkit is really weird especially on iOS, it doesn't even send out effective accept headers. + // Head to test case if you want to know more + if strings.Contains(header, "image/webp") { + return false + } else { + // go to origin + return true + } +} diff --git a/helper_test.go b/helper_test.go index ff05846..05d1735 100644 --- a/helper_test.go +++ b/helper_test.go @@ -64,9 +64,38 @@ func TestGenEtag(t *testing.T) { } func TestGoOrigin(t *testing.T) { + // this is a complete test case for webp compatibility + // func goOrigin(header, ua string) bool + // UNLESS YOU KNOW WHAT YOU ARE DOING, DO NOT CHANGE THE TEST CASE MAPPING HERE. + var testCase = map[[2]string]bool{ + // macOS Catalina Safari + [2]string{"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15"}: true, + // macOS Chrome + [2]string{"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.88 Safari/537.36"}: false, + // iOS14 Safari + [2]string{"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Mozilla/5.0 (iPhone; CPU iPhone OS 14_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Mobile/15E148 Safari/604.1"}: false, + // iOS14 Chrome + [2]string{"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Mozilla/5.0 (iPhone; CPU iPhone OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1"}: false, + // iPadOS 14 Safari + [2]string{"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Mozilla/5.0 (iPad; CPU OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Mobile/15E148 Safari/604.1"}: false, + // iPadOS 14 Chrome + [2]string{"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Mozilla/5.0 (iPad; CPU OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1"}: false, + // Warning: these three are real capture headers - I don't have iOS/iPadOS prior to version 14 + [2]string{"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15"}: true, + [2]string{"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Mozilla/5.0 (iPad; CPU OS 10_15_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/25.0 Mobile/15E148 Safari/605.1.15"}: true, + [2]string{"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/83.0.4103.63 Mobile/15E148 Safari/604.1"}: true, + } + + for value, is := range testCase { + assert.Equalf(t, is, goOrigin(value[0], value[1]), "[%v]:[%s]", value, is) + } +} + +func TestUaOrigin(t *testing.T) { // reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox // https://developer.chrome.com/multidevice/user-agent#chrome_for_ios_user_agent + // UNLESS YOU KNOW WHAT YOU ARE DOING, DO NOT CHANGE THE TEST CASE MAPPING HERE. var testCase = map[string]bool{ // Chrome on Windows, macOS, linux, iOS and Android "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36": false, @@ -98,14 +127,37 @@ func TestGoOrigin(t *testing.T) { // Others "PostmanRuntime/7.26.1": true, "curl/7.64.1": true, + + // these four are captured from iOS14/iPadOS14, which supports webp + "Mozilla/5.0 (iPhone; CPU iPhone OS 14_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Mobile/15E148 Safari/604.1": false, + "Mozilla/5.0 (iPhone; CPU iPhone OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1\n": false, + "Mozilla/5.0 (iPad; CPU OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Mobile/15E148 Safari/604.1": false, + "Mozilla/5.0 (iPad; CPU OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1": false, } for browser, is := range testCase { - assert.Equalf(t, is, goOrigin(browser), "[%v]:[%s]", is, browser) + assert.Equalf(t, is, uaOrigin(browser), "[%v]:[%s]", is, browser) } } +func TestHeaderOrigin(t *testing.T) { + // Chrome: Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8 + // Safari 版本14.0.1 (15610.2.11.51.10, 15610): Accept: image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5 + // Safari Technology Preview Release 116 (Safari 14.1, WebKit 15611.1.5.3) Accept: image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5 + + // UNLESS YOU KNOW WHAT YOU ARE DOING, DO NOT CHANGE THE TEST CASE MAPPING HERE. + var testCase = map[string]bool{ + "image/avif,image/webp,image/apng,image/*,*/*;q=0.8": false, + "*/*": true, + "image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5": true, + "I don't know what it is:-)": true, + } + for header, is := range testCase { + assert.Equalf(t, is, headerOrigin(header), "[%v]:[%s]", is, header) + } +} + func TestChanErr(t *testing.T) { var value = 2 var testC = make(chan int, 2) diff --git a/router.go b/router.go index 49d0123..4a3ed2f 100644 --- a/router.go +++ b/router.go @@ -19,12 +19,13 @@ func convert(c *fiber.Ctx) error { var rawImageAbs = path.Join(config.ImgPath, reqURI) // /home/xxx/mypic/123.jpg var imgFilename = path.Base(reqURI) // pure filename, 123.jpg var finalFile string // We'll only need one c.sendFile() - var UA = c.Get("User-Agent") - log.Debugf("Incoming connection from %s@%s with %s", UA, c.IP(), imgFilename) + var ua = c.Get("User-Agent") + var accept = c.Get("accept") + log.Debugf("Incoming connection from %s@%s with %s", ua, c.IP(), imgFilename) - needOrigin := goOrigin(UA) + needOrigin := goOrigin(accept, ua) if needOrigin { - log.Infof("A Safari/IE/whatever user has arrived...%s", UA) + log.Infof("A Safari/IE/whatever user has arrived...%s", ua) // Check for Safari users. If they're Safari, just simply ignore everything. etag := genEtag(rawImageAbs) c.Set("ETag", etag) diff --git a/scripts/webp_server.spec b/scripts/webp_server.spec index 6c79e7b..8219670 100644 --- a/scripts/webp_server.spec +++ b/scripts/webp_server.spec @@ -1,5 +1,5 @@ Name: webp-server -Version: 0.3.0 +Version: 0.3.1 Release: 1%{?dist} Summary: Go version of WebP Server. A tool that will serve your JPG/PNGs as WebP format with compression, on-the-fly.