Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: How can I know which browser name is the request from? #8930

Open
warmbook opened this issue Jun 1, 2024 · 14 comments
Open

Bug: How can I know which browser name is the request from? #8930

warmbook opened this issue Jun 1, 2024 · 14 comments

Comments

@warmbook
Copy link

warmbook commented Jun 1, 2024

PHP Version

8.1

CodeIgniter4 Version

4.5.0

CodeIgniter4 Installation Method

Manual (zip or tar.gz)

Which operating systems have you tested for this bug?

Linux

Which server did you use?

apache

Database

No response

What happened?

When I call the method CodeIgniter\HTTP\UserAgent->isBrowser with 'Spartan', if the client browser agent string matched the keyword 'Edge', the method will return false.

Steps to Reproduce

Call CodeIgniter\HTTP\UserAgent->isBrowser with 'Spartan', and request the server by a browser which's agent string includes 'Edge'.

Expected Output

method should return true

Anything else?

By viewing the source code,I found this method only check whether the param $key is existed in user agent string, but not compare it with the property 'browser'. It's not compliant with description in the manual, which say the param $key is 'Optional browser name'. Maybe you should change the statement "if($key === null)" to "if ($key === null||$this->browser === $key)"?
The other two method 'isRobot' and 'isMobile' also have the same problem.

@warmbook warmbook added the bug Verified issues on the current code behavior or pull requests that will fix them label Jun 1, 2024
@kenjis kenjis removed the bug Verified issues on the current code behavior or pull requests that will fix them label Jun 1, 2024
@kenjis
Copy link
Member

kenjis commented Jun 1, 2024

Call isBrowser () without $key.
There is no key Spartan in $browsers:

