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  }