github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/middlewares/user_agent.go (about) 1 package middlewares 2 3 import ( 4 "net/http" 5 "strconv" 6 "strings" 7 8 build "github.com/cozy/cozy-stack/pkg/config" 9 "github.com/labstack/echo/v4" 10 "github.com/mssola/user_agent" 11 ) 12 13 const maxInt = int(^uint(0) >> 1) 14 15 // Some constants for the browser names 16 const ( 17 InternetExplorer = "Internet Explorer" 18 Edge = "Edge" 19 Firefox = "Firefox" 20 Chrome = "Chrome" 21 Chromium = "Chromium" 22 Opera = "Opera" 23 Safari = "Safari" 24 Android = "Android" 25 Electron = "Electron" 26 ) 27 28 type iPhoneState int 29 30 const ( 31 iPhoneOrNotIPhone iPhoneState = iota 32 notIphone 33 onlyIphone 34 ) 35 36 // browserRule is a rule for saying which browser is accepted based on the 37 // user-agent. 38 type browserRule struct { 39 name string 40 iPhone iPhoneState 41 minVersion int 42 } 43 44 func (rule *browserRule) canApply(browser string, iPhone bool) bool { 45 if rule.name != browser { 46 return false 47 } 48 if rule.iPhone == notIphone && iPhone { 49 return false 50 } 51 if rule.iPhone == onlyIphone && !iPhone { 52 return false 53 } 54 return true 55 } 56 57 func (rule *browserRule) acceptVersion(rawVersion string) bool { 58 version, ok := GetMajorVersion(rawVersion) 59 if !ok { 60 return true 61 } 62 return version >= rule.minVersion 63 } 64 65 var rules = []browserRule{ 66 // We don't support IE 67 { 68 name: InternetExplorer, 69 iPhone: iPhoneOrNotIPhone, 70 minVersion: maxInt, 71 }, 72 // We don't support Edge before version 80, because we need support for 73 // unicode property escape of regexps, and PBKDF2 in subtle crypto. 74 { 75 name: Edge, 76 iPhone: iPhoneOrNotIPhone, 77 minVersion: 80, 78 }, 79 // Idem for Chrome and Chromium before version 64. 80 { 81 name: Chrome, 82 iPhone: iPhoneOrNotIPhone, 83 minVersion: 64, 84 }, 85 { 86 name: Chromium, 87 iPhone: iPhoneOrNotIPhone, 88 minVersion: 64, 89 }, 90 // We don't support Firefox before version 78, except on iOS where the 91 // webkit engine is used on the version numbers are not the same. 92 { 93 name: Firefox, 94 iPhone: notIphone, 95 minVersion: 78, 96 }, 97 { 98 name: Firefox, 99 iPhone: onlyIphone, 100 minVersion: 34, // Firefox Focus has a lower version number than Firefox for iOS 101 }, 102 // We don't support Safari before version 11, as window.crypto is not 103 // available. 104 { 105 name: Safari, 106 iPhone: iPhoneOrNotIPhone, 107 minVersion: 11, 108 }, 109 } 110 111 // CheckUserAgent is a middleware that shows an HTML page of error when a 112 // browser that is not supported try to load a webapp. 113 func CheckUserAgent(next echo.HandlerFunc) echo.HandlerFunc { 114 return func(c echo.Context) error { 115 ua := user_agent.New(c.Request().UserAgent()) 116 browser, rawVersion := ua.Browser() 117 iPhone := ua.Platform() == "iPhone" 118 acceptHeader := c.Request().Header.Get(echo.HeaderAccept) 119 120 if strings.Contains(acceptHeader, echo.MIMETextHTML) { 121 for _, rule := range rules { 122 if rule.canApply(browser, iPhone) { 123 if rule.acceptVersion(rawVersion) { 124 return next(c) 125 } 126 127 instance := GetInstance(c) 128 return c.Render(http.StatusOK, "compat.html", echo.Map{ 129 "Domain": instance.ContextualDomain(), 130 "ContextName": instance.ContextName, 131 "Locale": instance.Locale, 132 "Favicon": Favicon(instance), 133 }) 134 } 135 } 136 } 137 return next(c) 138 } 139 } 140 141 // GetMajorVersion returns the major version of a browser 142 // 12 => 12 143 // 12.13 => 12 144 func GetMajorVersion(rawVersion string) (int, bool) { 145 splitted := strings.SplitN(rawVersion, ".", 2) 146 v, err := strconv.Atoi(splitted[0]) 147 if err != nil { 148 return -1, false 149 } 150 return v, true 151 } 152 153 // CryptoPolyfill returns true if the browser can't use its window.crypto API 154 // to hash the password with PBKDF2. It is the case in development mode, 155 // because this API is only available in secure more (HTTPS or localhost). 156 func CryptoPolyfill(c echo.Context) bool { 157 req := c.Request() 158 return build.IsDevRelease() && !strings.Contains(req.Host, "localhost") 159 } 160 161 // BottomNavigationBar returns true if the navigation bar of the browser is at 162 // the bottom of the screen (Firefox Mobile). 163 func BottomNavigationBar(c echo.Context) bool { 164 ua := user_agent.New(c.Request().UserAgent()) 165 browser, _ := ua.Browser() 166 return browser == Firefox && ua.Mobile() 167 }