public array $browsers = [

Note
The string “Safari” in this example is an array key in the list of browser definitions. You can find this list in app/Config/UserAgents.php if you want to add new browsers or change the strings.
https://codeigniter4.github.io/CodeIgniter4/libraries/user_agent.html#CodeIgniter\HTTP\UserAgent::isBrowser

@kenjis
Copy link
Member

kenjis commented Jun 1, 2024

How can I know which browser name is the request from?

In a controller:

$agent = $this->request->getUserAgent();
d($agent->getBrowser());

@kenjis
Copy link
Member

kenjis commented Jun 1, 2024

Ah, the following $agent->isBrowser(...) result is not correct?

        $agent = $this->request->getUserAgent();
        d($agent->getBrowser());
        echo($agent->getAgentString());
        dd($agent->isBrowser('Edge'));
$agent->getBrowser() string (4) "Edge"
⧉Called from .../app/Controllers/Home.php:11 [d()]

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0

$agent->isBrowser(...) boolean false
⧉Called from .../app/Controllers/Home.php:13 [dd()]

@kenjis kenjis added the bug Verified issues on the current code behavior or pull requests that will fix them label Jun 1, 2024
@kenjis
Copy link
Member

kenjis commented Jun 1, 2024

@codeigniter4/core-team
According to the documentation, the parameter for isBrowser() is "Optional browser name".
See https://codeigniter4.github.io/CodeIgniter4/libraries/user_agent.html#CodeIgniter\HTTP\UserAgent::isBrowser

But it is actually the key of Config\UserAgents::$browsers, and the key of $browsers is (a part of) the regex for the browser. So it is NOT a browser name.

protected function setBrowser(): bool
{
if (is_array($this->config->browsers) && $this->config->browsers) {
foreach ($this->config->browsers as $key => $val) {
if (preg_match('|' . $key . '.*?([0-9\.]+)|i', $this->agent, $match)) {
$this->isBrowser = true;
$this->version = $match[1];
$this->browser = $val;
$this->setMobile();
return true;
}
}
}

After all, the current implementation (line 133) seems to be incorrect.
What do you think?

public function isBrowser(?string $key = null): bool
{
if (! $this->isBrowser) {
return false;
}
// No need to be specific, it's a browser
if ($key === null) {
return true;
}
// Check for a specific browser
return isset($this->config->browsers[$key]) && $this->browser === $this->config->browsers[$key];
}

@michalsn
Copy link
Member

michalsn commented Jun 1, 2024

The user guide states:

The string “Safari” in this example is an array key in the list of browser definitions. You can find this list in app/Config/UserAgents.php if you want to add new browsers or change the strings.

https://codeigniter4.github.io/CodeIgniter4/libraries/user_agent.html#CodeIgniter%5CHTTP%5CUserAgent::isBrowser

So, I would say we're good.

@kenjis
Copy link
Member

kenjis commented Jun 1, 2024

Yes, the Note says it is an array key.
But if the current implementation is good, we must write code $agent->isBrowser('Edg') instead of $agent->isBrowser('Edge').
It is difficult to use/understand.

@warmbook
Copy link
Author

warmbook commented Jun 2, 2024

Yes, the Note says it is an array key. But if the current implementation is good, we must write code $agent->isBrowser('Edg') instead of $agent->isBrowser('Edge'). It is difficult to use/understand.

Yes, It's indeed difficult to use/understand. Especially when some browsers can't be specifyed by pure letter string ,such as QQ, we must write code $agent->isBrowser('QQ/'), that's not elegant.

@michalsn
Copy link
Member

michalsn commented Jun 2, 2024

Perhaps, but the behavior is the same since probably v2. There is no bug, the method works correctly.

Feel free to suggest a better option (preferably with PR), but please make it an alternative. The current behavior should remain unchanged - too many apps may rely on it.

@warmbook
Copy link
Author

warmbook commented Jun 2, 2024

In my humble opinion, change the statement "if($key === null)" to "if ($key === null||$this->browser === $key)" may not affect apps rely on it?

@michalsn
Copy link
Member

michalsn commented Jun 2, 2024

I kindly disagree. If we start matching the result based on both: the array key and the value, there is no chance to determine which value was matched.

"Edge" is a great example of this:

'Edge'   => 'Spartan',
'Edg'    => 'Edge',

If we change the code according to your suggestion, it will give unexpected results.

$agent->isBrowser('Edge');

If the above code returns true, we don't know if it's because the browser is indeed "Edge" or "Spartan".

To check a browser by its name, I suggest you simply use:

$agent->getBrowser() === 'Edge'

@kenjis
Copy link
Member

kenjis commented Jun 2, 2024

The format is the same as the Chromium user agent with the addition of a new Edg token at the end. Microsoft chose the Edg token to avoid compatibility issues caused by Edge string, which was previously used for the legacy Microsoft Edge browser based on EdgeHTML. The Edg token is also consistent with existing tokens used for iOS and Android.
https://learn.microsoft.com/en-us/microsoft-edge/web-platform/user-agent-guidance

@kenjis
Copy link
Member

kenjis commented Jun 2, 2024

@michalsn Indeed, if we think the parameter for isBrowser() is an array key (actually it is),
there is no inconsistency.

But if we want to check if it is "Spartan", we must write:

$agent->isBrowser('Edge')

and check if it is "Edge", we must write:

$agent->isBrowser('Edg')

This seems too tricky.

It seems the following is better and simpler:

--- a/system/HTTP/UserAgent.php
+++ b/system/HTTP/UserAgent.php
@@ -130,7 +130,7 @@ class UserAgent implements Stringable
         }
 
         // Check for a specific browser
-        return isset($this->config->browsers[$key]) && $this->browser === $this->config->browsers[$key];
+        return $this->browser === $key;
     }
 
     /**

@michalsn
Copy link
Member

michalsn commented Jun 3, 2024

@kenjis It might be less intuitive, but this is how it was designed to work and it's well-documented.

         // Check for a specific browser
-        return isset($this->config->browsers[$key]) && $this->browser === $this->config->browsers[$key];
+        return $this->browser === $key;

This change would completely break the code logic for some apps. Checking $agent->isBrowser('Edg') would simply fail.

@warmbook
Copy link
Author

warmbook commented Jun 4, 2024

@michalsn Thanks! I have got it.

@michalsn michalsn removed the bug Verified issues on the current code behavior or pull requests that will fix them label Jun 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants