Chromium 中chrome.cookies扩展接口c++实现分析

news2024/11/22 22:16:29

chrome.cookies

使用 chrome.cookies API 查询和修改 Cookie,并在 Cookie 发生更改时收到通知。

更多参考官网定义:chrome.cookies  |  API  |  Chrome for Developers (google.cn)

本文以加载一个清理cookies功能扩展为例 https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/cookies/cookie-clearer

 摘自官网扩展例子:

manifest.json

{
  "name": "Cookie Clearer",
  "manifest_version": 3,
  "version": "1.0",
  "description": "Uses the chrome.cookies API by letting a user delete their cookies via a popup.",
  "permissions": ["cookies"],
  "host_permissions": ["<all_urls>"],
  "action": {
    "default_popup": "popup.html"
  }
}

popup.js

const form = document.getElementById('control-row');
const input = document.getElementById('input');
const message = document.getElementById('message');

// The async IIFE is necessary because Chrome <89 does not support top level await.
(async function initPopupWindow() {
  let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

  if (tab?.url) {
    try {
      let url = new URL(tab.url);
      input.value = url.hostname;
    } catch {
      // ignore
    }
  }

  input.focus();
})();

form.addEventListener('submit', handleFormSubmit);

async function handleFormSubmit(event) {
  event.preventDefault();

  clearMessage();

  let url = stringToUrl(input.value);
  if (!url) {
    setMessage('Invalid URL');
    return;
  }

  let message = await deleteDomainCookies(url.hostname);
  setMessage(message);
}

function stringToUrl(input) {
  // Start with treating the provided value as a URL
  try {
    return new URL(input);
  } catch {
    // ignore
  }
  // If that fails, try assuming the provided input is an HTTP host
  try {
    return new URL('http://' + input);
  } catch {
    // ignore
  }
  // If that fails ¯\_(ツ)_/¯
  return null;
}

async function deleteDomainCookies(domain) {
  let cookiesDeleted = 0;
  try {
    const cookies = await chrome.cookies.getAll({ domain });

    if (cookies.length === 0) {
      return 'No cookies found';
    }

    let pending = cookies.map(deleteCookie);
    await Promise.all(pending);

    cookiesDeleted = pending.length;
  } catch (error) {
    return `Unexpected error: ${error.message}`;
  }

  return `Deleted ${cookiesDeleted} cookie(s).`;
}

function deleteCookie(cookie) {
  // Cookie deletion is largely modeled off of how deleting cookies works when using HTTP headers.
  // Specific flags on the cookie object like `secure` or `hostOnly` are not exposed for deletion
  // purposes. Instead, cookies are deleted by URL, name, and storeId. Unlike HTTP headers, though,
  // we don't have to delete cookies by setting Max-Age=0; we have a method for that ;)
  //
  // To remove cookies set with a Secure attribute, we must provide the correct protocol in the
  // details object's `url` property.
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Secure
  const protocol = cookie.secure ? 'https:' : 'http:';

  // Note that the final URL may not be valid. The domain value for a standard cookie is prefixed
  // with a period (invalid) while cookies that are set to `cookie.hostOnly == true` do not have
  // this prefix (valid).
  // https://developer.chrome.com/docs/extensions/reference/cookies/#type-Cookie
  const cookieUrl = `${protocol}//${cookie.domain}${cookie.path}`;

  return chrome.cookies.remove({
    url: cookieUrl,
    name: cookie.name,
    storeId: cookie.storeId
  });
}

function setMessage(str) {
  message.textContent = str;
  message.hidden = false;
}

function clearMessage() {
  message.hidden = true;
  message.textContent = '';
}

popup.html

<!doctype html>
<html>
  <head>
    <script src="popup.js" type="module"></script>
  </head>
  <body>
    <form id="control-row">
      <label for="input">Domain:</label>
      <input type="text" id="input" />
      <br />
      <button id="go">Clear Cookies</button>
    </form>
    <span id="message" hidden></span>
  </body>
</html>

一、看下c++提供 的cookies接口定义  chrome\common\extensions\api\cookies.json

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

