Javascript 用户代理 (ajax) 与请求网站时发送的用户代理不同

Javascript user-agent (ajax) different to sent user-agent when requesting website

我注意到我的 phone(OnePlus 3,Android 8.0.0)上的 Chrome (64.0.3282.137) 在请求网页时发送略有不同的用户代理通过 ajax.

请求

请求网页时发送此用户代理:

Mozilla/5.0 (Linux; Android 8.0.0; ONEPLUS A3003 Build/OPR6.170623.013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36

发送此用户代理进行 ajax 调用,并在调用 navigator.userAgent 时返回:

Mozilla/5.0 (Linux; Android 8.0.0; Build/OPR6.170623.013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36

区别:ONEPLUS A3003

你能告诉我为什么模型包含在本地调用中,而不是在 ajax-calls 中吗?

Additional information: With the "Request desktop site"-feature enabled the user agent is Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Safari/537.36 in both cases.

第一个userAgent中,浏览器在发起请求前通过修改userAgent来识别设备为移动设备;因此 ONEPLUS A3003。然而,在第二个中,由于 w3 规范 (Find it here),您不能修改 userAgent;因此省略了 ONEPLUS A3003.

当您使用"Request desktop site"功能时,浏览器不需要修改userAgent,因此您得到相同的userAgent。

注意:Chrome 浏览器的默认 userAgent 是: Mozilla/5.0 (Linux; Android 8.0.0; Build/OPR6.170623.013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36

我分析了 Chromium 源代码以获得一些见解。我在 C++ 方面的新手能力只能达到一定程度。

在此代码块中检测到客户端或平台的用户代理(文件:useragent.cc)。

std::string BuildUserAgentFromProduct(const std::string& product) {
  std::string os_info;
  base::StringAppendF(
      &os_info,
      "%s%s",
      getUserAgentPlatform().c_str(),
      BuildOSCpuInfo().c_str());
  return BuildUserAgentFromOSAndProduct(os_info, product);
}

您可以在代码块中看到 BuildOSCpuInfo(),它负责添加 os 基于平台的相关信息,可在此处找到

std::string android_build_codename = base::SysInfo::GetAndroidBuildCodename();
std::string android_device_name = base::SysInfo::HardwareModelName(); // this line in particular adds the ONEPLUS A3003

但是这个函数 (BuildUserAgentFromProduct()) 没有直接用在负责发送 http 请求的 net 模块中。

当我调查 net(http) 模块的代码时,我发现他们正在获取 useragent* 并通过一系列字符串操作和白色 space 修剪功能对其进行处理。 http_request_headers.cc 中的 AddHeadersFromString() 是将用户代理字符串添加到请求中的接口 header.

注意*:但我认为 header 数据不是来自 useragent.cc,因为我无法在任何地方找到对此函数的调用。但我在这里可能是错的。

**我相信这是修改 OSInfo 值的地方。任何无法识别或格式错误的白色 space 字符都可能导致此结果。

注意**:我无法测试并证明上面的说法,因为在 Chromium 中使用的字符串有一个名为 StringPiece 的包装器(*wrapper 只是我正在使用的一个术语,从技术上讲,它可以用我不知道的不同方式调用。)。而且我不知道如何用 C++ 为 StringPiece 编写代码。

但是下面给出了一个非常简单的例子来说明它是如何出错的。

int main()
{
   std::string s = " ONEPLUS\rA3003\rBuild/OPR6.170623.013";
   std::string delimiter = "\r\n"; //this is the delimeter used in chromium source code.
   std::string token = s.substr(0, s.find(delimiter,0));
   std::cout << token << std::endl;
   return 0;
}

https://www.onlinegdb.com/SkTrbFJDz

初始用户代理字符串有值而后续 http 请求没有值的原因在于 android 中 chrome 应用程序的架构。当最初加载页面时,这些值实际上是由 chrome 应用程序设置的(​​一个非常大的 java 代码库,但我认为我们需要看到的核心文件是 LoadUrlParams.java)它有发送 http 请求的不同实现(这里 useragent 不是由同一个 net(http) 模块修剪,而是由 Java 实现处理),这仅在第一次加载期间发生。但是任何其他后续调用都使用浏览器的 net(http) 模块。

文件参考链接: https://cs.chromium.org/chromium/src/content/common/user_agent.cc?sq=package:chromium&dr=CSs&l=80

https://cs.chromium.org/chromium/src/net/http/http_request_headers.cc?type=cs&q=AddHeadersFromString&l=155

https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java?q=createLoadDataParamsWithBaseUrl&dr=CSs

我加入这个答案只是为了给出问题可能发生的原因之一。如果我有更多时间,我会看看我是否可以 运行 以某种方式进行测试并证明这一点。 最后请注意,此答案未提供解决问题的任何解决方案。它只是给出了原因。

[更新]

一个非常便宜的技巧是查看 navigator.useragent 是否具有 oneplus 值并在请求上设置 ajax headers 并发送它。这将覆盖浏览器添加用户代理的机制 header.

XMLHttpRequest.setRequestHeader(header, value)