[
  {
    "namespace": "cookies",
    "description": "Use the <code>chrome.cookies</code> API to query and modify cookies, and to be notified when they change.",
    "types": [
      {
        "id": "SameSiteStatus",
        "type": "string",
        "enum": ["no_restriction", "lax", "strict", "unspecified"],
        "description": "A cookie's 'SameSite' state (https://tools.ietf.org/html/draft-west-first-party-cookies). 'no_restriction' corresponds to a cookie set with 'SameSite=None', 'lax' to 'SameSite=Lax', and 'strict' to 'SameSite=Strict'. 'unspecified' corresponds to a cookie set without the SameSite attribute."
      },
      {
        "id": "CookiePartitionKey",
        "type": "object",
        "description": "Represents a partitioned cookie's partition key.",
        "properties": {
          "topLevelSite": {"type": "string", "optional": true, "description": "The top-level site the partitioned cookie is available in."}
        }
      },
      {
        "id": "Cookie",
        "type": "object",
        "description": "Represents information about an HTTP cookie.",
        "properties": {
          "name": {"type": "string", "description": "The name of the cookie."},
          "value": {"type": "string", "description": "The value of the cookie."},
          "domain": {"type": "string", "description": "The domain of the cookie (e.g. \"www.google.com\", \"example.com\")."},
          "hostOnly": {"type": "boolean", "description": "True if the cookie is a host-only cookie (i.e. a request's host must exactly match the domain of the cookie)."},
          "path": {"type": "string", "description": "The path of the cookie."},
          "secure": {"type": "boolean", "description": "True if the cookie is marked as Secure (i.e. its scope is limited to secure channels, typically HTTPS)."},
          "httpOnly": {"type": "boolean", "description": "True if the cookie is marked as HttpOnly (i.e. the cookie is inaccessible to client-side scripts)."},
          "sameSite": {"$ref": "SameSiteStatus", "description": "The cookie's same-site status (i.e. whether the cookie is sent with cross-site requests)."},
          "session": {"type": "boolean", "description": "True if the cookie is a session cookie, as opposed to a persistent cookie with an expiration date."},
          "expirationDate": {"type": "number", "optional": true, "description": "The expiration date of the cookie as the number of seconds since the UNIX epoch. Not provided for session cookies."},
          "storeId": {"type": "string", "description": "The ID of the cookie store containing this cookie, as provided in getAllCookieStores()."},
          "partitionKey": {"$ref": "CookiePartitionKey", "optional": true, "description": "The partition key for reading or modifying cookies with the Partitioned attribute."}
        }
      },
      {
        "id": "CookieStore",
        "type": "object",
        "description": "Represents a cookie store in the browser. An incognito mode window, for instance, uses a separate cookie store from a non-incognito window.",
        "properties": {
          "id": {"type": "string", "description": "The unique identifier for the cookie store."},
          "tabIds": {"type": "array", "items": {"type": "integer"}, "description": "Identifiers of all the browser tabs that share this cookie store."}
        }
      },
      {
        "id": "OnChangedCause",
        "type": "string",
        "enum": ["evicted", "expired", "explicit", "expired_overwrite", "overwrite"],
        "description": "The underlying reason behind the cookie's change. If a cookie was inserted, or removed via an explicit call to \"chrome.cookies.remove\", \"cause\" will be \"explicit\". If a cookie was automatically removed due to expiry, \"cause\" will be \"expired\". If a cookie was removed due to being overwritten with an already-expired expiration date, \"cause\" will be set to \"expired_overwrite\".  If a cookie was automatically removed due to garbage collection, \"cause\" will be \"evicted\".  If a cookie was automatically removed due to a \"set\" call that overwrote it, \"cause\" will be \"overwrite\". Plan your response accordingly."
      },
      {
        "id": "CookieDetails",
        "type": "object",
        "description": "Details to identify the cookie.",
        "properties": {
          "url": {"type": "string", "description": "The URL with which the cookie to access is associated. This argument may be a full URL, in which case any data following the URL path (e.g. the query string) is simply ignored. If host permissions for this URL are not specified in the manifest file, the API call will fail."},
          "name": {"type": "string", "description": "The name of the cookie to access."},
          "storeId": {"type": "string", "optional": true, "description": "The ID of the cookie store in which to look for the cookie. By default, the current execution context's cookie store will be used."},
          "partitionKey": {"$ref": "CookiePartitionKey", "optional": true, "description": "The partition key for reading or modifying cookies with the Partitioned attribute."}
        }
      }
    ],
    "functions": [
      {
        "name": "get",
        "type": "function",
        "description": "Retrieves information about a single cookie. If more than one cookie of the same name exists for the given URL, the one with the longest path will be returned. For cookies with the same path length, the cookie with the earliest creation time will be returned.",
        "parameters": [
          {
            "name": "details",
            "$ref": "CookieDetails"
          }
        ],
        "returns_async": {
          "name": "callback",
          "parameters": [
            {
              "name": "cookie", "$ref": "Cookie", "optional": true, "description": "Contains details about the cookie. This parameter is null if no such cookie was found."
            }
          ]
        }
      },
      {
        "name": "getAll",
        "type": "function",
        "description": "Retrieves all cookies from a single cookie store that match the given information.  The cookies returned will be sorted, with those with the longest path first.  If multiple cookies have the same path length, those with the earliest creation time will be first. Only retrieves cookies for domains which the extension has host permissions to.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Information to filter the cookies being retrieved.",
            "properties": {
              "url": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those that would match the given URL."},
              "name": {"type": "string", "optional": true, "description": "Filters the cookies by name."},
              "domain": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those whose domains match or are subdomains of this one."},
              "path": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those whose path exactly matches this string."},
              "secure": {"type": "boolean", "optional": true, "description": "Filters the cookies by their Secure property."},
              "session": {"type": "boolean", "optional": true, "description": "Filters out session vs. persistent cookies."},
              "storeId": {"type": "string", "optional": true, "description": "The cookie store to retrieve cookies from. If omitted, the current execution context's cookie store will be used."},
              "partitionKey": {"$ref": "CookiePartitionKey", "optional": true, "description": "The partition key for reading or modifying cookies with the Partitioned attribute."}
            }
          }
        ],
        "returns_async": {
          "name": "callback",
          "parameters": [
            {
              "name": "cookies", "type": "array", "items": {"$ref": "Cookie"}, "description": "All the existing, unexpired cookies that match the given cookie info."
            }
          ]
        }
      },
      {
        "name": "set",
        "type": "function",
        "description": "Sets a cookie with the given cookie data; may overwrite equivalent cookies if they exist.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Details about the cookie being set.",
            "properties": {
              "url": {"type": "string", "description": "The request-URI to associate with the setting of the cookie. This value can affect the default domain and path values of the created cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail."},
              "name": {"type": "string", "optional": true, "description": "The name of the cookie. Empty by default if omitted."},
              "value": {"type": "string", "optional": true, "description": "The value of the cookie. Empty by default if omitted."},
              "domain": {"type": "string", "optional": true, "description": "The domain of the cookie. If omitted, the cookie becomes a host-only cookie."},
              "path": {"type": "string", "optional": true, "description": "The path of the cookie. Defaults to the path portion of the url parameter."},
              "secure": {"type": "boolean", "optional": true, "description": "Whether the cookie should be marked as Secure. Defaults to false."},
              "httpOnly": {"type": "boolean", "optional": true, "description": "Whether the cookie should be marked as HttpOnly. Defaults to false."},
              "sameSite": {"$ref": "SameSiteStatus", "optional": true, "description": "The cookie's same-site status. Defaults to \"unspecified\", i.e., if omitted, the cookie is set without specifying a SameSite attribute."},
              "expirationDate": {"type": "number", "optional": true, "description": "The expiration date of the cookie as the number of seconds since the UNIX epoch. If omitted, the cookie becomes a session cookie."},
              "storeId": {"type": "string", "optional": true, "description": "The ID of the cookie store in which to set the cookie. By default, the cookie is set in the current execution context's cookie store."},
              "partitionKey": {"$ref": "CookiePartitionKey", "optional": true, "description": "The partition key for reading or modifying cookies with the Partitioned attribute."}
            }
          }
        ],
        "returns_async": {
          "name": "callback",
          "optional": true,
          "min_version": "11.0.674.0",
          "parameters": [
            {
              "name": "cookie", "$ref": "Cookie", "optional": true, "description": "Contains details about the cookie that's been set.  If setting failed for any reason, this will be \"null\", and $(ref:runtime.lastError) will be set."
            }
          ]
        }
      },
      {
        "name": "remove",
        "type": "function",
        "description": "Deletes a cookie by name.",
        "parameters": [
          {
            "name": "details",
            "$ref": "CookieDetails"
          }
        ],
        "returns_async": {
          "name": "callback",
          "optional": true,
          "min_version": "11.0.674.0",
          "parameters": [
            {
              "name": "details",
              "type": "object",
              "description": "Contains details about the cookie that's been removed.  If removal failed for any reason, this will be \"null\", and $(ref:runtime.lastError) will be set.",
              "optional": true,
              "properties": {
                "url": {"type": "string", "description": "The URL associated with the cookie that's been removed."},
                "name": {"type": "string", "description": "The name of the cookie that's been removed."},
                "storeId": {"type": "string", "description": "The ID of the cookie store from which the cookie was removed."},
                "partitionKey": {"$ref": "CookiePartitionKey", "optional": true, "description": "The partition key for reading or modifying cookies with the Partitioned attribute."}
              }
            }
          ]
        }
      },
      {
        "name": "getAllCookieStores",
        "type": "function",
        "description": "Lists all existing cookie stores.",
        "parameters": [],
        "returns_async": {
          "name": "callback",
          "parameters": [
            {
              "name": "cookieStores", "type": "array", "items": {"$ref": "CookieStore"}, "description": "All the existing cookie stores."
            }
          ]
        }
      }
    ],
    "events": [
      {
        "name": "onChanged",
        "type": "function",
        "description": "Fired when a cookie is set or removed. As a special case, note that updating a cookie's properties is implemented as a two step process: the cookie to be updated is first removed entirely, generating a notification with \"cause\" of \"overwrite\" .  Afterwards, a new cookie is written with the updated values, generating a second notification with \"cause\" \"explicit\".",
        "parameters": [
          {
            "type": "object",
            "name": "changeInfo",
            "properties": {
              "removed": {"type": "boolean", "description": "True if a cookie was removed."},
              "cookie": {"$ref": "Cookie", "description": "Information about the cookie that was set or removed."},
              "cause": {"min_version": "12.0.707.0", "$ref": "OnChangedCause", "description": "The underlying reason behind the cookie's change."}
            }
          }
        ]
      }
    ]
  }
]

同时会生成

out\Debug\gen\chrome\common\extensions\api\cookies.h

out\Debug\gen\chrome\common\extensions\api\cookies.cc

此文件是cookies.json 定义的一个c++实现,自动生成请勿手动更改【tools\json_schema_compiler\compiler.py】。

二、cookies api接口定义:

       chrome\browser\extensions\api\cookies\cookies_api.h

       chrome\browser\extensions\api\cookies\cookies_api.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Defines the Chrome Extensions Cookies API functions for accessing internet
// cookies, as specified in the extension API JSON.

#ifndef CHROME_BROWSER_EXTENSIONS_API_COOKIES_COOKIES_API_H_
#define CHROME_BROWSER_EXTENSIONS_API_COOKIES_COOKIES_API_H_

#include <memory>
#include <string>

#include "base/memory/raw_ptr.h"
#include "base/values.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/common/extensions/api/cookies.h"
#include "extensions/browser/browser_context_keyed_api_factory.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_function.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_change_dispatcher.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"

class Profile;

namespace extensions {

// Observes CookieManager Mojo messages and routes them as events to the
// extension system.
class CookiesEventRouter : public BrowserListObserver {
 public:
  explicit CookiesEventRouter(content::BrowserContext* context);

  CookiesEventRouter(const CookiesEventRouter&) = delete;
  CookiesEventRouter& operator=(const CookiesEventRouter&) = delete;

  ~CookiesEventRouter() override;

  // BrowserListObserver:
  void OnBrowserAdded(Browser* browser) override;

 private:
  // This helper class connects to the CookieMonster over Mojo, and relays Mojo
  // messages to the owning CookiesEventRouter. This rather clumsy arrangement
  // is necessary to differentiate which CookieMonster the Mojo message comes
  // from (that associated with the incognito profile vs the original profile),
  // since it's not possible to tell the source from inside OnCookieChange().
  class CookieChangeListener : public network::mojom::CookieChangeListener {
   public:
    CookieChangeListener(CookiesEventRouter* router, bool otr);

    CookieChangeListener(const CookieChangeListener&) = delete;
    CookieChangeListener& operator=(const CookieChangeListener&) = delete;

    ~CookieChangeListener() override;

    // network::mojom::CookieChangeListener:
    void OnCookieChange(const net::CookieChangeInfo& change) override;

   private:
    raw_ptr<CookiesEventRouter> router_;
    bool otr_;
  };

  void MaybeStartListening();
  void BindToCookieManager(
      mojo::Receiver<network::mojom::CookieChangeListener>* receiver,
      Profile* profile);
  void OnConnectionError(
      mojo::Receiver<network::mojom::CookieChangeListener>* receiver);
  void OnCookieChange(bool otr, const net::CookieChangeInfo& change);

  // This method dispatches events to the extension message service.
  void DispatchEvent(content::BrowserContext* context,
                     events::HistogramValue histogram_value,
                     const std::string& event_name,
                     base::Value::List event_args,
                     const GURL& cookie_domain);

  raw_ptr<Profile> profile_;

  // To listen to cookie changes in both the original and the off the record
  // profiles, we need a pair of bindings, as well as a pair of
  // CookieChangeListener instances.
  CookieChangeListener listener_{this, false};
  mojo::Receiver<network::mojom::CookieChangeListener> receiver_{&listener_};

  CookieChangeListener otr_listener_{this, true};
  mojo::Receiver<network::mojom::CookieChangeListener> otr_receiver_{
      &otr_listener_};
};

// Implements the cookies.get() extension function.
class CookiesGetFunction : public ExtensionFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("cookies.get", COOKIES_GET)

  CookiesGetFunction();

 protected:
  ~CookiesGetFunction() override;

  // ExtensionFunction:
  ResponseAction Run() override;

 private:
  void GetCookieListCallback(
      const net::CookieAccessResultList& cookie_list,
      const net::CookieAccessResultList& excluded_cookies);

  // Notify the extension telemetry service when API is called.
  void NotifyExtensionTelemetry();

  GURL url_;
  mojo::Remote<network::mojom::CookieManager> store_browser_cookie_manager_;
  absl::optional<api::cookies::Get::Params> parsed_args_;
};

// Implements the cookies.getAll() extension function.
class CookiesGetAllFunction : public ExtensionFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("cookies.getAll", COOKIES_GETALL)

  CookiesGetAllFunction();

 protected:
  ~CookiesGetAllFunction() override;

  // ExtensionFunction:
  ResponseAction Run() override;

 private:
  // For the two different callback signatures for getting cookies for a URL vs
  // getting all cookies. They do the same thing.
  void GetAllCookiesCallback(const net::CookieList& cookie_list);
  void GetCookieListCallback(
      const net::CookieAccessResultList& cookie_list,
      const net::CookieAccessResultList& excluded_cookies);

  // Notify the extension telemetry service when API is called.
  void NotifyExtensionTelemetry();

  GURL url_;
  mojo::Remote<network::mojom::CookieManager> store_browser_cookie_manager_;
  absl::optional<api::cookies::GetAll::Params> parsed_args_;
};

// Implements the cookies.set() extension function.
class CookiesSetFunction : public ExtensionFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("cookies.set", COOKIES_SET)

  CookiesSetFunction();

 protected:
  ~CookiesSetFunction() override;
  ResponseAction Run() override;

 private:
  void SetCanonicalCookieCallback(net::CookieAccessResult set_cookie_result);
  void GetCookieListCallback(
      const net::CookieAccessResultList& cookie_list,
      const net::CookieAccessResultList& excluded_cookies);

  enum { NO_RESPONSE, SET_COMPLETED, GET_COMPLETED } state_;
  GURL url_;
  bool success_;
  mojo::Remote<network::mojom::CookieManager> store_browser_cookie_manager_;
  absl::optional<api::cookies::Set::Params> parsed_args_;
};

// Implements the cookies.remove() extension function.
class CookiesRemoveFunction : public ExtensionFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("cookies.remove", COOKIES_REMOVE)

  CookiesRemoveFunction();

 protected:
  ~CookiesRemoveFunction() override;

  // ExtensionFunction:
  ResponseAction Run() override;

 private:
  void RemoveCookieCallback(uint32_t /* num_deleted */);

  GURL url_;
  mojo::Remote<network::mojom::CookieManager> store_browser_cookie_manager_;
  absl::optional<api::cookies::Remove::Params> parsed_args_;
};

// Implements the cookies.getAllCookieStores() extension function.
class CookiesGetAllCookieStoresFunction : public ExtensionFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("cookies.getAllCookieStores",
                             COOKIES_GETALLCOOKIESTORES)

 protected:
  ~CookiesGetAllCookieStoresFunction() override {}

  // ExtensionFunction:
  ResponseAction Run() override;
};

class CookiesAPI : public BrowserContextKeyedAPI, public EventRouter::Observer {
 public:
  explicit CookiesAPI(content::BrowserContext* context);

  CookiesAPI(const CookiesAPI&) = delete;
  CookiesAPI& operator=(const CookiesAPI&) = delete;

  ~CookiesAPI() override;

  // KeyedService implementation.
  void Shutdown() override;

  // BrowserContextKeyedAPI implementation.
  static BrowserContextKeyedAPIFactory<CookiesAPI>* GetFactoryInstance();

  // EventRouter::Observer implementation.
  void OnListenerAdded(const EventListenerInfo& details) override;

 private:
  friend class BrowserContextKeyedAPIFactory<CookiesAPI>;

  raw_ptr<content::BrowserContext> browser_context_;

  // BrowserContextKeyedAPI implementation.
  static const char* service_name() {
    return "CookiesAPI";
  }
  static const bool kServiceIsNULLWhileTesting = true;

  // Created lazily upon OnListenerAdded.
  std::unique_ptr<CookiesEventRouter> cookies_event_router_;
};

}  // namespace extensions

#endif  // CHROME_BROWSER_EXTENSIONS_API_COOKIES_COOKIES_API_H_

三、开发者模式打开加载下扩展看下堆栈效果:

     

至此分析完毕,如果想拦截 chrome.cookies.getAll等方法在  chrome\browser\extensions\api\cookies\cookies_api.cc中更改即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2198255.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RHCSA的学习(4)

一、vi编辑器 &#xff08;1&#xff09;为什么学vi&#xff1f; 所有的Unix Like 系统都会内建 vi 文本编辑器&#xff0c;其他的文本编辑器则不一定会存在&#xff1b; 很多个别软件的编辑接口都会主动呼叫 vi (例如未来会谈到的 crontab, visudo, edquota 等指令)&#x…

考试宝 逆向 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像私信联系我…

【打印模板】子表类型数据支持超出行默认填充

09/25 主要更新模块概览 打印分组 默认填充 匹配地址 嵌入页面 01 表单管理 1.1 【打印模板】-子表类型&#xff08;数据关联&#xff0c;数据查询&#xff0c;子表单&#xff09;支持分组设置 说明&#xff1a; 在打印模板中&#xff0c;子表类型&#…

如何让 Raft 更稳健,使用 Pre-vote

本文参考文献 《Consensus: Bridging Theory and Practice》 1. Provote 解析原文 该算法解决的是某台机器被隔离后&#xff0c;再次加入时出现的扰动其他机器的问题。 1. 防止服务器重新加入集群时引发的中断 Raft领导者选举算法的一个缺点是&#xff0c;当一台已从集群中…

使用tcpkill断开异常tcp连接

在linux系统中&#xff0c;遇到TCP链接迟迟不能释放的情况&#xff0c;类似FIN_WAIT1、FIN_WAIT2的状态&#xff0c;释放时间不确定&#xff0c;而且对应的程序已经关闭&#xff0c;相应的端口也不再监听&#xff0c;无法通过杀进程来解决&#xff0c;这种情况下&#xff0c;为…

JS设计模式之策略模式:灵活、可扩展的编程利器

一. 前言 在 JavaScript 前端开发中&#xff0c;随着代码规模的增长和项目的复杂性&#xff0c;我们常常需要处理各种不同的条件和情况&#xff0c;而这可能导致代码变得冗长、难以维护。这时&#xff0c;我们就需要一种强大而灵活的编程模式来应对这些复杂的逻辑&#xff0c;…

人工智能的未来:从知识廉价时代到AI主导国家模式

随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;知识的获取和使用正变得更加普及与廉价。这不仅引发了技术领域的深刻变革&#xff0c;也将对全球社会经济模式产生广泛影响。特别是在《时代》杂志对风险投资巨头维诺德科斯拉&#xff08;Vinod Khosla&#…

【AUTOSAR 基础软件】PduR模块详解(通信路由)

文章包含了AUTOSAR基础软件&#xff08;BSW&#xff09;中PduR模块相关的内容详解。本文从AUTOSAR规范解析&#xff0c;ISOLAR-AB配置以及模块相关代码分析三个维度来帮读者清晰的认识和了解PduR这一基础软件模块。文中涉及的ISOLAR-AB配置以及模块相关代码都是依托于ETAS提供的…

[Python学习日记-42] Python 中的生成器

[Python学习日记-42] Python 中的生成器 简介 表达式生成器 函数生成器 用生成器实现并发编程 简介 Python 中的生成器&#xff08;Generator&#xff09;是一种特殊的迭代器&#xff0c;它又被成为惰性运算&#xff0c;它可以在迭代过程中动态生成值&#xff0c;而不需要事…

HTML CSS 基础

HTML & CSS 基础 HTML一、HTML简介1、网页1.1 什么是网页1.2 什么是HTML1.3 网页的形成1.4总结 2、web标准2.1 为什么需要web标准2.2 Web 标准的构成 二、HTML 标签1、HTML 语法规范1.1基本语法概述1.2 标签关系 2、 HTML 基本结构标签2.1 第一个 HTML 网页2.2 基本结构标签…

PHP input 多文件上传功能实现-网页不为人知的数据库缺陷——未来之窗行业应用跨平台架构

一、多文件上传html部分 1.1错误示例 <input type"file" class"input fl" name"file" style"width:200px;display:inline;border:0px;" multiple />1.2 正确示例 <input type"file" class"input fl" …

Vxe UI vue vxe-table 实现表格单元格选中功能

Vxe UI vue vxe-table 实现表格单元格选中功能 在表格中实现鼠标点击任意单元格&#xff0c;选取的功能&#xff0c;通过 mouse-config 配置就可以开启单选功能&#xff0c;多选单元格选取功能需安装插件支持。 代码 参数说明 mouse-config 鼠标配置项&#xff1a; selected&…

Linux之shell详解(Linux Shell Detailed Explanation)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

【韩顺平Java笔记】第8章:面向对象编程(中级部分)【285-296】

文章目录 285. 为什么需要继承286. 继承原理图287. 继承快速入门288. 289. 290. 291. 292. 继承使用细节1,2,3,4,5288.1 继承给编程带来的便利288.2 继承的深入讨论/细节问题 293. 继承本质详解294. 继承课堂练习1295. 继承课堂练习2296. 继承课堂练习3 285. 为什么需要继承 28…

【软件部署安装】OpenOffice转换PDF字体乱码

现象与原因分析 执行fc-list查看系统字体 经分析发现&#xff0c;linux默认不带中文字体&#xff0c;因此打开我们本地的windows系统的TTF、TTC字体安装到centos机器上。 安装字体 将Windows的路径&#xff1a; C:\Windows\Fonts 的中文字体&#xff0c;如扩展名为 TTC 与TT…

App模拟心跳长连接的实现方法demo

摘要 背景&#xff1a;心跳通常是指客户端或服务器定期发送一个小型的、空的消息以保持连接的活动状态。它用于检测连接是否仍然有效&#xff0c;并防止连接由于长时间没有活动而被关闭。 技术原理&#xff1a;App定时发消息给服务器&#xff0c;服务器回消息表示连接依旧有效…

手机竖屏 Premiere Pro 电影转场特效视频模板Pr工程文件

10个不同的类别和115个过渡。过渡很容易使用。随附视频教程。 下载地址&#xff1a;Pr模板网 下载链接&#xff1a;https://prmuban.com/40597.html

动态规划算法专题(五):子序列问题

目录 1、最长递增子序列 1.1 算法原理 1.2 算法代码 2、摆动序列 2.1 算法原理 2.2 算法代码 3、最长递增子序列的个数 3.1 算法原理 3.2 算法代码 4、最长数对链 4.1 算法原理 4.2 算法代码 5、最长定差子序列 5.1 算法原理 5.2 算法代码 6、最长的斐波那契子序…

NASA:气象追踪分子光谱(ATMOS)二级产品,包含在垂直高度(公里)网格上的微量气体

目录 简介 摘要 引用 网址推荐 0代码在线构建地图应用 机器学习 ATMOS L2 Trace Gases on Altitude Grid, Tab Delimited Format V3 (ATMOSL2AT) at GES DISC 简介 这是版本3的气象追踪分子光谱&#xff08;ATMOS&#xff09;二级产品&#xff0c;包含在垂直高度&#…

多线程股吧(东方财富)用户信息爬取

多线程东方财富&#xff08;股吧&#xff09;用户信息爬取 在上一篇博客股吧信息爬取的基础上加入了多线程&#xff0c;使得速度提升了十几倍&#xff0c;爬取内容如下&#xff1a; 最终爬取结果如下&#xff1a; 完整代码如下&#xff08;准备好环境&#xff0c;比如pytho…