forked from mirrors/gecko-dev
Bug 1811076: Part 1 - Initial commit of content analysis SDK r=cmartin
SDK from https://github.com/chromium/content_analysis_sdk Pulled July 31, 2023 Differential Revision: https://phabricator.services.mozilla.com/D189567
This commit is contained in:
parent
efeeadc85f
commit
5fdb72e1f5
62 changed files with 5657 additions and 0 deletions
58
third_party/content_analysis_sdk/.gitattributes
vendored
Normal file
58
third_party/content_analysis_sdk/.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# Stop Windows python license check presubmit errors by forcing LF checkout.
|
||||
*.py text eol=lf
|
||||
|
||||
# Force LF checkout of the pins files to avoid transport_security_state_generator errors.
|
||||
/net/http/*.pins text eol=lf
|
||||
|
||||
# Force LF checkout for all source files
|
||||
*.bin binary
|
||||
*.c text eol=lf
|
||||
*.cc text eol=lf
|
||||
*.cpp text eol=lf
|
||||
*.csv text eol=lf
|
||||
*.grd text eol=lf
|
||||
*.grdp text eol=lf
|
||||
*.gn text eol=lf
|
||||
*.gni text eol=lf
|
||||
*.h text eol=lf
|
||||
*.html text eol=lf
|
||||
*.idl text eol=lf
|
||||
*.in text eol=lf
|
||||
*.inc text eol=lf
|
||||
*.java text eol=lf
|
||||
*.js text eol=lf
|
||||
*.json text eol=lf
|
||||
*.json5 text eol=lf
|
||||
*.md text eol=lf
|
||||
*.mm text eol=lf
|
||||
*.mojom text eol=lf
|
||||
*.pdf -diff
|
||||
*.proto text eol=lf
|
||||
*.rs text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.sql text eol=lf
|
||||
*.toml text eol=lf
|
||||
*.txt text eol=lf
|
||||
*.xml text eol=lf
|
||||
*.xslt text eol=lf
|
||||
.clang-format text eol=lf
|
||||
.eslintrc.js text eol=lf
|
||||
.git-blame-ignore-revs text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
.gitignore text eol=lf
|
||||
.vpython text eol=lf
|
||||
codereview.settings text eol=lf
|
||||
DEPS text eol=lf
|
||||
ENG_REVIEW_OWNERS text eol=lf
|
||||
LICENSE text eol=lf
|
||||
LICENSE.* text eol=lf
|
||||
MAJOR_BRANCH_DATE text eol=lf
|
||||
OWNERS text eol=lf
|
||||
README text eol=lf
|
||||
README.* text eol=lf
|
||||
WATCHLISTS text eol=lf
|
||||
VERSION text eol=lf
|
||||
DIR_METADATA text eol=lf
|
||||
|
||||
# Skip Tricium by default on files in third_party.
|
||||
third_party/** -tricium
|
||||
6
third_party/content_analysis_sdk/.gitignore
vendored
Normal file
6
third_party/content_analysis_sdk/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.vscode/
|
||||
.ccls-cache/
|
||||
.cache/
|
||||
build/
|
||||
*.bak
|
||||
*.swp
|
||||
213
third_party/content_analysis_sdk/CMakeLists.txt
vendored
Normal file
213
third_party/content_analysis_sdk/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
# Copyright 2022 The Chromium Authors.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
project(chrome_enterprise_connector_local_analysis)
|
||||
|
||||
# Ensure a C++14 compiler is used.
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
|
||||
# Determine the operating system being targeted.
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(WIN TRUE)
|
||||
set(MAC FALSE)
|
||||
set(LINUX FALSE)
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(WIN FALSE)
|
||||
set(MAC TRUE)
|
||||
set(LINUX FALSE)
|
||||
else()
|
||||
set(WIN FALSE)
|
||||
set(MAC FALSE)
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
# Set the path to the protoc protobuf compiler.
|
||||
if(WIN)
|
||||
set(PROTOC ${PROJECT_BINARY_DIR}/vcpkg/installed/x64-windows/tools/protobuf/protoc.exe)
|
||||
elseif(MAC)
|
||||
set(PROTOC ${PROJECT_BINARY_DIR}/vcpkg/installed/x64-osx/tools/protobuf/protoc)
|
||||
elseif(LINUX)
|
||||
set(PROTOC ${PROJECT_BINARY_DIR}/vcpkg/installed/x64-linux/tools/protobuf/protoc)
|
||||
endif()
|
||||
|
||||
# Calls the protoc compiler using the arguments specific to this project.
|
||||
# protobuf_generate_cpp is not flexible enough for our needs.
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_BINARY_DIR}/gen/content_analysis/sdk/analysis.pb.cc
|
||||
COMMAND
|
||||
${PROTOC}
|
||||
--cpp_out=${PROJECT_BINARY_DIR}/gen
|
||||
--proto_path=${PROJECT_SOURCE_DIR}/proto
|
||||
${PROJECT_SOURCE_DIR}/proto/content_analysis/sdk/analysis.proto
|
||||
DEPENDS ./proto/content_analysis/sdk/analysis.proto
|
||||
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
|
||||
)
|
||||
# Define proto target. Compile this target exclusively by calling:
|
||||
# `cmake --build <build_dir> --target proto`
|
||||
add_custom_target(proto
|
||||
ALL
|
||||
DEPENDS
|
||||
${PROJECT_BINARY_DIR}/gen/content_analysis/sdk/analysis.pb.cc
|
||||
)
|
||||
|
||||
# The include directory contains the header files needed by the demo code.
|
||||
# The gen directory contains generated protobuf headers describing the request
|
||||
# and response objects used to communicate with Google Chrome.
|
||||
set(AGENT_INCLUDES
|
||||
./agent/include
|
||||
.
|
||||
${PROJECT_BINARY_DIR}/gen
|
||||
)
|
||||
set(BROWSER_INCLUDES
|
||||
./browser/include
|
||||
.
|
||||
${PROJECT_BINARY_DIR}/gen
|
||||
)
|
||||
|
||||
# The SDK contains platform specific code for each of the supported platforms.
|
||||
# ${PLATFORM_AGENT_CODE} holds the list of source files needed for the current
|
||||
# platform being built.
|
||||
if(WIN)
|
||||
set(PLATFORM_AGENT_CODE
|
||||
./agent/src/agent_utils_win.cc
|
||||
./agent/src/agent_utils_win.h
|
||||
./agent/src/agent_win.cc
|
||||
./agent/src/agent_win.h
|
||||
./agent/src/event_win.cc
|
||||
./agent/src/event_win.h
|
||||
./agent/src/scoped_print_handle_win.cc
|
||||
./agent/src/scoped_print_handle_win.h
|
||||
./common/utils_win.cc
|
||||
./common/utils_win.h
|
||||
)
|
||||
set(PLATFORM_TEST_CODE
|
||||
./agent/src/agent_win_unittest.cc
|
||||
./agent/src/event_win_unittest.cc
|
||||
)
|
||||
elseif(MAC)
|
||||
set(PLATFORM_AGENT_CODE
|
||||
./agent/src/agent_mac.cc
|
||||
./agent/src/agent_mac.h
|
||||
./agent/src/event_mac.cc
|
||||
./agent/src/event_mac.h
|
||||
./agent/src/scoped_print_handle_mac.cc
|
||||
./agent/src/scoped_print_handle_mac.h
|
||||
)
|
||||
set(PLATFORM_TEST_CODE
|
||||
./agent/src/event_mac_unittest.cc
|
||||
)
|
||||
elseif(LINUX)
|
||||
set(PLATFORM_AGENT_CODE
|
||||
./agent/src/agent_posix.cc
|
||||
./agent/src/agent_posix.h
|
||||
./agent/src/event_posix.cc
|
||||
./agent/src/event_posix.h
|
||||
./agent/src/scoped_print_handle_posix.cc
|
||||
./agent/src/scoped_print_handle_posix.h
|
||||
)
|
||||
set(PLATFORM_TEST_CODE
|
||||
./agent/src/event_posix_unittest.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
# The SDK contains platform specific code for each of the supported platforms.
|
||||
# ${PLATFORM_BROWSER_CODE} holds the list of source files needed for the current
|
||||
# platform being built.
|
||||
if(WIN)
|
||||
set(PLATFORM_BROWSER_CODE
|
||||
./browser/src/client_win.cc
|
||||
./browser/src/client_win.h
|
||||
./common/utils_win.cc
|
||||
./common/utils_win.h
|
||||
)
|
||||
elseif(MAC)
|
||||
set(PLATFORM_BROWSER_CODE
|
||||
./browser/src/client_mac.cc
|
||||
./browser/src/client_mac.h
|
||||
)
|
||||
elseif(LINUX)
|
||||
set(PLATFORM_BROWSER_CODE
|
||||
./browser/src/client_posix.cc
|
||||
./browser/src/client_posix.h
|
||||
)
|
||||
endif()
|
||||
|
||||
# Makes available the package definitions in vcpkg.
|
||||
include("${PROJECT_BINARY_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake")
|
||||
find_package(Protobuf CONFIG REQUIRED)
|
||||
# Unit tests
|
||||
enable_testing()
|
||||
find_package(GTest CONFIG REQUIRED)
|
||||
include(GoogleTest)
|
||||
|
||||
add_executable(unit_tests
|
||||
${PLATFORM_TEST_CODE}
|
||||
)
|
||||
set_property(TARGET unit_tests PROPERTY CXX_STANDARD 20)
|
||||
target_include_directories(unit_tests
|
||||
PRIVATE
|
||||
${AGENT_INCLUDES}
|
||||
${BROWSER_INCLUDES}
|
||||
)
|
||||
target_link_libraries(unit_tests
|
||||
PUBLIC
|
||||
cac_agent
|
||||
cac_browser
|
||||
GTest::gtest GTest::gtest_main
|
||||
)
|
||||
|
||||
gtest_discover_tests(unit_tests)
|
||||
|
||||
# Builds the content analysis connector agent linker library. This library
|
||||
# is linked into the agent in order to listen for and process content analysis
|
||||
# requests from Google Chrome.
|
||||
add_library(cac_agent
|
||||
./agent/include/content_analysis/sdk/analysis_agent.h
|
||||
./agent/include/content_analysis/sdk/result_codes.h
|
||||
./agent/src/agent_base.cc
|
||||
./agent/src/agent_base.h
|
||||
./agent/src/event_base.cc
|
||||
./agent/src/event_base.h
|
||||
./agent/src/scoped_print_handle_base.cc
|
||||
./agent/src/scoped_print_handle_base.h
|
||||
${PLATFORM_AGENT_CODE}
|
||||
${PROJECT_BINARY_DIR}/gen/content_analysis/sdk/analysis.pb.cc
|
||||
)
|
||||
target_link_libraries(cac_agent
|
||||
PUBLIC
|
||||
protobuf::libprotoc
|
||||
protobuf::libprotobuf
|
||||
protobuf::libprotobuf-lite)
|
||||
target_include_directories(cac_agent PRIVATE ${AGENT_INCLUDES})
|
||||
# Builds the content analysis connector browser linker library. This library
|
||||
# is linked into the client in order to send content analysis requests to the
|
||||
# agent.
|
||||
add_library(cac_browser
|
||||
./browser/include/content_analysis/sdk/analysis_client.h
|
||||
./browser/src/client_base.cc
|
||||
./browser/src/client_base.h
|
||||
${PLATFORM_BROWSER_CODE}
|
||||
${PROJECT_BINARY_DIR}/gen/content_analysis/sdk/analysis.pb.cc
|
||||
)
|
||||
target_include_directories(cac_browser PRIVATE ${BROWSER_INCLUDES})
|
||||
target_link_libraries(cac_browser
|
||||
PUBLIC
|
||||
protobuf::libprotoc
|
||||
protobuf::libprotobuf
|
||||
protobuf::libprotobuf-lite)
|
||||
|
||||
# The demo agent executable.
|
||||
add_executable(agent
|
||||
./demo/agent.cc
|
||||
./demo/handler.h
|
||||
)
|
||||
target_include_directories(agent PRIVATE ${AGENT_INCLUDES})
|
||||
target_link_libraries(agent PRIVATE cac_agent)
|
||||
|
||||
# The demo client executable.
|
||||
add_executable(browser ./demo/client.cc)
|
||||
target_include_directories(browser PRIVATE ${BROWSER_INCLUDES})
|
||||
target_link_libraries(browser PRIVATE cac_browser)
|
||||
|
||||
28
third_party/content_analysis_sdk/LICENSE
vendored
Normal file
28
third_party/content_analysis_sdk/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
70
third_party/content_analysis_sdk/README.md
vendored
Normal file
70
third_party/content_analysis_sdk/README.md
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Google Chrome Content Analysis Connector Agent SDK
|
||||
|
||||
The Google Chrome Content Analysis Connector provides an official mechanism
|
||||
allowing Data Loss Prevention (DLP) agents to more deeply integrate their
|
||||
services with Google Chrome.
|
||||
|
||||
DLP agents are background processes on managed computers that allow enterprises
|
||||
to monitor locally running applications for data exfiltration events. They can
|
||||
allow/block these activities based on customer defined DLP policies.
|
||||
|
||||
This repository contains the SDK that DLP agents may use to become service
|
||||
providers for the Google Chrome Content Analysis Connector. The code that must
|
||||
be compiled and linked into the content analysis agent is located in the `agent`
|
||||
subdirectory.
|
||||
|
||||
A demo implementation of a service provider is located in the `demo` subdirectory.
|
||||
|
||||
The code that must be compiled and linked into Google Chrome is located in
|
||||
the `browser` subdirectory.
|
||||
|
||||
The Protocol Buffer serialization format is used to serialize messages between the
|
||||
browser and the agent. The protobuf definitions used can be found in the `proto`
|
||||
subdirectory.
|
||||
|
||||
## Google Protocol Buffers
|
||||
|
||||
This SDK depends on Google Protocol Buffers version 3.18 or later. It may be
|
||||
installed from Google's [download page](https://developers.google.com/protocol-buffers/docs/downloads#release-packages)
|
||||
for your developement platform. It may also be installed using a package
|
||||
manager.
|
||||
|
||||
The included prepare_build scripts use the Microsoft [vcpkg](https://github.com/microsoft/vcpkg)
|
||||
package manager to install protobuf. vcpkg is available on all supported
|
||||
platforms.
|
||||
|
||||
## Build
|
||||
|
||||
### Pre-requisites
|
||||
|
||||
The following must be installed on the computer before building the demo:
|
||||
|
||||
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) version 2.33 or later.
|
||||
- [cmake](https://cmake.org/install/) version 3.23 or later.
|
||||
- A C++ compiler toolchain for your platform.
|
||||
- On linux, the `realpath` shell tool, available in the `coreutils` package.
|
||||
In Debian-based distributions use `sudo apt intall coreutils`.
|
||||
In Red Hat distributions use `sudo yum install coreutils`.
|
||||
- On Mac, use `brew install cmake coreutils pkg-config googletest` or an equivalent setup
|
||||
|
||||
### Running prepare_build
|
||||
|
||||
First get things ready by installing required dependencies:
|
||||
```
|
||||
$SDK_DIR/prepare_build <build-dir>
|
||||
```
|
||||
where `<build-dir>` is the path to a directory where the demo will be built.
|
||||
By default, if no argument is provided, a directory named `build` will be
|
||||
created in the project root. Any output within the `build/` directory will
|
||||
be ignored by version control.
|
||||
|
||||
`prepare_build` performs the following steps:
|
||||
1. Downloads the vcpkg package manager.
|
||||
2. Downloads and builds the Google Protocol Buffers library.
|
||||
3. Creates build files for your specific platform.
|
||||
|
||||
### Cmake Targets
|
||||
|
||||
To build the demo run the command `cmake --build <build-dir>`.
|
||||
|
||||
To build the protocol buffer targets run the command `cmake --build <build-dir> --target proto`
|
||||
38
third_party/content_analysis_sdk/agent/README.md
vendored
Normal file
38
third_party/content_analysis_sdk/agent/README.md
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Google Chrome Content Analysis Connector Agent SDK
|
||||
|
||||
This directory holds the Google Chrome Content Analysis Connector Agent SDK.
|
||||
An Agent is an OS process running on the same computer as Google Chrome
|
||||
that listens for and processes content analysis requests from the browser.
|
||||
Supported OSes are Windows, Mac, and Linux.
|
||||
|
||||
## Google Protocol Buffers
|
||||
|
||||
This SDK depends on Google Protocol Buffers version 3.18 or later. It may be
|
||||
installed from Google's [download page](https://developers.google.com/protocol-buffers/docs/downloads#release-packages)
|
||||
for your developement platform. It may also be installed using a package
|
||||
manager.
|
||||
|
||||
The included demo uses the Microsoft [vcpkg](https://github.com/microsoft/vcpkg)
|
||||
package manager to install protobuf. vcpkg is available on all supported
|
||||
platforms. See the demo for more details.
|
||||
|
||||
## Adding the SDK into an agent
|
||||
|
||||
Add the SDK to a content analysis agent as follows:
|
||||
|
||||
1. Clone the SDK from [Github](https://github.com/chromium/content_analysis_sdk).
|
||||
This document assumes that the SDK is downloaded and extracted into the
|
||||
directory $SDK_DIR.
|
||||
|
||||
2. Add the directory $SDK_DIR/include to the include path of the agent
|
||||
code base.
|
||||
|
||||
3. Add all the source files in the directory $SDK_DIR/src to the agent build.
|
||||
Note that files ending in _win.cc or _win.h are meant only for Windows, files
|
||||
ending in _posix.cc or _posix.h are meant only for Linux, and files ending in
|
||||
_mac.cc or _mac.h are meant only for Mac.
|
||||
|
||||
4. Reference the SDK in agent code using:
|
||||
```
|
||||
#include "content_analysis/sdk/local_analysis.h"
|
||||
```
|
||||
288
third_party/content_analysis_sdk/agent/include/content_analysis/sdk/analysis_agent.h
vendored
Normal file
288
third_party/content_analysis_sdk/agent/include/content_analysis/sdk/analysis_agent.h
vendored
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_AGENT_INCLUDE_CONTENT_ANALYSIS_SDK_ANALYSIS_AGENT_H_
|
||||
#define CONTENT_ANALYSIS_AGENT_INCLUDE_CONTENT_ANALYSIS_SDK_ANALYSIS_AGENT_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "content_analysis/sdk/analysis.pb.h"
|
||||
#include "content_analysis/sdk/result_codes.h"
|
||||
|
||||
// This is the main include file for code using Content Analysis Connector
|
||||
// Agent SDK. No other include is needed.
|
||||
//
|
||||
// An agent begins by creating an instance of Agent using the factory
|
||||
// function Agent::Create(). This instance should live as long as the agent
|
||||
// intends to receive content analysis requests from Google Chrome.
|
||||
//
|
||||
// Agent::Create() must be passed an object that implements the
|
||||
// AgentEventHandler interface. Methods on this interface will be called
|
||||
// at the approriate time to handle specific events. The events are:
|
||||
//
|
||||
// - A Google Chrome browser has started or stopped. This events contains
|
||||
// information about the browser such as process id and executable path.
|
||||
// - A request to analyze content. The agent reads and analyses the
|
||||
// request in to determine a verdict: allow or block. When the verdict is
|
||||
// known the response is sent back to Google Chrome.
|
||||
// - An acknowledgement that Google Chrome has properly received the agent's
|
||||
// verdict.
|
||||
//
|
||||
// The agent is not required to serialize event handling. That is, content
|
||||
// analysis events can be analyze in the background and the response does
|
||||
// not need to be sent before OnAnalysisRequested() returns.
|
||||
//
|
||||
// Google Chrome thottles the number of requests sent to the agent to 5
|
||||
// current requests at a time but this is subject to change.
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Represents information about one instance of a Google Chrome browser
|
||||
// process that is connected to the agent.
|
||||
struct BrowserInfo {
|
||||
unsigned long pid = 0; // Process ID of Google Chrome browser process.
|
||||
std::string binary_path; // The full path to the process's main binary.
|
||||
};
|
||||
|
||||
// Represents one content analysis request as generated by a given user action
|
||||
// in Google Chrome.
|
||||
//
|
||||
// The agent should retrieve information about the content analysis request
|
||||
// using the GetRequest() method. The agent should analyze the request and
|
||||
// update the response, returned by GetResponse(), with a verdict (allow or
|
||||
// block). Once the verdict is set the response can be sent back to Google
|
||||
// Chrome by calling Send().
|
||||
//
|
||||
// The default verdict is to allow the requested user action. If the final
|
||||
// verdict is to allow then the agent does not need to update the response and
|
||||
// can simply call Send().
|
||||
//
|
||||
// If the final verdict should be to block, the agent should first update the
|
||||
// response by calling SetEventVerdictToBlock() before calling Send().
|
||||
//
|
||||
// This class is not thread safe. However, it may be passed to another thread
|
||||
// as long as the agent properly serializses access to the event.
|
||||
//
|
||||
// See the demo directory for an example of how to use this class.
|
||||
class ContentAnalysisEvent {
|
||||
public:
|
||||
virtual ~ContentAnalysisEvent() = default;
|
||||
|
||||
// Prepares the event for graceful shutdown. Upon return calls to all
|
||||
// other methods of this class will fail.
|
||||
virtual ResultCode Close() = 0;
|
||||
|
||||
// Retrives information about the browser that generated this content
|
||||
// analysis event.
|
||||
virtual const BrowserInfo& GetBrowserInfo() const = 0;
|
||||
|
||||
// Retrieves a read-only reference to the content analysis request received
|
||||
// from Google Chrome.
|
||||
virtual const ContentAnalysisRequest& GetRequest() const = 0;
|
||||
|
||||
// Retrieves a writable reference to the content analysis response that will
|
||||
// be sent to Google Chrome as the verdict for the request of this event.
|
||||
// The agent may modify this response in place before calling Send().
|
||||
virtual ContentAnalysisResponse& GetResponse() = 0;
|
||||
|
||||
// Send the verdict to Google Chrome. Once this method is called further
|
||||
// changes to the response are ignored.
|
||||
virtual ResultCode Send() = 0;
|
||||
|
||||
// Returns a string containing internal state of the object that is useful
|
||||
// for debugging.
|
||||
virtual std::string DebugString() const = 0;
|
||||
|
||||
protected:
|
||||
ContentAnalysisEvent() = default;
|
||||
ContentAnalysisEvent(const ContentAnalysisEvent& rhs) = delete;
|
||||
ContentAnalysisEvent(ContentAnalysisEvent&& rhs) = delete;
|
||||
ContentAnalysisEvent& operator=(const ContentAnalysisEvent& rhs) = delete;
|
||||
ContentAnalysisEvent& operator=(ContentAnalysisEvent&& rhs) = delete;
|
||||
|
||||
};
|
||||
|
||||
// Agents should implement this interface in order to handle events as needed.
|
||||
//
|
||||
// OnBrowserConnected() and OnBrowserDisonnected() notify the agent when
|
||||
// instances of Google Chome start and stop. The agent may perform any one-time
|
||||
// actions as required for these events. The default action is to do nothing
|
||||
// for both events. If the agent does not need perform any special actions
|
||||
// these methods do not need to be overridden.
|
||||
//
|
||||
// OnAnalysisRequested() notifies the agent of a new content analysis request
|
||||
// from Google Chrome. The agent should perform the analysis and respond to
|
||||
// the event. It is not required for the agent complete the analysis and
|
||||
// respond to before this callback returns. The agent may pass the
|
||||
// ContentAnalysisEvent to a background task and respond when ready. This
|
||||
// callback has no default action and agents must override it.
|
||||
//
|
||||
// OnResponseAcknowledged() notifies the agent that Google Chrome has received
|
||||
// the content analysis response and how it has handled it.
|
||||
class AgentEventHandler {
|
||||
public:
|
||||
AgentEventHandler() = default;
|
||||
virtual ~AgentEventHandler() = default;
|
||||
|
||||
// Called when a new Google Chrome browser instance connects to the agent.
|
||||
// This is always called before the first OnAnalysisRequested() from that
|
||||
// browser.
|
||||
virtual void OnBrowserConnected(const BrowserInfo& info) {}
|
||||
|
||||
// Called when a Google Chrome browser instance disconnects from the agent.
|
||||
// The agent will no longer receive new content analysis requests from this
|
||||
// browser.
|
||||
virtual void OnBrowserDisconnected(const BrowserInfo& info) {}
|
||||
|
||||
// Called when a Google Chrome browser requests a content analysis.
|
||||
virtual void OnAnalysisRequested(
|
||||
std::unique_ptr<ContentAnalysisEvent> event) = 0;
|
||||
|
||||
// Called when a Google Chrome browser acknowledges the content analysis
|
||||
// response from the agent. The default action is to do nothing.
|
||||
// If the agent does not need perform any special actions this methods does
|
||||
// not need to be overridden.
|
||||
virtual void OnResponseAcknowledged(
|
||||
const ContentAnalysisAcknowledgement& ack) {}
|
||||
|
||||
// Called when a Google Chrome browser asks the agent to cancels one or
|
||||
// more content analysis requests. This happens when the user presses the
|
||||
// Cancel button in the in-progress dialog. This is expected to be a best
|
||||
// effort only; agents may choose to ignore this message or possibly only
|
||||
// cancel a subset of requests with the given user action id.
|
||||
//
|
||||
// The default action is to do nothing. If the agent does not need perform
|
||||
// any special actions this methods does not need to be overridden.
|
||||
virtual void OnCancelRequests(
|
||||
const ContentAnalysisCancelRequests& cancel) {}
|
||||
|
||||
// Called whenever the Agent implementation detects an error. `context`
|
||||
// is a string that provide a hint to the handler as to where the error
|
||||
// happened in the agent. `error` represent the actual error detected.
|
||||
virtual void OnInternalError(const char* context, ResultCode error) {}
|
||||
};
|
||||
|
||||
// Represents an agent that can perform content analysis for the Google Chrome
|
||||
// browser. This class holds the server endpoint that Google Chrome connects
|
||||
// to when content analysis is required.
|
||||
//
|
||||
// Agent instances should outlive all ContentAnalysisEvent instances created
|
||||
// with it. Agent instances are not thread safe except for Stop() which can be
|
||||
// called from any thread to shutdown the agent. Outstanding
|
||||
// ContentAnalysisEvents created from this agent may or may not still complete.
|
||||
//
|
||||
// See the demo directory for an example of how to use this class.
|
||||
class Agent {
|
||||
public:
|
||||
// Configuration options where creating an agent. `name` is used to create
|
||||
// a channel between the agent and Google Chrome.
|
||||
struct Config {
|
||||
// Used to create a channel between the agent and Google Chrome. Both must
|
||||
// use the same name to properly rendezvous with each other. The channel
|
||||
// is platform specific.
|
||||
std::string name;
|
||||
|
||||
// Set to true if there is a different agent instance per OS user. Defaults
|
||||
// to false.
|
||||
bool user_specific = false;
|
||||
};
|
||||
|
||||
// Creates a new agent instance. If successful, an agent is returned.
|
||||
// Otherwise a nullptr is returned and `rc` contains the reason for the
|
||||
// failure.
|
||||
static std::unique_ptr<Agent> Create(
|
||||
Config config,
|
||||
std::unique_ptr<AgentEventHandler> handler,
|
||||
ResultCode* rc);
|
||||
|
||||
virtual ~Agent() = default;
|
||||
|
||||
// Returns the configuration parameters used to create the agent.
|
||||
virtual const Config& GetConfig() const = 0;
|
||||
|
||||
// Handles events triggered on this agent and calls the coresponding
|
||||
// callbacks in the AgentEventHandler. This method is blocking and returns
|
||||
// when Stop() is called or if an error occurs.
|
||||
virtual ResultCode HandleEvents() = 0;
|
||||
|
||||
// Prepares the agent for graceful shutdown. Any function blocked on
|
||||
// HandleEvents() will return. It is safe to call this method from any
|
||||
// thread.
|
||||
virtual ResultCode Stop() = 0;
|
||||
|
||||
// Returns a string containing internal state of the object that is useful
|
||||
// for debugging.
|
||||
virtual std::string DebugString() const = 0;
|
||||
|
||||
protected:
|
||||
Agent() = default;
|
||||
Agent(const Agent& rhs) = delete;
|
||||
Agent(Agent&& rhs) = delete;
|
||||
Agent& operator=(const Agent& rhs) = delete;
|
||||
Agent& operator=(Agent&& rhs) = delete;
|
||||
};
|
||||
|
||||
// Update the tag or status of `response`. This function assumes that the
|
||||
// response contains only one Result. If one already exists it is updated
|
||||
// otherwise a new Result is created.
|
||||
//
|
||||
// The response contained within ContentAnalysisEvent has already been updated.
|
||||
// This function is useful only when create a new instance of
|
||||
// ContentAnalysisResponse.
|
||||
//
|
||||
// If `tag` is not empty it will replace the result's tag.
|
||||
// If `status` is not STATUS_UNKNOWN it will will replace the result's status.
|
||||
ResultCode UpdateResponse(ContentAnalysisResponse& response,
|
||||
const std::string& tag,
|
||||
ContentAnalysisResponse::Result::Status status);
|
||||
|
||||
// Sets the response verdict of an event to `action`. This is a convenience
|
||||
// function that is equivalent to the following:
|
||||
//
|
||||
// auto result = event->GetResponse().mutable_results(0);
|
||||
// auto rule = result->mutable_triggered_rules(0);
|
||||
// rule->set_action(action);
|
||||
//
|
||||
// This function assumes the event's response has already been initialized
|
||||
// using UpdateResponse().
|
||||
ResultCode SetEventVerdictTo(
|
||||
ContentAnalysisEvent* event,
|
||||
ContentAnalysisResponse::Result::TriggeredRule::Action action);
|
||||
|
||||
// Sets the reponse verdict of an event to "block". This is a convenience
|
||||
// function that is equivalent to the following:
|
||||
//
|
||||
// SetEventVerdictTo(event,
|
||||
// ContentAnalysisResponse::Result::TriggeredRule::BLOCK);
|
||||
ResultCode SetEventVerdictToBlock(ContentAnalysisEvent* event);
|
||||
|
||||
// Helper class to handle the lifetime and access of print data.
|
||||
class ScopedPrintHandle {
|
||||
public:
|
||||
virtual ~ScopedPrintHandle() = default;
|
||||
virtual const char* data() = 0;
|
||||
virtual size_t size() = 0;
|
||||
|
||||
protected:
|
||||
ScopedPrintHandle() = default;
|
||||
|
||||
ScopedPrintHandle(const ScopedPrintHandle&) = delete;
|
||||
ScopedPrintHandle& operator=(const ScopedPrintHandle&) = delete;
|
||||
|
||||
ScopedPrintHandle(ScopedPrintHandle&&) = default;
|
||||
ScopedPrintHandle& operator=(ScopedPrintHandle&&) = default;
|
||||
};
|
||||
|
||||
// Returns a `ScopedPrintHandle` initialized from the request's print data
|
||||
// if it exists.
|
||||
std::unique_ptr<ScopedPrintHandle>
|
||||
CreateScopedPrintHandle(const ContentAnalysisRequest& request,
|
||||
int64_t browser_pid);
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_AGENT_INCLUDE_CONTENT_ANALYSIS_SDK_ANALYSIS_AGENT_H_
|
||||
36
third_party/content_analysis_sdk/agent/include/content_analysis/sdk/result_codes.h
vendored
Normal file
36
third_party/content_analysis_sdk/agent/include/content_analysis/sdk/result_codes.h
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_AGENT_INCLUDE_CONTENT_ANALYSIS_SDK_RESULT_CODES_H_
|
||||
#define CONTENT_ANALYSIS_AGENT_INCLUDE_CONTENT_ANALYSIS_SDK_RESULT_CODES_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Result codes of methods and functions.
|
||||
|
||||
#define RC_RECOVERABLE(RC, MSG) RC,
|
||||
#define RC_UNRECOVERABLE(RC, MSG) RC,
|
||||
enum class ResultCode {
|
||||
#include "content_analysis/sdk/result_codes.inc"
|
||||
};
|
||||
#undef RC_RECOVERABLE
|
||||
#undef RC_UNRECOVERABLE
|
||||
|
||||
// Returns true if the error is recoverable. A recoverable errors means the
|
||||
// agent may still receive new requests from Google Chrome. An unrecoverable
|
||||
// error means the agent is unlikely to get more request from Google Chrome.
|
||||
inline bool IsRecoverableError(ResultCode rc) {
|
||||
return rc < ResultCode::ERR_FIRST_UNRECOVERABLE_ERROR;
|
||||
}
|
||||
|
||||
// Returns a human readable error for the given result code.
|
||||
const char* ResultCodeToString(ResultCode rc);
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_AGENT_INCLUDE_CONTENT_ANALYSIS_SDK_RESULT_CODES_H_
|
||||
25
third_party/content_analysis_sdk/agent/include/content_analysis/sdk/result_codes.inc
vendored
Normal file
25
third_party/content_analysis_sdk/agent/include/content_analysis/sdk/result_codes.inc
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// This file is #included from C++ headers and source code to generate code
|
||||
// specific to each ResultCode. The including code is expected to #define
|
||||
// macros for RC_RECOVERABLE and RC_UNRECOVERABLE before #including this file
|
||||
// and then #undef then after use.
|
||||
|
||||
RC_RECOVERABLE(OK, "Operation completed successfully.")
|
||||
RC_RECOVERABLE(ERR_MISSING_RESULT, "Response is missing a result message.")
|
||||
RC_RECOVERABLE(ERR_RESPONSE_ALREADY_SENT, "A resonse has already been sent for this request.")
|
||||
RC_RECOVERABLE(ERR_MISSING_REQUEST_TOKEN, "The request is missing a request token.")
|
||||
RC_RECOVERABLE(ERR_AGENT_NOT_INITIALIZED, "The agent is not proplerly initialized to handle events.")
|
||||
RC_RECOVERABLE(ERR_INVALID_REQUEST_FROM_BROWSER, "The browser sent an incorrectly formatted message.")
|
||||
RC_RECOVERABLE(ERR_IO_PENDING, "IO incomplete, the operation is still pending.")
|
||||
RC_RECOVERABLE(ERR_MORE_DATA, "There is more data to read before the entire message has been received.")
|
||||
RC_RECOVERABLE(ERR_CANNOT_GET_BROWSER_PID, "Cannot get process Id of browser.")
|
||||
RC_RECOVERABLE(ERR_CANNOT_GET_BROWSER_BINARY_PATH, "Cannot get the full path to the brower's main binary file.")
|
||||
RC_RECOVERABLE(ERR_BROKEN_PIPE, "Browser process has disconnected.")
|
||||
RC_RECOVERABLE(ERR_UNEXPECTED, "An internal error has occured.")
|
||||
|
||||
// All unrecoverable errors should be declared below ERR_FIRST_UNRECOVERABLE_ERROR.
|
||||
RC_UNRECOVERABLE(ERR_FIRST_UNRECOVERABLE_ERROR, "Marker for the first unrecoverable error.")
|
||||
RC_UNRECOVERABLE(ERR_AGENT_ALREADY_EXISTS, "Another process is already running as an agent on this computer.")
|
||||
RC_UNRECOVERABLE(ERR_AGENT_EVENT_HANDLER_NOT_SPECIFIED, "An agent handler was not specified when creating an agent.")
|
||||
RC_UNRECOVERABLE(ERR_CANNOT_CREATE_AGENT_STOP_EVENT, "Could not create event to signal the agent to stop.")
|
||||
RC_UNRECOVERABLE(ERR_INVALID_CHANNEL_NAME, "Invalid channel name specified in Agent::Config.")
|
||||
RC_UNRECOVERABLE(ERR_CANNOT_CREATE_CHANNEL_IO_EVENT, "Could not create event to perform async IO with a client.")
|
||||
42
third_party/content_analysis_sdk/agent/src/agent_base.cc
vendored
Normal file
42
third_party/content_analysis_sdk/agent/src/agent_base.cc
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "agent_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
AgentBase::AgentBase(Config config, std::unique_ptr<AgentEventHandler> handler)
|
||||
: config_(std::move(config)), handler_(std::move(handler)) {}
|
||||
|
||||
const Agent::Config& AgentBase::GetConfig() const {
|
||||
return config_;
|
||||
}
|
||||
|
||||
ResultCode AgentBase::Stop() {
|
||||
return ResultCode::OK;
|
||||
}
|
||||
|
||||
ResultCode AgentBase::NotifyError(const char* context, ResultCode error) {
|
||||
if (handler_) {
|
||||
handler_->OnInternalError(context, error);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
#define RC_RECOVERABLE(RC, MSG) case ResultCode::RC: return MSG;
|
||||
#define RC_UNRECOVERABLE(RC, MSG) case ResultCode::RC: return MSG;
|
||||
const char* ResultCodeToString(ResultCode rc) {
|
||||
switch (rc) {
|
||||
#include "content_analysis/sdk/result_codes.inc"
|
||||
}
|
||||
return "Unknown error code.";
|
||||
}
|
||||
#undef RC_RECOVERABLE
|
||||
#undef RC_UNRECOVERABLE
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
40
third_party/content_analysis_sdk/agent/src/agent_base.h
vendored
Normal file
40
third_party/content_analysis_sdk/agent/src/agent_base.h
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_AGENT_SRC_AGENT_BASE_H_
|
||||
#define CONTENT_ANALYSIS_AGENT_SRC_AGENT_BASE_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "content_analysis/sdk/analysis_agent.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Base Agent class with code common to all platforms.
|
||||
class AgentBase : public Agent {
|
||||
public:
|
||||
// Agent:
|
||||
const Config& GetConfig() const override;
|
||||
ResultCode Stop() override;
|
||||
|
||||
protected:
|
||||
AgentBase(Config config, std::unique_ptr<AgentEventHandler> handler);
|
||||
|
||||
AgentEventHandler* handler() const { return handler_.get(); }
|
||||
const Config& configuration() const { return config_; }
|
||||
|
||||
// Notifies the handler of the given error. Returns the error
|
||||
// passed into the method.
|
||||
ResultCode NotifyError(const char* context, ResultCode error);
|
||||
|
||||
private:
|
||||
Config config_;
|
||||
std::unique_ptr<AgentEventHandler> handler_;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_AGENT_SRC_AGENT_BASE_H_
|
||||
34
third_party/content_analysis_sdk/agent/src/agent_mac.cc
vendored
Normal file
34
third_party/content_analysis_sdk/agent/src/agent_mac.cc
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "agent_mac.h"
|
||||
#include "event_mac.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// static
|
||||
std::unique_ptr<Agent> Agent::Create(
|
||||
Config config,
|
||||
std::unique_ptr<AgentEventHandler> handler,
|
||||
ResultCode* rc) {
|
||||
*rc = ResultCode::ERR_UNEXPECTED;
|
||||
return std::make_unique<AgentMac>(std::move(config), std::move(handler));
|
||||
}
|
||||
|
||||
AgentMac::AgentMac(
|
||||
Config config,
|
||||
std::unique_ptr<AgentEventHandler> handler)
|
||||
: AgentBase(std::move(config), std::move(handler)) {}
|
||||
|
||||
ResultCode AgentMac::HandleEvents() {
|
||||
return ResultCode::ERR_UNEXPECTED;
|
||||
}
|
||||
|
||||
std::string AgentMac::DebugString() const {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
27
third_party/content_analysis_sdk/agent/src/agent_mac.h
vendored
Normal file
27
third_party/content_analysis_sdk/agent/src/agent_mac.h
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_SRC_AGENT_MAC_H_
|
||||
#define CONTENT_ANALYSIS_SRC_AGENT_MAC_H_
|
||||
|
||||
#include "agent_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Agent implementaton for macOS.
|
||||
class AgentMac : public AgentBase {
|
||||
public:
|
||||
AgentMac(Config config, std::unique_ptr<AgentEventHandler> handler);
|
||||
|
||||
ResultCode HandleEvents() override;
|
||||
std::string DebugString() const override;
|
||||
|
||||
// TODO(rogerta): Fill in implementation.
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_SRC_AGENT_MAC_H_
|
||||
36
third_party/content_analysis_sdk/agent/src/agent_posix.cc
vendored
Normal file
36
third_party/content_analysis_sdk/agent/src/agent_posix.cc
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "agent_posix.h"
|
||||
#include "event_posix.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// static
|
||||
std::unique_ptr<Agent> Agent::Create(
|
||||
Config config,
|
||||
std::unique_ptr<AgentEventHandler> handler,
|
||||
ResultCode* rc) {
|
||||
*rc = ResultCode::ERR_UNEXPECTED;
|
||||
return std::make_unique<AgentPosix>(std::move(config), std::move(handler));
|
||||
}
|
||||
|
||||
AgentPosix::AgentPosix(
|
||||
Config config,
|
||||
std::unique_ptr<AgentEventHandler> handler)
|
||||
: AgentBase(std::move(config), std::move(handler)) {}
|
||||
|
||||
ResultCode AgentPosix::HandleEvents() {
|
||||
return ResultCode::ERR_UNEXPECTED;
|
||||
}
|
||||
|
||||
std::string AgentPosix::DebugString() const {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
27
third_party/content_analysis_sdk/agent/src/agent_posix.h
vendored
Normal file
27
third_party/content_analysis_sdk/agent/src/agent_posix.h
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_SRC_AGENT_POSIX_H_
|
||||
#define CONTENT_ANALYSIS_SRC_AGENT_POSIX_H_
|
||||
|
||||
#include "agent_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Agent implementaton for linux.
|
||||
class AgentPosix : public AgentBase {
|
||||
public:
|
||||
AgentPosix(Config config, std::unique_ptr<AgentEventHandler> handler);
|
||||
|
||||
ResultCode HandleEvents() override;
|
||||
std::string DebugString() const override;
|
||||
|
||||
// TODO(rogerta): Fill in implementation.
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_SRC_AGENT_POSIX_H_
|
||||
28
third_party/content_analysis_sdk/agent/src/agent_utils_win.cc
vendored
Normal file
28
third_party/content_analysis_sdk/agent/src/agent_utils_win.cc
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "content_analysis/sdk/result_codes.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
#define ERR_TO_RC(ERR, RC) case ERR: return ResultCode::RC;
|
||||
|
||||
ResultCode ErrorToResultCode(DWORD err) {
|
||||
switch (err) {
|
||||
ERR_TO_RC(ERROR_SUCCESS, OK);
|
||||
ERR_TO_RC(ERROR_ACCESS_DENIED, ERR_AGENT_ALREADY_EXISTS);
|
||||
ERR_TO_RC(ERROR_BROKEN_PIPE, ERR_BROKEN_PIPE);
|
||||
ERR_TO_RC(ERROR_INVALID_NAME, ERR_INVALID_CHANNEL_NAME);
|
||||
ERR_TO_RC(ERROR_MORE_DATA, ERR_MORE_DATA);
|
||||
ERR_TO_RC(ERROR_IO_PENDING, ERR_IO_PENDING);
|
||||
default:
|
||||
return ResultCode::ERR_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
19
third_party/content_analysis_sdk/agent/src/agent_utils_win.h
vendored
Normal file
19
third_party/content_analysis_sdk/agent/src/agent_utils_win.h
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_SRC_EVENT_MAC_H_
|
||||
#define CONTENT_ANALYSIS_SRC_EVENT_MAC_H_
|
||||
|
||||
#include "event_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Maps a Windows error status code (ERROR_xxx codes) to an SDK result code.
|
||||
ResultCode ErrorToResultCode(DWORD err);
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_SRC_EVENT_MAC_H_
|
||||
546
third_party/content_analysis_sdk/agent/src/agent_win.cc
vendored
Normal file
546
third_party/content_analysis_sdk/agent/src/agent_win.cc
vendored
Normal file
|
|
@ -0,0 +1,546 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <windows.h>
|
||||
#include <sddl.h>
|
||||
|
||||
#include "common/utils_win.h"
|
||||
|
||||
#include "agent_utils_win.h"
|
||||
#include "agent_win.h"
|
||||
#include "event_win.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// The minimum number of pipe in listening mode. This is greater than one to
|
||||
// handle the case of multiple instance of Google Chrome browser starting
|
||||
// at the same time.
|
||||
const DWORD kMinNumListeningPipeInstances = 2;
|
||||
|
||||
// The minimum number of handles to wait on. This is the minimum number
|
||||
// of pipes in listening mode plus the stop event.
|
||||
const DWORD kMinNumWaitHandles = kMinNumListeningPipeInstances + 1;
|
||||
|
||||
// static
|
||||
std::unique_ptr<Agent> Agent::Create(
|
||||
Config config,
|
||||
std::unique_ptr<AgentEventHandler> handler,
|
||||
ResultCode* rc) {
|
||||
auto agent = std::make_unique<AgentWin>(std::move(config), std::move(handler), rc);
|
||||
return *rc == ResultCode::OK ? std::move(agent) : nullptr;
|
||||
}
|
||||
|
||||
AgentWin::Connection::Connection(const std::string& pipename,
|
||||
bool user_specific,
|
||||
AgentEventHandler* handler,
|
||||
bool is_first_pipe,
|
||||
ResultCode* rc)
|
||||
: handler_(handler) {
|
||||
*rc = ResultCode::OK;
|
||||
memset(&overlapped_, 0, sizeof(overlapped_));
|
||||
// Create a manual reset event as specified for overlapped IO.
|
||||
// Use default security attriutes and no name since this event is not
|
||||
// shared with other processes.
|
||||
overlapped_.hEvent = CreateEvent(/*securityAttr=*/nullptr,
|
||||
/*manualReset=*/TRUE,
|
||||
/*initialState=*/FALSE,
|
||||
/*name=*/nullptr);
|
||||
if (!overlapped_.hEvent) {
|
||||
*rc = ResultCode::ERR_CANNOT_CREATE_CHANNEL_IO_EVENT;
|
||||
return;
|
||||
}
|
||||
|
||||
*rc = ResetInternal(pipename, user_specific, is_first_pipe);
|
||||
}
|
||||
|
||||
AgentWin::Connection::~Connection() {
|
||||
Cleanup();
|
||||
|
||||
if (handle_ != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(handle_);
|
||||
}
|
||||
|
||||
// Invalid event handles are represented as null.
|
||||
if (overlapped_.hEvent) {
|
||||
CloseHandle(overlapped_.hEvent);
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode AgentWin::Connection::Reset(
|
||||
const std::string& pipename,
|
||||
bool user_specific) {
|
||||
return NotifyIfError("ConnectionReset",
|
||||
ResetInternal(pipename, user_specific, false));
|
||||
}
|
||||
|
||||
ResultCode AgentWin::Connection::HandleEvent(HANDLE handle) {
|
||||
auto rc = ResultCode::OK;
|
||||
DWORD count;
|
||||
BOOL success = GetOverlappedResult(handle, &overlapped_, &count,
|
||||
/*wait=*/FALSE);
|
||||
if (!is_connected_) {
|
||||
// This connection is currently listing for a new connection from a Google
|
||||
// Chrome browser. If the result is a success, this means the browser has
|
||||
// connected as expected. Otherwise an error occured so report it to the
|
||||
// caller.
|
||||
if (success) {
|
||||
// A Google Chrome browser connected to the agent. Reset this
|
||||
// connection object to handle communication with the browser and then
|
||||
// tell the handler about it.
|
||||
|
||||
is_connected_ = true;
|
||||
buffer_.resize(internal::kBufferSize);
|
||||
|
||||
rc = BuildBrowserInfo();
|
||||
if (rc == ResultCode::OK) {
|
||||
handler_->OnBrowserConnected(browser_info_);
|
||||
}
|
||||
} else {
|
||||
rc = ErrorToResultCode(GetLastError());
|
||||
NotifyIfError("GetOverlappedResult", rc);
|
||||
}
|
||||
} else {
|
||||
// Some data has arrived from Google Chrome. This data is (part of) an
|
||||
// instance of the proto message `ChromeToAgent`.
|
||||
//
|
||||
// If the message is small it is received in by one call to ReadFile().
|
||||
// If the message is larger it is received in by multiple calls to
|
||||
// ReadFile().
|
||||
//
|
||||
// `success` is true if the data just read is the last bytes for a message.
|
||||
// Otherwise it is false.
|
||||
rc = OnReadFile(success, count);
|
||||
}
|
||||
|
||||
// If all data has been read, queue another read.
|
||||
if (rc == ResultCode::OK || rc == ResultCode::ERR_MORE_DATA) {
|
||||
rc = QueueReadFile(rc == ResultCode::OK);
|
||||
}
|
||||
|
||||
if (rc != ResultCode::OK && rc != ResultCode::ERR_IO_PENDING &&
|
||||
rc != ResultCode::ERR_MORE_DATA) {
|
||||
Cleanup();
|
||||
} else {
|
||||
// Don't propagate all the "success" error codes to the called to keep
|
||||
// this simpler.
|
||||
rc = ResultCode::OK;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void AgentWin::Connection::AppendDebugString(std::stringstream& state) const {
|
||||
state << "{handle=" << handle_;
|
||||
state << " connected=" << is_connected_;
|
||||
state << " pid=" << browser_info_.pid;
|
||||
state << " rsize=" << read_size_;
|
||||
state << " fsize=" << final_size_;
|
||||
state << "}";
|
||||
}
|
||||
|
||||
ResultCode AgentWin::Connection::ConnectPipe() {
|
||||
// In overlapped mode, connecting to a named pipe always returns false.
|
||||
if (ConnectNamedPipe(handle_, &overlapped_)) {
|
||||
return ErrorToResultCode(GetLastError());
|
||||
}
|
||||
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_IO_PENDING) {
|
||||
// Waiting for a Google Chrome Browser to connect.
|
||||
return ResultCode::OK;
|
||||
} else if (err == ERROR_PIPE_CONNECTED) {
|
||||
// A Google Chrome browser is already connected. Make sure event is in
|
||||
// signaled state in order to process the connection.
|
||||
if (SetEvent(overlapped_.hEvent)) {
|
||||
err = ERROR_SUCCESS;
|
||||
} else {
|
||||
err = GetLastError();
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorToResultCode(err);
|
||||
}
|
||||
|
||||
ResultCode AgentWin::Connection::ResetInternal(const std::string& pipename,
|
||||
bool user_specific,
|
||||
bool is_first_pipe) {
|
||||
auto rc = ResultCode::OK;
|
||||
|
||||
// If this is the not the first time, disconnect from any existing Google
|
||||
// Chrome browser. Otherwise creater a new pipe.
|
||||
if (handle_ != INVALID_HANDLE_VALUE) {
|
||||
if (!DisconnectNamedPipe(handle_)) {
|
||||
rc = ErrorToResultCode(GetLastError());
|
||||
}
|
||||
} else {
|
||||
rc = ErrorToResultCode(
|
||||
internal::CreatePipe(pipename, user_specific, is_first_pipe, &handle_));
|
||||
}
|
||||
|
||||
// Make sure event starts in reset state.
|
||||
if (rc == ResultCode::OK && !ResetEvent(overlapped_.hEvent)) {
|
||||
rc = ErrorToResultCode(GetLastError());
|
||||
}
|
||||
|
||||
if (rc == ResultCode::OK) {
|
||||
rc = ConnectPipe();
|
||||
}
|
||||
|
||||
if (rc != ResultCode::OK) {
|
||||
Cleanup();
|
||||
handle_ = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void AgentWin::Connection::Cleanup() {
|
||||
if (is_connected_ && handler_) {
|
||||
handler_->OnBrowserDisconnected(browser_info_);
|
||||
}
|
||||
|
||||
is_connected_ = false;
|
||||
browser_info_ = BrowserInfo();
|
||||
buffer_.clear();
|
||||
cursor_ = nullptr;
|
||||
read_size_ = 0;
|
||||
final_size_ = 0;
|
||||
|
||||
if (handle_ != INVALID_HANDLE_VALUE) {
|
||||
// Cancel all outstanding IO requests on this pipe by using a null for
|
||||
// overlapped.
|
||||
CancelIoEx(handle_, /*overlapped=*/nullptr);
|
||||
}
|
||||
|
||||
// This function does not close `handle_` or the event in `overlapped` so
|
||||
// that the server can resuse the pipe with an new Google Chrome browser
|
||||
// instance.
|
||||
}
|
||||
|
||||
ResultCode AgentWin::Connection::QueueReadFile(bool reset_cursor) {
|
||||
if (reset_cursor) {
|
||||
cursor_ = buffer_.data();
|
||||
read_size_ = buffer_.size();
|
||||
final_size_ = 0;
|
||||
}
|
||||
|
||||
// When this function is called there are the following possiblities:
|
||||
//
|
||||
// 1/ Data is already available and the buffer is filled in. ReadFile()
|
||||
// return TRUE and the event is set.
|
||||
// 2/ Data is not avaiable yet. ReadFile() returns FALSE and the last error
|
||||
// is ERROR_IO_PENDING(997) and the event is reset.
|
||||
// 3/ Some error occurred, like for example Google Chrome stops. ReadFile()
|
||||
// returns FALSE and the last error is something other than
|
||||
// ERROR_IO_PENDING, for example ERROR_BROKEN_PIPE(109). The event
|
||||
// state is unchanged.
|
||||
auto rc = ResultCode::OK;
|
||||
DWORD count;
|
||||
if (!ReadFile(handle_, cursor_, read_size_, &count, &overlapped_)) {
|
||||
DWORD err = GetLastError();
|
||||
rc = ErrorToResultCode(err);
|
||||
|
||||
// IO pending is not an error so don't notify.
|
||||
//
|
||||
// Ignore broken pipes for notifications since that happens when the Google
|
||||
// Chrome browser shuts down. The agent will be notified of a browser
|
||||
// disconnect in that case.
|
||||
//
|
||||
// More data means that `buffer_` was too small to read the entire message
|
||||
// from the browser. The buffer has already been resized. Another call to
|
||||
// ReadFile() is needed to get the remainder.
|
||||
if (rc != ResultCode::ERR_IO_PENDING &&
|
||||
rc != ResultCode::ERR_BROKEN_PIPE &&
|
||||
rc != ResultCode::ERR_MORE_DATA) {
|
||||
NotifyIfError("QueueReadFile", rc, err);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
ResultCode AgentWin::Connection::OnReadFile(BOOL done_reading, DWORD count) {
|
||||
final_size_ += count;
|
||||
|
||||
// If `done_reading` is TRUE, this means the full message has been read.
|
||||
// Call the appropriate handler method.
|
||||
if (done_reading) {
|
||||
return CallHandler();
|
||||
}
|
||||
|
||||
// Otherwise there are two possibilities:
|
||||
//
|
||||
// 1/ The last error is ERROR_MORE_DATA(234). This means there are more
|
||||
// bytes to read before the request message is complete. Resize the
|
||||
// buffer and adjust the cursor. The caller will queue up another read
|
||||
// and wait. don't notify the handler since this is not an error.
|
||||
// 2/ Some error occured. In this case notify the handler and return the
|
||||
// error.
|
||||
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_MORE_DATA) {
|
||||
read_size_ = internal::kBufferSize;
|
||||
buffer_.resize(buffer_.size() + read_size_);
|
||||
cursor_ = buffer_.data() + buffer_.size() - read_size_;
|
||||
return ErrorToResultCode(err);
|
||||
}
|
||||
|
||||
return NotifyIfError("OnReadFile", ErrorToResultCode(err));
|
||||
}
|
||||
|
||||
ResultCode AgentWin::Connection::CallHandler() {
|
||||
ChromeToAgent message;
|
||||
if (!message.ParseFromArray(buffer_.data(), final_size_)) {
|
||||
// Malformed message.
|
||||
return NotifyIfError("ParseChromeToAgent",
|
||||
ResultCode::ERR_INVALID_REQUEST_FROM_BROWSER);
|
||||
}
|
||||
|
||||
auto rc = ResultCode::OK;
|
||||
|
||||
if (message.has_request()) {
|
||||
// This is a request from Google Chrome to perform a content analysis
|
||||
// request.
|
||||
//
|
||||
// Move the request from `message` to the event to reduce the amount
|
||||
// of memory allocation/copying and also because the the handler takes
|
||||
// ownership of the event.
|
||||
auto event = std::make_unique<ContentAnalysisEventWin>(
|
||||
handle_, browser_info_, std::move(*message.mutable_request()));
|
||||
rc = event->Init();
|
||||
if (rc == ResultCode::OK) {
|
||||
handler_->OnAnalysisRequested(std::move(event));
|
||||
} else {
|
||||
NotifyIfError("RequestValidation", rc);
|
||||
}
|
||||
} else if (message.has_ack()) {
|
||||
// This is an ack from Google Chrome that it has received a content
|
||||
// analysis response from the agent.
|
||||
handler_->OnResponseAcknowledged(message.ack());
|
||||
} else if (message.has_cancel()) {
|
||||
// Google Chrome is informing the agent that the content analysis
|
||||
// request(s) associated with the given user action id have been
|
||||
// canceled by the user.
|
||||
handler_->OnCancelRequests(message.cancel());
|
||||
} else {
|
||||
// Malformed message.
|
||||
rc = NotifyIfError("NoRequestOrAck",
|
||||
ResultCode::ERR_INVALID_REQUEST_FROM_BROWSER);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
ResultCode AgentWin::Connection::BuildBrowserInfo() {
|
||||
if (!GetNamedPipeClientProcessId(handle_, &browser_info_.pid)) {
|
||||
return NotifyIfError("BuildBrowserInfo",
|
||||
ResultCode::ERR_CANNOT_GET_BROWSER_PID);
|
||||
}
|
||||
|
||||
if (!internal::GetProcessPath(browser_info_.pid,
|
||||
&browser_info_.binary_path)) {
|
||||
return NotifyIfError("BuildBrowserInfo",
|
||||
ResultCode::ERR_CANNOT_GET_BROWSER_BINARY_PATH);
|
||||
}
|
||||
|
||||
return ResultCode::OK;
|
||||
}
|
||||
|
||||
ResultCode AgentWin::Connection::NotifyIfError(
|
||||
const char* context,
|
||||
ResultCode rc,
|
||||
DWORD err) {
|
||||
if (handler_ && rc != ResultCode::OK) {
|
||||
std::stringstream stm;
|
||||
stm << context << " pid=" << browser_info_.pid;
|
||||
if (err != ERROR_SUCCESS) {
|
||||
stm << context << " err=" << err;
|
||||
}
|
||||
|
||||
handler_->OnInternalError(stm.str().c_str(), rc);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
AgentWin::AgentWin(
|
||||
Config config,
|
||||
std::unique_ptr<AgentEventHandler> event_handler,
|
||||
ResultCode* rc)
|
||||
: AgentBase(std::move(config), std::move(event_handler)) {
|
||||
*rc = ResultCode::OK;
|
||||
if (handler() == nullptr) {
|
||||
*rc = ResultCode::ERR_AGENT_EVENT_HANDLER_NOT_SPECIFIED;
|
||||
return;
|
||||
}
|
||||
|
||||
stop_event_ = CreateEvent(/*securityAttr=*/nullptr,
|
||||
/*manualReset=*/TRUE,
|
||||
/*initialState=*/FALSE,
|
||||
/*name=*/nullptr);
|
||||
if (stop_event_ == nullptr) {
|
||||
*rc = ResultCode::ERR_CANNOT_CREATE_AGENT_STOP_EVENT;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string pipename =
|
||||
internal::GetPipeNameForAgent(configuration().name,
|
||||
configuration().user_specific);
|
||||
if (pipename.empty()) {
|
||||
*rc = ResultCode::ERR_INVALID_CHANNEL_NAME;
|
||||
return;
|
||||
}
|
||||
|
||||
pipename_ = pipename;
|
||||
|
||||
connections_.reserve(kMinNumListeningPipeInstances);
|
||||
for (DWORD i = 0; i < kMinNumListeningPipeInstances; ++i) {
|
||||
connections_.emplace_back(
|
||||
std::make_unique<Connection>(pipename_, configuration().user_specific,
|
||||
handler(), i == 0, rc));
|
||||
if (*rc != ResultCode::OK || !connections_.back()->IsValid()) {
|
||||
Shutdown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AgentWin::~AgentWin() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
ResultCode AgentWin::HandleEvents() {
|
||||
std::vector<HANDLE> wait_handles;
|
||||
auto rc = ResultCode::OK;
|
||||
bool stopped = false;
|
||||
while (!stopped && rc == ResultCode::OK) {
|
||||
rc = HandleOneEvent(wait_handles, &stopped);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
ResultCode AgentWin::Stop() {
|
||||
SetEvent(stop_event_);
|
||||
return AgentBase::Stop();
|
||||
}
|
||||
|
||||
std::string AgentWin::DebugString() const {
|
||||
std::stringstream state;
|
||||
state.setf(std::ios::boolalpha);
|
||||
state << "AgentWin{pipe=\"" << pipename_;
|
||||
state << "\" stop=" << stop_event_;
|
||||
|
||||
for (size_t i = 0; i < connections_.size(); ++i) {
|
||||
state << " conn@" << i;
|
||||
connections_[i]->AppendDebugString(state);
|
||||
}
|
||||
|
||||
state << "}" << std::ends;
|
||||
return state.str();
|
||||
}
|
||||
|
||||
void AgentWin::GetHandles(std::vector<HANDLE>& wait_handles) const {
|
||||
// Reserve enough space in the handles vector to include the stop event plus
|
||||
// all connections.
|
||||
wait_handles.clear();
|
||||
wait_handles.reserve(1 + connections_.size());
|
||||
|
||||
for (auto& state : connections_) {
|
||||
HANDLE wait_handle = state->GetWaitHandle();
|
||||
if (!wait_handle) {
|
||||
wait_handles.clear();
|
||||
break;
|
||||
}
|
||||
wait_handles.push_back(wait_handle);
|
||||
}
|
||||
|
||||
// Push the stop event last so that connections_ index calculations in
|
||||
// HandleOneEvent() don't have to account for this handle.
|
||||
wait_handles.push_back(stop_event_);
|
||||
}
|
||||
|
||||
ResultCode AgentWin::HandleOneEventForTesting() {
|
||||
std::vector<HANDLE> wait_handles;
|
||||
bool stopped;
|
||||
return HandleOneEvent(wait_handles, &stopped);
|
||||
}
|
||||
|
||||
bool AgentWin::IsAClientConnectedForTesting() {
|
||||
for (const auto& state : connections_) {
|
||||
if (state->IsConnected()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ResultCode AgentWin::HandleOneEvent(
|
||||
std::vector<HANDLE>& wait_handles,
|
||||
bool* stopped) {
|
||||
*stopped = false;
|
||||
|
||||
// Wait on the specified handles for an event to occur.
|
||||
GetHandles(wait_handles);
|
||||
if (wait_handles.size() < kMinNumWaitHandles) {
|
||||
return NotifyError("GetHandles", ResultCode::ERR_AGENT_NOT_INITIALIZED);
|
||||
}
|
||||
|
||||
DWORD index = WaitForMultipleObjects(
|
||||
wait_handles.size(), wait_handles.data(),
|
||||
/*waitAll=*/FALSE, /*timeoutMs=*/INFINITE);
|
||||
if (index == WAIT_FAILED) {
|
||||
return NotifyError("WaitForMultipleObjects",
|
||||
ErrorToResultCode(GetLastError()));
|
||||
}
|
||||
|
||||
// If the index of signaled handle is the last one in wait_handles, then the
|
||||
// stop event was signaled.
|
||||
index -= WAIT_OBJECT_0;
|
||||
if (index == wait_handles.size() - 1) {
|
||||
*stopped = true;
|
||||
return ResultCode::OK;
|
||||
}
|
||||
|
||||
auto& connection = connections_[index];
|
||||
bool was_listening = !connection->IsConnected();
|
||||
auto rc = connection->HandleEvent(wait_handles[index]);
|
||||
if (rc != ResultCode::OK) {
|
||||
// If `connection` was not listening and there are more than
|
||||
// kMinNumListeningPipeInstances pipes, delete this connection. Otherwise
|
||||
// reset it so that it becomes a listener.
|
||||
if (!was_listening &&
|
||||
connections_.size() > kMinNumListeningPipeInstances) {
|
||||
connections_.erase(connections_.begin() + index);
|
||||
} else {
|
||||
rc = connection->Reset(pipename_, configuration().user_specific);
|
||||
}
|
||||
}
|
||||
|
||||
// If `connection` was listening and is now connected, create a new
|
||||
// one so that there are always kMinNumListeningPipeInstances listening.
|
||||
if (rc == ResultCode::OK && was_listening && connection->IsConnected()) {
|
||||
connections_.emplace_back(
|
||||
std::make_unique<Connection>(pipename_, configuration().user_specific,
|
||||
handler(), false, &rc));
|
||||
}
|
||||
|
||||
return ResultCode::OK;
|
||||
}
|
||||
|
||||
void AgentWin::Shutdown() {
|
||||
connections_.clear();
|
||||
pipename_.clear();
|
||||
if (stop_event_ != nullptr) {
|
||||
CloseHandle(stop_event_);
|
||||
stop_event_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
196
third_party/content_analysis_sdk/agent/src/agent_win.h
vendored
Normal file
196
third_party/content_analysis_sdk/agent/src/agent_win.h
vendored
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_AGENT_SRC_AGENT_WIN_H_
|
||||
#define CONTENT_ANALYSIS_AGENT_SRC_AGENT_WIN_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "agent_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Agent implementaton for Windows.
|
||||
class AgentWin : public AgentBase {
|
||||
public:
|
||||
// Creates a new agent given the specific configuration and handler.
|
||||
// If an error occurs during creation, `rc` is set to the specific
|
||||
// error. Otherwise `rc` is ResultCode::OK.
|
||||
AgentWin(Config config, std::unique_ptr<AgentEventHandler> handler,
|
||||
ResultCode* rc);
|
||||
~AgentWin() override;
|
||||
|
||||
// Agent:
|
||||
ResultCode HandleEvents() override;
|
||||
ResultCode Stop() override;
|
||||
std::string DebugString() const override;
|
||||
|
||||
// Handles one pipe event and returns. Used only in tests.
|
||||
ResultCode HandleOneEventForTesting();
|
||||
|
||||
// Returns true if there is at least one client connected to this agent.
|
||||
bool IsAClientConnectedForTesting();
|
||||
|
||||
private:
|
||||
// Represents one connection to a Google Chrome browser, or one pipe
|
||||
// listening for a Google Chrome browser to connect.
|
||||
class Connection {
|
||||
public:
|
||||
// Starts listening on a pipe with the given name. `is_first_pipe` should
|
||||
// be true only for the first pipe created by the agent. If an error
|
||||
// occurs while creating the connction object it is returned in `rc`.
|
||||
// If no errors occur then rc is set to ResultCode::OK.
|
||||
//
|
||||
// When `user_specific` is true there is a different agent instance per OS
|
||||
// user.
|
||||
//
|
||||
// `Connection` objects cannot be copied or moved because the OVERLAPPED
|
||||
// structure cannot be changed or moved in memory while an I/O operation
|
||||
// is in progress.
|
||||
Connection(const std::string& pipename,
|
||||
bool user_specific,
|
||||
AgentEventHandler* handler,
|
||||
bool is_first_pipe,
|
||||
ResultCode* rc);
|
||||
Connection(const Connection& other) = delete;
|
||||
Connection(Connection&& other) = delete;
|
||||
Connection& operator=(const Connection& other) = delete;
|
||||
Connection& operator=(Connection&& other) = delete;
|
||||
~Connection();
|
||||
|
||||
bool IsValid() const { return handle_ != INVALID_HANDLE_VALUE; }
|
||||
bool IsConnected() const { return is_connected_; }
|
||||
HANDLE GetWaitHandle() const { return overlapped_.hEvent; }
|
||||
|
||||
// Resets this connection object to listen for a new Google Chrome browser.
|
||||
// When `user_specific` is true there is a different agent instance per OS
|
||||
// user.
|
||||
ResultCode Reset(const std::string& pipename, bool user_specific);
|
||||
|
||||
// Hnadles an event for this connection. `wait_handle` corresponds to
|
||||
// this connections wait handle.
|
||||
ResultCode HandleEvent(HANDLE wait_handle);
|
||||
|
||||
// Append debuf information to the string stream.
|
||||
void AppendDebugString(std::stringstream& state) const;
|
||||
|
||||
private:
|
||||
// Listens for a new connection from Google Chrome.
|
||||
ResultCode ConnectPipe();
|
||||
|
||||
// Resets this connection object to listen for a new Google Chrome browser.
|
||||
// When `user_specific` is true there is a different agent instance per OS
|
||||
// user.
|
||||
ResultCode ResetInternal(const std::string& pipename,
|
||||
bool user_specific,
|
||||
bool is_first_pipe);
|
||||
|
||||
// Cleans up this connection object so that it can be reused with a new
|
||||
// Google Chroem browser instance. The handles assocated with this object
|
||||
// are not closed. On return, this object is neither connected nor
|
||||
// listening and any buffer used to hold browser messages are cleared.
|
||||
void Cleanup();
|
||||
|
||||
// Queues a read on the pipe to receive a message from Google Chrome.
|
||||
// ERROR_SUCCESS, ERROR_IO_PENDING, and ERROR_MORE_DATA are successful
|
||||
// return values. Other values represent an error with the connection.
|
||||
// If `reset_cursor` is true the internal read buffer cursor is reset to
|
||||
// the start of the buffer, otherwise it is unchanged.
|
||||
ResultCode QueueReadFile(bool reset_cursor);
|
||||
|
||||
// Called when data from Google Chrome is available for reading from the
|
||||
// pipe. ERROR_SUCCESS and ERROR_MORE_DATA are both successful return
|
||||
// values. Other values represent an error with the connection.
|
||||
//
|
||||
// `done_reading` is true if the code has finished reading an entire message
|
||||
// from chrome. Regardless of whether reading is done, `count` contains
|
||||
// the number of bytes read.
|
||||
//
|
||||
// If `done_reading` is true, the data received from the browser is parsed
|
||||
// as if it were a `ChromeToAgent` proto message and the handler is called
|
||||
// as needed.
|
||||
//
|
||||
// If `done_reading` is false, the data received from the browser is
|
||||
// appended to the data already received from the browser. `buffer_` is
|
||||
// resized to allow reading more data from the browser.
|
||||
//
|
||||
// In all cases the caller is expected to use QueueReadFile() to continue
|
||||
// reading data from the browser.
|
||||
ResultCode OnReadFile(BOOL done_reading, DWORD count);
|
||||
|
||||
// Calls the appropriate method the handler depending on the message
|
||||
// received from Google Chrome.
|
||||
ResultCode CallHandler();
|
||||
|
||||
// Fills in the browser_info_ member of this Connection. Assumes
|
||||
// IsConnected() is true.
|
||||
ResultCode BuildBrowserInfo();
|
||||
|
||||
// Notifies the handler of the given error iff `rc` is not equal to
|
||||
// ResultCode::OK. Appends the Google Chrome browser process id to the
|
||||
// context before calling the handler. Also append `err` to the context
|
||||
// if it is not ERROR_SUCCESS.
|
||||
//
|
||||
// Returns the error passed into the method.
|
||||
ResultCode NotifyIfError(const char* context,
|
||||
ResultCode rc,
|
||||
DWORD err=ERROR_SUCCESS);
|
||||
|
||||
// The handler to call for various agent events.
|
||||
AgentEventHandler* handler_ = nullptr;
|
||||
|
||||
// Members used to communicate with Google Chrome.
|
||||
HANDLE handle_ = INVALID_HANDLE_VALUE;
|
||||
OVERLAPPED overlapped_;
|
||||
|
||||
// True if this connection is assigned to a specific Google Chrome browser,
|
||||
// otherwise this connection is listening for a new browser.
|
||||
bool is_connected_ = false;
|
||||
|
||||
// Information about the Google Chrome browser process.
|
||||
BrowserInfo browser_info_;
|
||||
|
||||
// Members used to read messages from Google Chrome.
|
||||
std::vector<char> buffer_;
|
||||
char* cursor_ = nullptr;
|
||||
DWORD read_size_ = 0;
|
||||
DWORD final_size_ = 0;
|
||||
};
|
||||
|
||||
// Returns handles that can be used to wait for events from all handles
|
||||
// managed by this agent. This includes all connection objects and the
|
||||
// stop event. The stop event is always last in the list.
|
||||
void GetHandles(std::vector<HANDLE>& wait_handles) const;
|
||||
|
||||
// Handles one pipe event and returns. If the return value is
|
||||
// ResultCode::OK, the `stopped` argument is set to true if the agent
|
||||
// should stop handling more events. If the return value is not
|
||||
// ResultCode::OK, `stopped` is undefined.
|
||||
ResultCode HandleOneEvent(std::vector<HANDLE>& wait_handles, bool* stopped);
|
||||
|
||||
// Performs a clean shutdown of the agent.
|
||||
void Shutdown();
|
||||
|
||||
// Name used to create the pipes between the agent and Google Chrome browsers.
|
||||
std::string pipename_;
|
||||
|
||||
// A list of pipes to already connected Google Chrome browsers.
|
||||
// The first kMinNumListeningPipeInstances pipes in the list correspond to
|
||||
// listening pipes.
|
||||
std::vector<std::unique_ptr<Connection>> connections_;
|
||||
|
||||
// An event that is set when the agent should stop. Set in Stop().
|
||||
HANDLE stop_event_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_AGENT_SRC_AGENT_WIN_H_
|
||||
522
third_party/content_analysis_sdk/agent/src/agent_win_unittest.cc
vendored
Normal file
522
third_party/content_analysis_sdk/agent/src/agent_win_unittest.cc
vendored
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <latch>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "agent/src/agent_win.h"
|
||||
#include "agent/src/event_win.h"
|
||||
#include "browser/src/client_win.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
namespace testing {
|
||||
|
||||
// A handler that counts the number of times the callback methods are invoked.
|
||||
// Also remembers the last BrowserInfo structure passed to it from any of the
|
||||
// callbacks.
|
||||
struct TestHandler : public AgentEventHandler {
|
||||
void OnBrowserConnected(const BrowserInfo& info) override {
|
||||
last_info_ = info;
|
||||
++connect_count_;
|
||||
}
|
||||
void OnBrowserDisconnected(const BrowserInfo& info) override {
|
||||
last_info_ = info;
|
||||
++disconnect_count_;
|
||||
}
|
||||
void OnAnalysisRequested(
|
||||
std::unique_ptr<ContentAnalysisEvent> event) override {
|
||||
++request_count_;
|
||||
ResultCode ret = event->Send();
|
||||
ASSERT_EQ(ResultCode::OK, ret);
|
||||
}
|
||||
void OnResponseAcknowledged(
|
||||
const ContentAnalysisAcknowledgement& ack) override {
|
||||
++ack_count_;
|
||||
}
|
||||
void OnCancelRequests(
|
||||
const ContentAnalysisCancelRequests& cancel) override {
|
||||
++cancel_count_;
|
||||
}
|
||||
|
||||
int connect_count_ = 0;
|
||||
int disconnect_count_ = 0;
|
||||
int request_count_ = 0;
|
||||
int ack_count_ = 0;
|
||||
int cancel_count_ = 0;
|
||||
BrowserInfo last_info_;
|
||||
};
|
||||
|
||||
// A test handler that closes its event before sending the response.
|
||||
struct CloseEventTestHandler : public TestHandler {
|
||||
void OnAnalysisRequested(
|
||||
std::unique_ptr<ContentAnalysisEvent> event) override {
|
||||
++request_count_;
|
||||
|
||||
// Closing the event before sending should generate an error.
|
||||
ResultCode ret = event->Close();
|
||||
ASSERT_EQ(ResultCode::OK, ret);
|
||||
|
||||
ret = event->Send();
|
||||
ASSERT_NE(ResultCode::OK, ret);
|
||||
}
|
||||
};
|
||||
|
||||
// A test handler that attempts to send two responses for a given request.
|
||||
struct DoubleSendTestHandler : public TestHandler {
|
||||
void OnAnalysisRequested(
|
||||
std::unique_ptr<ContentAnalysisEvent> event) override {
|
||||
++request_count_;
|
||||
|
||||
ResultCode ret = event->Send();
|
||||
ASSERT_EQ(ResultCode::OK, ret);
|
||||
|
||||
// Trying to send again fails.
|
||||
ret = event->Send();
|
||||
ASSERT_NE(ResultCode::OK, ret);
|
||||
}
|
||||
};
|
||||
|
||||
// A test handler that signals a latch after a client connects.
|
||||
// Can only be used with one client.
|
||||
struct SignalClientConnectedTestHandler : public TestHandler {
|
||||
void OnBrowserConnected(const BrowserInfo& info) override {
|
||||
TestHandler::OnBrowserConnected(info);
|
||||
wait_for_client.count_down();
|
||||
}
|
||||
|
||||
std::latch wait_for_client{ 1 };
|
||||
};
|
||||
|
||||
// A test handler that signals a latch after a request is processed.
|
||||
// Can only be used with one request.
|
||||
struct SignalClientRequestedTestHandler : public TestHandler {
|
||||
void OnAnalysisRequested(
|
||||
std::unique_ptr<ContentAnalysisEvent> event) override {
|
||||
TestHandler::OnAnalysisRequested(std::move(event));
|
||||
wait_for_request.count_down();
|
||||
}
|
||||
|
||||
std::latch wait_for_request{ 1 };
|
||||
};
|
||||
|
||||
std::unique_ptr<AgentWin> CreateAgent(
|
||||
Agent::Config config,
|
||||
TestHandler** handler_ptr,
|
||||
ResultCode expected_rc=ResultCode::OK) {
|
||||
ResultCode rc;
|
||||
auto handler = std::make_unique<TestHandler>();
|
||||
*handler_ptr = handler.get();
|
||||
auto agent = std::make_unique<AgentWin>(
|
||||
std::move(config), std::move(handler), &rc);
|
||||
EXPECT_EQ(expected_rc, rc);
|
||||
return agent;
|
||||
}
|
||||
|
||||
std::unique_ptr<ClientWin> CreateClient(
|
||||
Client::Config config) {
|
||||
int rc;
|
||||
auto client = std::make_unique<ClientWin>(std::move(config), &rc);
|
||||
return rc == 0 ? std::move(client) : nullptr;
|
||||
}
|
||||
|
||||
ContentAnalysisRequest BuildRequest(std::string content=std::string()) {
|
||||
ContentAnalysisRequest request;
|
||||
request.set_request_token("req-token");
|
||||
*request.add_tags() = "dlp";
|
||||
request.set_text_content(content); // Moved.
|
||||
return request;
|
||||
}
|
||||
|
||||
TEST(AgentTest, Create) {
|
||||
const Agent::Config config{"test", false};
|
||||
TestHandler* handler_ptr;
|
||||
auto agent = CreateAgent(config, &handler_ptr);
|
||||
ASSERT_TRUE(agent);
|
||||
ASSERT_TRUE(handler_ptr);
|
||||
|
||||
ASSERT_EQ(config.name, agent->GetConfig().name);
|
||||
ASSERT_EQ(config.user_specific, agent->GetConfig().user_specific);
|
||||
}
|
||||
|
||||
TEST(AgentTest, Create_InvalidPipename) {
|
||||
// TODO(rogerta): The win32 docs say that a backslash is an invalid
|
||||
// character for a pipename, but it seemed to work correctly in testing.
|
||||
// Using an empty name was the easiest way to generate an invalid pipe
|
||||
// name.
|
||||
const Agent::Config config{"", false};
|
||||
TestHandler* handler_ptr;
|
||||
auto agent = CreateAgent(config, &handler_ptr,
|
||||
ResultCode::ERR_INVALID_CHANNEL_NAME);
|
||||
ASSERT_TRUE(agent);
|
||||
|
||||
ASSERT_EQ(ResultCode::ERR_AGENT_NOT_INITIALIZED,
|
||||
agent->HandleOneEventForTesting());
|
||||
}
|
||||
|
||||
// Can't create two agents with the same name.
|
||||
TEST(AgentTest, Create_SecondFails) {
|
||||
const Agent::Config config{ "test", false };
|
||||
TestHandler* handler_ptr1;
|
||||
auto agent1 = CreateAgent(config, &handler_ptr1);
|
||||
ASSERT_TRUE(agent1);
|
||||
|
||||
TestHandler* handler_ptr2;
|
||||
auto agent2 = CreateAgent(config, &handler_ptr2,
|
||||
ResultCode::ERR_AGENT_ALREADY_EXISTS);
|
||||
ASSERT_TRUE(agent2);
|
||||
|
||||
ASSERT_EQ(ResultCode::ERR_AGENT_NOT_INITIALIZED,
|
||||
agent2->HandleOneEventForTesting());
|
||||
}
|
||||
|
||||
TEST(AgentTest, Stop) {
|
||||
TestHandler* handler_ptr;
|
||||
auto agent = CreateAgent({ "test", false }, &handler_ptr);
|
||||
ASSERT_TRUE(agent);
|
||||
|
||||
// Create a separate thread to stop the agent.
|
||||
std::thread thread([&agent]() {
|
||||
agent->Stop();
|
||||
});
|
||||
|
||||
agent->HandleEvents();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
TEST(AgentTest, ConnectAndStop) {
|
||||
ResultCode rc;
|
||||
auto handler = std::make_unique<SignalClientConnectedTestHandler>();
|
||||
auto* handler_ptr = handler.get();
|
||||
auto agent = std::make_unique<AgentWin>(
|
||||
Agent::Config{"test", false}, std::move(handler), &rc);
|
||||
ASSERT_TRUE(agent);
|
||||
ASSERT_EQ(ResultCode::OK, rc);
|
||||
|
||||
// Client thread waits until latch reaches zero.
|
||||
std::latch stop_client{ 1 };
|
||||
|
||||
// Create a thread to handle the client. Since the client is sync, it can't
|
||||
// run in the same thread as the agent.
|
||||
std::thread client_thread([&stop_client]() {
|
||||
auto client = CreateClient({ "test", false });
|
||||
ASSERT_TRUE(client);
|
||||
stop_client.wait();
|
||||
});
|
||||
|
||||
// A thread that stops the agent after one client connects.
|
||||
std::thread stop_agent([&handler_ptr, &agent]() {
|
||||
handler_ptr->wait_for_client.wait();
|
||||
agent->Stop();
|
||||
});
|
||||
|
||||
agent->HandleEvents();
|
||||
|
||||
stop_client.count_down();
|
||||
client_thread.join();
|
||||
stop_agent.join();
|
||||
}
|
||||
|
||||
TEST(AgentTest, Connect_UserSpecific) {
|
||||
ResultCode rc;
|
||||
auto handler = std::make_unique<SignalClientConnectedTestHandler>();
|
||||
auto* handler_ptr = handler.get();
|
||||
auto agent = std::make_unique<AgentWin>(
|
||||
Agent::Config{ "test", true }, std::move(handler), &rc);
|
||||
ASSERT_TRUE(agent);
|
||||
ASSERT_EQ(ResultCode::OK, rc);
|
||||
|
||||
// Create a thread to handle the client. Since the client is sync, it can't
|
||||
// run in the same thread as the agent.
|
||||
std::thread client_thread([]() {
|
||||
// If the user_specific does not match the agent, the client should not
|
||||
// connect.
|
||||
auto client = CreateClient({ "test", false });
|
||||
ASSERT_FALSE(client);
|
||||
|
||||
auto client2 = CreateClient({ "test", true });
|
||||
ASSERT_TRUE(client2);
|
||||
});
|
||||
|
||||
// A thread that stops the agent after one client connects.
|
||||
std::thread stop_agent([&handler_ptr, &agent]() {
|
||||
handler_ptr->wait_for_client.wait();
|
||||
agent->Stop();
|
||||
});
|
||||
|
||||
agent->HandleEvents();
|
||||
|
||||
client_thread.join();
|
||||
stop_agent.join();
|
||||
}
|
||||
|
||||
TEST(AgentTest, ConnectRequestAndStop) {
|
||||
ResultCode rc;
|
||||
auto handler = std::make_unique<SignalClientRequestedTestHandler>();
|
||||
auto* handler_ptr = handler.get();
|
||||
auto agent = std::make_unique<AgentWin>(
|
||||
Agent::Config{"test", false}, std::move(handler), &rc);
|
||||
ASSERT_TRUE(agent);
|
||||
ASSERT_EQ(ResultCode::OK, rc);
|
||||
|
||||
// Create a thread to handle the client. Since the client is sync, it can't
|
||||
// run in the same thread as the agent.
|
||||
std::thread client_thread([]() {
|
||||
auto client = CreateClient({ "test", false });
|
||||
ASSERT_TRUE(client);
|
||||
|
||||
ContentAnalysisRequest request = BuildRequest("test");
|
||||
ContentAnalysisResponse response;
|
||||
client->Send(request, &response);
|
||||
});
|
||||
|
||||
// A thread that stops the agent after one client connects.
|
||||
std::thread stop_agent([&handler_ptr, &agent]() {
|
||||
handler_ptr->wait_for_request.wait();
|
||||
agent->Stop();
|
||||
});
|
||||
|
||||
agent->HandleEvents();
|
||||
|
||||
client_thread.join();
|
||||
stop_agent.join();
|
||||
}
|
||||
|
||||
TEST(AgentTest, ConnectAndClose) {
|
||||
const Agent::Config aconfig{ "test", false };
|
||||
const Client::Config cconfig{ "test", false };
|
||||
|
||||
// Create an agent and client that connects to it.
|
||||
TestHandler* handler_ptr;
|
||||
auto agent = CreateAgent(aconfig, &handler_ptr);
|
||||
ASSERT_TRUE(agent);
|
||||
auto client = CreateClient(cconfig);
|
||||
ASSERT_TRUE(client);
|
||||
ASSERT_EQ(cconfig.name, client->GetConfig().name);
|
||||
ASSERT_EQ(cconfig.user_specific, client->GetConfig().user_specific);
|
||||
|
||||
agent->HandleOneEventForTesting();
|
||||
ASSERT_EQ(1, handler_ptr->connect_count_);
|
||||
ASSERT_EQ(0, handler_ptr->disconnect_count_);
|
||||
ASSERT_EQ(0, handler_ptr->cancel_count_);
|
||||
ASSERT_EQ(GetCurrentProcessId(), handler_ptr->last_info_.pid);
|
||||
|
||||
// Close the client and make sure a disconnect is received.
|
||||
client.reset();
|
||||
agent->HandleOneEventForTesting();
|
||||
ASSERT_EQ(1, handler_ptr->connect_count_);
|
||||
ASSERT_EQ(1, handler_ptr->disconnect_count_);
|
||||
ASSERT_EQ(0, handler_ptr->cancel_count_);
|
||||
ASSERT_EQ(GetCurrentProcessId(), handler_ptr->last_info_.pid);
|
||||
}
|
||||
|
||||
TEST(AgentTest, Request) {
|
||||
TestHandler* handler_ptr;
|
||||
auto agent = CreateAgent({"test", false}, &handler_ptr);
|
||||
ASSERT_TRUE(agent);
|
||||
|
||||
bool done = false;
|
||||
|
||||
// Create a thread to handle the client. Since the client is sync, it can't
|
||||
// run in the same thread as the agent.
|
||||
std::thread client_thread([&done]() {
|
||||
auto client = CreateClient({"test", false});
|
||||
ASSERT_TRUE(client);
|
||||
|
||||
// Send a request and wait for a response.
|
||||
ContentAnalysisRequest request = BuildRequest();
|
||||
ContentAnalysisResponse response;
|
||||
int ret = client->Send(request, &response);
|
||||
ASSERT_EQ(0, ret);
|
||||
ASSERT_EQ(request.request_token(), response.request_token());
|
||||
|
||||
done = true;
|
||||
});
|
||||
|
||||
while (!done) {
|
||||
agent->HandleOneEventForTesting();
|
||||
}
|
||||
ASSERT_EQ(1, handler_ptr->request_count_);
|
||||
ASSERT_EQ(0, handler_ptr->ack_count_);
|
||||
ASSERT_EQ(0, handler_ptr->cancel_count_);
|
||||
|
||||
client_thread.join();
|
||||
}
|
||||
|
||||
TEST(AgentTest, Request_Large) {
|
||||
TestHandler* handler_ptr;
|
||||
auto agent = CreateAgent({"test", false}, &handler_ptr);
|
||||
ASSERT_TRUE(agent);
|
||||
|
||||
bool done = false;
|
||||
|
||||
// Create a thread to handle the client. Since the client is sync, it can't
|
||||
// run in the same thread as the agent.
|
||||
std::thread client_thread([&done]() {
|
||||
auto client = CreateClient({"test", false});
|
||||
ASSERT_TRUE(client);
|
||||
|
||||
// Send a request and wait for a response. Create a large string, which
|
||||
// means larger than the initial mesasge buffer size specified when
|
||||
// creating the pipes (4096 bytes).
|
||||
ContentAnalysisRequest request = BuildRequest(std::string(5000, 'a'));
|
||||
ContentAnalysisResponse response;
|
||||
int ret = client->Send(request, &response);
|
||||
ASSERT_EQ(0, ret);
|
||||
ASSERT_EQ(request.request_token(), response.request_token());
|
||||
|
||||
done = true;
|
||||
});
|
||||
|
||||
while (!done) {
|
||||
agent->HandleOneEventForTesting();
|
||||
}
|
||||
ASSERT_EQ(1, handler_ptr->request_count_);
|
||||
ASSERT_EQ(0, handler_ptr->ack_count_);
|
||||
ASSERT_EQ(0, handler_ptr->cancel_count_);
|
||||
|
||||
client_thread.join();
|
||||
}
|
||||
|
||||
TEST(AgentTest, Request_DoubleSend) {
|
||||
ResultCode rc;
|
||||
auto handler = std::make_unique<DoubleSendTestHandler>();
|
||||
DoubleSendTestHandler* handler_ptr = handler.get();
|
||||
auto agent = std::make_unique<AgentWin>(
|
||||
Agent::Config{"test", false}, std::move(handler), &rc);
|
||||
ASSERT_TRUE(agent);
|
||||
ASSERT_EQ(ResultCode::OK, rc);
|
||||
|
||||
bool done = false;
|
||||
|
||||
// Create a thread to handle the client. Since the client is sync, it can't
|
||||
// run in the same thread as the agent.
|
||||
std::thread client_thread([&done]() {
|
||||
auto client = CreateClient({ "test", false });
|
||||
ASSERT_TRUE(client);
|
||||
|
||||
// Send a request and wait for a response.
|
||||
ContentAnalysisRequest request = BuildRequest();
|
||||
ContentAnalysisResponse response;
|
||||
int ret = client->Send(request, &response);
|
||||
ASSERT_EQ(0, ret);
|
||||
ASSERT_EQ(request.request_token(), response.request_token());
|
||||
|
||||
done = true;
|
||||
});
|
||||
|
||||
while (!done) {
|
||||
agent->HandleOneEventForTesting();
|
||||
}
|
||||
ASSERT_EQ(1, handler_ptr->request_count_);
|
||||
ASSERT_EQ(0, handler_ptr->ack_count_);
|
||||
ASSERT_EQ(0, handler_ptr->cancel_count_);
|
||||
|
||||
client_thread.join();
|
||||
}
|
||||
|
||||
TEST(AgentTest, Request_CloseEvent) {
|
||||
ResultCode rc;
|
||||
auto handler = std::make_unique<CloseEventTestHandler>();
|
||||
CloseEventTestHandler* handler_ptr = handler.get();
|
||||
auto agent = std::make_unique<AgentWin>(
|
||||
Agent::Config{"test", false}, std::move(handler), &rc);
|
||||
ASSERT_TRUE(agent);
|
||||
ASSERT_EQ(ResultCode::OK, rc);
|
||||
|
||||
bool done = false;
|
||||
|
||||
// Create a thread to handle the client. Since the client is sync, it can't
|
||||
// run in the same thread as the agent.
|
||||
std::thread client_thread([&done]() {
|
||||
auto client = CreateClient({"test", false});
|
||||
ASSERT_TRUE(client);
|
||||
|
||||
// Send a request and wait for a response.
|
||||
ContentAnalysisRequest request = BuildRequest();
|
||||
ContentAnalysisResponse response;
|
||||
int ret = client->Send(request, &response);
|
||||
ASSERT_EQ(0, ret);
|
||||
ASSERT_EQ(request.request_token(), response.request_token());
|
||||
|
||||
done = true;
|
||||
});
|
||||
|
||||
while (!done) {
|
||||
agent->HandleOneEventForTesting();
|
||||
}
|
||||
ASSERT_EQ(1, handler_ptr->request_count_);
|
||||
|
||||
client_thread.join();
|
||||
}
|
||||
|
||||
TEST(AgentTest, Ack) {
|
||||
TestHandler* handler_ptr;
|
||||
auto agent = CreateAgent({ "test", false }, &handler_ptr);
|
||||
ASSERT_TRUE(agent);
|
||||
|
||||
bool done = false;
|
||||
|
||||
// Create a thread to handle the client. Since the client is sync, it can't
|
||||
// run in the same thread as the agent.
|
||||
std::thread client_thread([&done]() {
|
||||
auto client = CreateClient({"test", false});
|
||||
ASSERT_TRUE(client);
|
||||
|
||||
// Send a request and wait for a response.
|
||||
ContentAnalysisRequest request = BuildRequest();
|
||||
ContentAnalysisResponse response;
|
||||
int ret = client->Send(request, &response);
|
||||
ASSERT_EQ(0, ret);
|
||||
|
||||
ContentAnalysisAcknowledgement ack;
|
||||
ack.set_request_token(request.request_token());
|
||||
ret = client->Acknowledge(ack);
|
||||
ASSERT_EQ(0, ret);
|
||||
|
||||
done = true;
|
||||
});
|
||||
|
||||
while (!done) {
|
||||
agent->HandleOneEventForTesting();
|
||||
}
|
||||
ASSERT_EQ(1, handler_ptr->request_count_);
|
||||
ASSERT_EQ(1, handler_ptr->ack_count_);
|
||||
ASSERT_EQ(0, handler_ptr->cancel_count_);
|
||||
|
||||
client_thread.join();
|
||||
}
|
||||
|
||||
TEST(AgentTest, Cancel) {
|
||||
TestHandler* handler_ptr;
|
||||
auto agent = CreateAgent({ "test", false }, &handler_ptr);
|
||||
ASSERT_TRUE(agent);
|
||||
|
||||
// Create a thread to handle the client. Since the client is sync, it can't
|
||||
// run in the same thread as the agent.
|
||||
std::thread client_thread([]() {
|
||||
auto client = CreateClient({"test", false});
|
||||
ASSERT_TRUE(client);
|
||||
|
||||
ContentAnalysisCancelRequests cancel;
|
||||
cancel.set_user_action_id("1234567890");
|
||||
int ret = client->CancelRequests(cancel);
|
||||
ASSERT_EQ(0, ret);
|
||||
});
|
||||
|
||||
while (handler_ptr->cancel_count_ == 0) {
|
||||
agent->HandleOneEventForTesting();
|
||||
}
|
||||
ASSERT_EQ(0, handler_ptr->request_count_);
|
||||
ASSERT_EQ(0, handler_ptr->ack_count_);
|
||||
|
||||
client_thread.join();
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
65
third_party/content_analysis_sdk/agent/src/event_base.cc
vendored
Normal file
65
third_party/content_analysis_sdk/agent/src/event_base.cc
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "event_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
ContentAnalysisEventBase::ContentAnalysisEventBase(
|
||||
const BrowserInfo& browser_info)
|
||||
: browser_info_(browser_info) {}
|
||||
|
||||
ResultCode ContentAnalysisEventBase::Close() {
|
||||
return ResultCode::OK;
|
||||
}
|
||||
|
||||
ResultCode UpdateResponse(ContentAnalysisResponse& response,
|
||||
const std::string& tag,
|
||||
ContentAnalysisResponse::Result::Status status) {
|
||||
auto result = response.results_size() > 0
|
||||
? response.mutable_results(0)
|
||||
: response.add_results();
|
||||
|
||||
if (!tag.empty()) {
|
||||
result->set_tag(tag);
|
||||
}
|
||||
|
||||
if (status != ContentAnalysisResponse::Result::STATUS_UNKNOWN) {
|
||||
result->set_status(status);
|
||||
}
|
||||
|
||||
return ResultCode::OK;
|
||||
}
|
||||
|
||||
ResultCode SetEventVerdictTo(
|
||||
ContentAnalysisEvent* event,
|
||||
ContentAnalysisResponse::Result::TriggeredRule::Action action) {
|
||||
// This function expects that the event's result has already been
|
||||
// initialized by a call to UpdateResponse().
|
||||
|
||||
if (event->GetResponse().results_size() == 0) {
|
||||
return ResultCode::ERR_MISSING_RESULT;
|
||||
}
|
||||
|
||||
auto result = event->GetResponse().mutable_results(0);
|
||||
|
||||
// Content analysis responses generated with this SDK contain at most one
|
||||
// triggered rule.
|
||||
auto rule = result->triggered_rules_size() > 0
|
||||
? result->mutable_triggered_rules(0)
|
||||
: result->add_triggered_rules();
|
||||
|
||||
rule->set_action(action);
|
||||
|
||||
return ResultCode::OK;
|
||||
}
|
||||
|
||||
ResultCode SetEventVerdictToBlock(ContentAnalysisEvent* event) {
|
||||
return SetEventVerdictTo(event,
|
||||
ContentAnalysisResponse::Result::TriggeredRule::BLOCK);
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
38
third_party/content_analysis_sdk/agent/src/event_base.h
vendored
Normal file
38
third_party/content_analysis_sdk/agent/src/event_base.h
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_AGENT_SRC_EVENT_BASE_H_
|
||||
#define CONTENT_ANALYSIS_AGENT_SRC_EVENT_BASE_H_
|
||||
|
||||
#include "content_analysis/sdk/analysis_agent.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Base ContentAnalysisEvent class with code common to all platforms.
|
||||
class ContentAnalysisEventBase : public ContentAnalysisEvent {
|
||||
public:
|
||||
// ContentAnalysisEvent:
|
||||
ResultCode Close() override;
|
||||
const BrowserInfo& GetBrowserInfo() const override { return browser_info_; }
|
||||
const ContentAnalysisRequest& GetRequest() const override { return request_; }
|
||||
ContentAnalysisResponse& GetResponse() override { return *response(); }
|
||||
|
||||
protected:
|
||||
explicit ContentAnalysisEventBase(const BrowserInfo& browser_info);
|
||||
|
||||
ContentAnalysisRequest* request() { return &request_; }
|
||||
AgentToChrome* agent_to_chrome() { return &agent_to_chrome_; }
|
||||
ContentAnalysisResponse* response() { return agent_to_chrome()->mutable_response(); }
|
||||
|
||||
private:
|
||||
BrowserInfo browser_info_;
|
||||
ContentAnalysisRequest request_;
|
||||
AgentToChrome agent_to_chrome_;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_AGENT_SRC_EVENT_BASE_H_
|
||||
29
third_party/content_analysis_sdk/agent/src/event_mac.cc
vendored
Normal file
29
third_party/content_analysis_sdk/agent/src/event_mac.cc
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "event_mac.h"
|
||||
|
||||
#include "scoped_print_handle_mac.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
ContentAnalysisEventMac::ContentAnalysisEventMac(
|
||||
const BrowserInfo& browser_info,
|
||||
ContentAnalysisRequest req)
|
||||
: ContentAnalysisEventBase(browser_info) {
|
||||
*request() = std::move(req);
|
||||
}
|
||||
|
||||
ResultCode ContentAnalysisEventMac::Send() {
|
||||
return ResultCode::ERR_UNEXPECTED;
|
||||
}
|
||||
|
||||
std::string ContentAnalysisEventMac::DebugString() const {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
29
third_party/content_analysis_sdk/agent/src/event_mac.h
vendored
Normal file
29
third_party/content_analysis_sdk/agent/src/event_mac.h
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_SRC_EVENT_MAC_H_
|
||||
#define CONTENT_ANALYSIS_SRC_EVENT_MAC_H_
|
||||
|
||||
#include "event_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// ContentAnalysisEvent implementaton for macOS.
|
||||
class ContentAnalysisEventMac : public ContentAnalysisEventBase {
|
||||
public:
|
||||
ContentAnalysisEventMac(const BrowserInfo& browser_info,
|
||||
ContentAnalysisRequest request);
|
||||
|
||||
// ContentAnalysisEvent:
|
||||
ResultCode Send() override;
|
||||
std::string DebugString() const override;
|
||||
|
||||
// TODO(rogerta): Fill in implementation.
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_SRC_EVENT_MAC_H_
|
||||
53
third_party/content_analysis_sdk/agent/src/event_mac_unittest.cc
vendored
Normal file
53
third_party/content_analysis_sdk/agent/src/event_mac_unittest.cc
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "agent/src/event_mac.h"
|
||||
#include "content_analysis/sdk/analysis_agent.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
namespace testing {
|
||||
|
||||
std::unique_ptr<ContentAnalysisEventMac> CreateEvent(
|
||||
const BrowserInfo& browser_info,
|
||||
ContentAnalysisRequest request) {
|
||||
return std::make_unique<ContentAnalysisEventMac>(
|
||||
browser_info, std::move(request));
|
||||
}
|
||||
|
||||
TEST(EventTest, Create_BrowserInfo) {
|
||||
const BrowserInfo bi{12345, "/path/to/binary"};
|
||||
ContentAnalysisRequest request;
|
||||
*request.add_tags() = "foo";
|
||||
request.set_request_token("req-token");
|
||||
|
||||
auto event = CreateEvent(bi, request);
|
||||
ASSERT_TRUE(event);
|
||||
|
||||
ASSERT_EQ(bi.pid, event->GetBrowserInfo().pid);
|
||||
ASSERT_EQ(bi.binary_path, event->GetBrowserInfo().binary_path);
|
||||
}
|
||||
|
||||
TEST(EventTest, Create_Request) {
|
||||
const BrowserInfo bi{ 12345, "/path/to/binary" };
|
||||
ContentAnalysisRequest request;
|
||||
*request.add_tags() = "foo";
|
||||
request.set_request_token("req-token");
|
||||
|
||||
auto event = CreateEvent(bi, request);
|
||||
ASSERT_TRUE(event);
|
||||
|
||||
ASSERT_EQ(1u, event->GetRequest().tags_size());
|
||||
ASSERT_EQ(request.tags(0), event->GetRequest().tags(0));
|
||||
ASSERT_TRUE(event->GetRequest().has_request_token());
|
||||
ASSERT_EQ(request.request_token(), event->GetRequest().request_token());
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
28
third_party/content_analysis_sdk/agent/src/event_posix.cc
vendored
Normal file
28
third_party/content_analysis_sdk/agent/src/event_posix.cc
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "event_posix.h"
|
||||
|
||||
#include "scoped_print_handle_posix.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
ContentAnalysisEventPosix::ContentAnalysisEventPosix(
|
||||
const BrowserInfo& browser_info,
|
||||
ContentAnalysisRequest req)
|
||||
: ContentAnalysisEventBase(browser_info) {
|
||||
*request() = std::move(req);
|
||||
}
|
||||
|
||||
ResultCode ContentAnalysisEventPosix::Send() {
|
||||
return ResultCode::ERR_UNEXPECTED;
|
||||
}
|
||||
|
||||
std::string ContentAnalysisEventPosix::DebugString() const {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
29
third_party/content_analysis_sdk/agent/src/event_posix.h
vendored
Normal file
29
third_party/content_analysis_sdk/agent/src/event_posix.h
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_SRC_EVENT_POSIX_H_
|
||||
#define CONTENT_ANALYSIS_SRC_EVENT_POSIX_H_
|
||||
|
||||
#include "event_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// ContentAnalysisEvent implementaton for linux.
|
||||
class ContentAnalysisEventPosix : public ContentAnalysisEventBase {
|
||||
public:
|
||||
ContentAnalysisEventPosix(const BrowserInfo& browser_info,
|
||||
ContentAnalysisRequest request);
|
||||
|
||||
// ContentAnalysisEvent:
|
||||
ResultCode Send() override;
|
||||
std::string DebugString() const override;
|
||||
|
||||
// TODO(rogerta): Fill in implementation.
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_SRC_EVENT_POSIX_H_
|
||||
53
third_party/content_analysis_sdk/agent/src/event_posix_unittest.cc
vendored
Normal file
53
third_party/content_analysis_sdk/agent/src/event_posix_unittest.cc
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "agent/src/event_posix.h"
|
||||
#include "content_analysis/sdk/analysis_agent.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
namespace testing {
|
||||
|
||||
std::unique_ptr<ContentAnalysisEventPosix> CreateEvent(
|
||||
const BrowserInfo& browser_info,
|
||||
ContentAnalysisRequest request) {
|
||||
return std::make_unique<ContentAnalysisEventPosix>(
|
||||
browser_info, std::move(request));
|
||||
}
|
||||
|
||||
TEST(EventTest, Create_BrowserInfo) {
|
||||
const BrowserInfo bi{12345, "/path/to/binary"};
|
||||
ContentAnalysisRequest request;
|
||||
*request.add_tags() = "foo";
|
||||
request.set_request_token("req-token");
|
||||
|
||||
auto event = CreateEvent(bi, request);
|
||||
ASSERT_TRUE(event);
|
||||
|
||||
ASSERT_EQ(bi.pid, event->GetBrowserInfo().pid);
|
||||
ASSERT_EQ(bi.binary_path, event->GetBrowserInfo().binary_path);
|
||||
}
|
||||
|
||||
TEST(EventTest, Create_Request) {
|
||||
const BrowserInfo bi{ 12345, "/path/to/binary" };
|
||||
ContentAnalysisRequest request;
|
||||
*request.add_tags() = "foo";
|
||||
request.set_request_token("req-token");
|
||||
|
||||
auto event = CreateEvent(bi, request);
|
||||
ASSERT_TRUE(event);
|
||||
|
||||
ASSERT_EQ(1u, event->GetRequest().tags_size());
|
||||
ASSERT_EQ(request.tags(0), event->GetRequest().tags(0));
|
||||
ASSERT_TRUE(event->GetRequest().has_request_token());
|
||||
ASSERT_EQ(request.request_token(), event->GetRequest().request_token());
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
133
third_party/content_analysis_sdk/agent/src/event_win.cc
vendored
Normal file
133
third_party/content_analysis_sdk/agent/src/event_win.cc
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <ios>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#include "event_win.h"
|
||||
|
||||
#include "common/utils_win.h"
|
||||
|
||||
#include "agent_utils_win.h"
|
||||
#include "scoped_print_handle_win.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Writes a string to the pipe. Returns ERROR_SUCCESS if successful, else
|
||||
// returns GetLastError() of the write. This function does not return until
|
||||
// the entire message has been sent (or an error occurs).
|
||||
static DWORD WriteMessageToPipe(HANDLE pipe, const std::string& message) {
|
||||
if (message.empty()) {
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
internal::ScopedOverlapped overlapped;
|
||||
if (!overlapped.is_valid()) {
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
DWORD err = ERROR_SUCCESS;
|
||||
const char* cursor = message.data();
|
||||
for (DWORD size = message.length(); size > 0;) {
|
||||
if (WriteFile(pipe, cursor, size, /*written=*/nullptr, overlapped)) {
|
||||
err = ERROR_SUCCESS;
|
||||
break;
|
||||
}
|
||||
|
||||
// If an I/O is not pending, return the error.
|
||||
err = GetLastError();
|
||||
if (err != ERROR_IO_PENDING) {
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD written;
|
||||
if (!GetOverlappedResult(pipe, overlapped, &written, /*wait=*/TRUE)) {
|
||||
err = GetLastError();
|
||||
break;
|
||||
}
|
||||
|
||||
cursor += written;
|
||||
size -= written;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
ContentAnalysisEventWin::ContentAnalysisEventWin(
|
||||
HANDLE handle,
|
||||
const BrowserInfo& browser_info,
|
||||
ContentAnalysisRequest req)
|
||||
: ContentAnalysisEventBase(browser_info),
|
||||
hPipe_(handle) {
|
||||
*request() = std::move(req);
|
||||
}
|
||||
|
||||
ContentAnalysisEventWin::~ContentAnalysisEventWin() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
ResultCode ContentAnalysisEventWin::Init() {
|
||||
// TODO(rogerta): do some extra validation of the request?
|
||||
if (request()->request_token().empty()) {
|
||||
return ResultCode::ERR_MISSING_REQUEST_TOKEN;
|
||||
}
|
||||
|
||||
response()->set_request_token(request()->request_token());
|
||||
|
||||
// Prepare the response so that ALLOW verdicts are the default().
|
||||
return UpdateResponse(*response(),
|
||||
request()->tags_size() > 0 ? request()->tags(0) : std::string(),
|
||||
ContentAnalysisResponse::Result::SUCCESS);
|
||||
}
|
||||
|
||||
ResultCode ContentAnalysisEventWin::Close() {
|
||||
Shutdown();
|
||||
return ContentAnalysisEventBase::Close();
|
||||
}
|
||||
|
||||
ResultCode ContentAnalysisEventWin::Send() {
|
||||
if (response_sent_) {
|
||||
return ResultCode::ERR_RESPONSE_ALREADY_SENT;
|
||||
}
|
||||
|
||||
response_sent_ = true;
|
||||
|
||||
DWORD err = WriteMessageToPipe(hPipe_,
|
||||
agent_to_chrome()->SerializeAsString());
|
||||
return ErrorToResultCode(err);
|
||||
}
|
||||
|
||||
std::string ContentAnalysisEventWin::DebugString() const {
|
||||
std::stringstream state;
|
||||
state.setf(std::ios::boolalpha);
|
||||
state << "ContentAnalysisEventWin{handle=" << hPipe_;
|
||||
state << " pid=" << GetBrowserInfo().pid;
|
||||
state << " rtoken=" << GetRequest().request_token();
|
||||
state << " sent=" << response_sent_;
|
||||
state << "}" << std::ends;
|
||||
|
||||
return state.str();
|
||||
}
|
||||
|
||||
void ContentAnalysisEventWin::Shutdown() {
|
||||
if (hPipe_ != INVALID_HANDLE_VALUE) {
|
||||
// If no response has been sent yet, attempt to send it now. Otherwise
|
||||
// the client may be stuck waiting. After shutdown the agent will not
|
||||
// have any other chance to respond.
|
||||
if (!response_sent_) {
|
||||
Send();
|
||||
}
|
||||
|
||||
// This event does not own the pipe, so don't close it.
|
||||
FlushFileBuffers(hPipe_);
|
||||
hPipe_ = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
45
third_party/content_analysis_sdk/agent/src/event_win.h
vendored
Normal file
45
third_party/content_analysis_sdk/agent/src/event_win.h
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_AGENT_SRC_EVENT_WIN_H_
|
||||
#define CONTENT_ANALYSIS_AGENT_SRC_EVENT_WIN_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "event_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// ContentAnalysisEvent implementaton for Windows.
|
||||
class ContentAnalysisEventWin : public ContentAnalysisEventBase {
|
||||
public:
|
||||
ContentAnalysisEventWin(HANDLE handle,
|
||||
const BrowserInfo& browser_info,
|
||||
ContentAnalysisRequest request);
|
||||
~ContentAnalysisEventWin() override;
|
||||
|
||||
// Initialize the event. This involves reading the request from Google
|
||||
// Chrome and making sure it is well formed.
|
||||
ResultCode Init();
|
||||
|
||||
// ContentAnalysisEvent:
|
||||
ResultCode Close() override;
|
||||
ResultCode Send() override;
|
||||
std::string DebugString() const override;
|
||||
|
||||
private:
|
||||
void Shutdown();
|
||||
|
||||
// This handle is not owned by the event.
|
||||
HANDLE hPipe_ = INVALID_HANDLE_VALUE;
|
||||
|
||||
// Set to true when Send() is called the first time.
|
||||
bool response_sent_ = false;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_AGENT_SRC_EVENT_WIN_H_
|
||||
116
third_party/content_analysis_sdk/agent/src/event_win_unittest.cc
vendored
Normal file
116
third_party/content_analysis_sdk/agent/src/event_win_unittest.cc
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "agent/src/event_win.h"
|
||||
#include "common/utils_win.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
namespace testing {
|
||||
|
||||
std::unique_ptr<ContentAnalysisEventWin> CreateEvent(
|
||||
HANDLE handle,
|
||||
const BrowserInfo& browser_info,
|
||||
ContentAnalysisRequest request) {
|
||||
return std::make_unique<ContentAnalysisEventWin>(
|
||||
handle, browser_info, std::move(request));
|
||||
}
|
||||
|
||||
TEST(EventTest, Create_BrowserInfo) {
|
||||
const BrowserInfo bi{12345, "/path/to/binary"};
|
||||
ContentAnalysisRequest request;
|
||||
*request.add_tags() = "foo";
|
||||
request.set_request_token("req-token");
|
||||
|
||||
auto event = CreateEvent(INVALID_HANDLE_VALUE, bi, request);
|
||||
ASSERT_TRUE(event);
|
||||
|
||||
ASSERT_EQ(bi.pid, event->GetBrowserInfo().pid);
|
||||
ASSERT_EQ(bi.binary_path, event->GetBrowserInfo().binary_path);
|
||||
}
|
||||
|
||||
TEST(EventTest, Create_Request) {
|
||||
const BrowserInfo bi{ 12345, "/path/to/binary" };
|
||||
ContentAnalysisRequest request;
|
||||
*request.add_tags() = "foo";
|
||||
request.set_request_token("req-token");
|
||||
|
||||
auto event = CreateEvent(INVALID_HANDLE_VALUE, bi, request);
|
||||
ASSERT_TRUE(event);
|
||||
|
||||
ASSERT_EQ(1u, event->GetRequest().tags_size());
|
||||
ASSERT_EQ(request.tags(0), event->GetRequest().tags(0));
|
||||
ASSERT_TRUE(event->GetRequest().has_request_token());
|
||||
ASSERT_EQ(request.request_token(), event->GetRequest().request_token());
|
||||
}
|
||||
|
||||
TEST(EventTest, Create_Init) {
|
||||
const BrowserInfo bi{ 12345, "/path/to/binary" };
|
||||
ContentAnalysisRequest request;
|
||||
*request.add_tags() = "foo";
|
||||
request.set_request_token("req-token");
|
||||
|
||||
auto event = CreateEvent(INVALID_HANDLE_VALUE, bi, request);
|
||||
ASSERT_TRUE(event);
|
||||
|
||||
ASSERT_EQ(ResultCode::OK, event->Init());
|
||||
|
||||
// Initializing an event should initialize the contained response for a
|
||||
// success verdict that matches the request.
|
||||
ASSERT_EQ(request.request_token(), event->GetResponse().request_token());
|
||||
ASSERT_EQ(1u, event->GetResponse().results_size());
|
||||
ASSERT_EQ(ContentAnalysisResponse::Result::SUCCESS,
|
||||
event->GetResponse().results(0).status());
|
||||
ASSERT_TRUE(event->GetResponse().results(0).has_tag());
|
||||
ASSERT_EQ(request.tags(0), event->GetResponse().results(0).tag());
|
||||
ASSERT_EQ(0u, event->GetResponse().results(0).triggered_rules_size());
|
||||
}
|
||||
|
||||
// Initializing an event whose request has no request token is an error.
|
||||
TEST(EventTest, Create_Init_RequestNoRequestToken) {
|
||||
const BrowserInfo bi{ 12345, "/path/to/binary" };
|
||||
ContentAnalysisRequest request;
|
||||
*request.add_tags() = "foo";
|
||||
|
||||
auto event = CreateEvent(INVALID_HANDLE_VALUE, bi, request);
|
||||
ASSERT_TRUE(event);
|
||||
|
||||
ASSERT_EQ(ResultCode::ERR_MISSING_REQUEST_TOKEN, event->Init());
|
||||
}
|
||||
|
||||
TEST(EventTest, Write_BadPipe) {
|
||||
HANDLE pipe;
|
||||
DWORD err = internal::CreatePipe(
|
||||
internal::GetPipeNameForAgent("testpipe", false), false, true, &pipe);
|
||||
ASSERT_EQ(ERROR_SUCCESS, err);
|
||||
ASSERT_NE(INVALID_HANDLE_VALUE, pipe);
|
||||
|
||||
// Create an event with the dummy pipe and initilalize it.
|
||||
const BrowserInfo bi{ 12345, "/path/to/binary" };
|
||||
ContentAnalysisRequest request;
|
||||
request.set_request_token("req-token");
|
||||
*request.add_tags() = "dlp";
|
||||
request.set_text_content("test");
|
||||
auto event = std::make_unique<ContentAnalysisEventWin>(
|
||||
pipe, bi, std::move(request));
|
||||
ASSERT_TRUE(event);
|
||||
ResultCode rc = event->Init();
|
||||
ASSERT_EQ(ResultCode::OK, rc);
|
||||
|
||||
// Close the handle before trying to send the response.
|
||||
// This simulates an error with the pipe.
|
||||
CloseHandle(pipe);
|
||||
ASSERT_EQ(ERROR_SUCCESS, GetLastError());
|
||||
|
||||
// The following call should not hang.
|
||||
event->Send();
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
17
third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.cc
vendored
Normal file
17
third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.cc
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2023 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "scoped_print_handle_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
ScopedPrintHandleBase::ScopedPrintHandleBase(
|
||||
const ContentAnalysisRequest::PrintData& print_data)
|
||||
: size_(print_data.size()) {}
|
||||
|
||||
size_t ScopedPrintHandleBase::size() { return size_; }
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
25
third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.h
vendored
Normal file
25
third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.h
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2023 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_BASE_H_
|
||||
#define CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_BASE_H_
|
||||
|
||||
#include "content_analysis/sdk/analysis_agent.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
class ScopedPrintHandleBase : public ScopedPrintHandle {
|
||||
public:
|
||||
ScopedPrintHandleBase(const ContentAnalysisRequest::PrintData& print_data);
|
||||
|
||||
size_t size() override;
|
||||
protected:
|
||||
size_t size_ = 0;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_BASE_H_
|
||||
36
third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.cc
vendored
Normal file
36
third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.cc
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2023 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "scoped_print_handle_mac.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
std::unique_ptr<ScopedPrintHandle>
|
||||
CreateScopedPrintHandle(const ContentAnalysisRequest& request,
|
||||
int64_t browser_pid) {
|
||||
if (!request.has_print_data() || !request.print_data().has_handle()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<ScopedPrintHandleMac>(request.print_data());
|
||||
}
|
||||
|
||||
ScopedPrintHandleMac::ScopedPrintHandleMac(
|
||||
const ContentAnalysisRequest::PrintData& print_data)
|
||||
: ScopedPrintHandleBase(print_data) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
ScopedPrintHandleMac::~ScopedPrintHandleMac() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
const char* ScopedPrintHandleMac::data() {
|
||||
// TODO
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
24
third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.h
vendored
Normal file
24
third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.h
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2023 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_MAC_H_
|
||||
#define CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_MAC_H_
|
||||
|
||||
#include "scoped_print_handle_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
class ScopedPrintHandleMac : public ScopedPrintHandleBase {
|
||||
public:
|
||||
ScopedPrintHandleMac(const ContentAnalysisRequest::PrintData& print_data);
|
||||
~ScopedPrintHandleMac() override;
|
||||
|
||||
const char* data() override;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_MAC_H_
|
||||
36
third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.cc
vendored
Normal file
36
third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.cc
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2023 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "scoped_print_handle_posix.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
std::unique_ptr<ScopedPrintHandle>
|
||||
CreateScopedPrintHandle(const ContentAnalysisRequest& request,
|
||||
int64_t browser_pid) {
|
||||
if (!request.has_print_data() || !request.print_data().has_handle()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<ScopedPrintHandlePosix>(request.print_data());
|
||||
}
|
||||
|
||||
ScopedPrintHandlePosix::ScopedPrintHandlePosix(
|
||||
const ContentAnalysisRequest::PrintData& print_data)
|
||||
: ScopedPrintHandleBase(print_data) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
ScopedPrintHandlePosix::~ScopedPrintHandlePosix() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
const char* ScopedPrintHandlePosix::data() {
|
||||
// TODO
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
24
third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.h
vendored
Normal file
24
third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.h
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2023 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_POSIX_H_
|
||||
#define CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_POSIX_H_
|
||||
|
||||
#include "scoped_print_handle_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
class ScopedPrintHandlePosix : public ScopedPrintHandleBase {
|
||||
public:
|
||||
ScopedPrintHandlePosix(const ContentAnalysisRequest::PrintData& print_data);
|
||||
~ScopedPrintHandlePosix() override;
|
||||
|
||||
const char* data() override;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_POSIX_H_
|
||||
67
third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.cc
vendored
Normal file
67
third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.cc
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright 2023 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "scoped_print_handle_win.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
std::unique_ptr<ScopedPrintHandle>
|
||||
CreateScopedPrintHandle(const ContentAnalysisRequest& request,
|
||||
int64_t browser_pid) {
|
||||
if (!request.has_print_data() || !request.print_data().has_handle()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The handle in the request must be duped to be read by the agent
|
||||
// process. If that doesn't work for any reason, return null.
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle
|
||||
// for details.
|
||||
HANDLE browser_process = OpenProcess(
|
||||
/*dwDesiredAccess=*/PROCESS_DUP_HANDLE,
|
||||
/*bInheritHandle=*/false,
|
||||
/*dwProcessId=*/browser_pid);
|
||||
if (!browser_process)
|
||||
return nullptr;
|
||||
|
||||
HANDLE dupe = nullptr;
|
||||
DuplicateHandle(
|
||||
/*hSourceProcessHandle=*/browser_process,
|
||||
/*hSourceHandle=*/reinterpret_cast<HANDLE>(request.print_data().handle()),
|
||||
/*hTargetProcessHandle=*/GetCurrentProcess(),
|
||||
/*lpTargetHandle=*/&dupe,
|
||||
/*dwDesiredAccess=*/PROCESS_DUP_HANDLE | FILE_MAP_READ,
|
||||
/*bInheritHandle=*/false,
|
||||
/*dwOptions=*/0);
|
||||
|
||||
CloseHandle(browser_process);
|
||||
|
||||
if (!dupe)
|
||||
return nullptr;
|
||||
|
||||
ContentAnalysisRequest::PrintData dupe_print_data;
|
||||
dupe_print_data.set_handle(reinterpret_cast<int64_t>(dupe));
|
||||
dupe_print_data.set_size(request.print_data().size());
|
||||
|
||||
|
||||
return std::make_unique<ScopedPrintHandleWin>(dupe_print_data);
|
||||
}
|
||||
|
||||
ScopedPrintHandleWin::ScopedPrintHandleWin(
|
||||
const ContentAnalysisRequest::PrintData& print_data)
|
||||
: ScopedPrintHandleBase(print_data),
|
||||
handle_(reinterpret_cast<HANDLE>(print_data.handle())) {
|
||||
mapped_ = MapViewOfFile(handle_, FILE_MAP_READ, 0, 0, 0);
|
||||
}
|
||||
|
||||
ScopedPrintHandleWin::~ScopedPrintHandleWin() {
|
||||
CloseHandle(handle_);
|
||||
}
|
||||
|
||||
const char* ScopedPrintHandleWin::data() {
|
||||
return reinterpret_cast<const char*>(mapped_);
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
29
third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.h
vendored
Normal file
29
third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.h
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2023 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_WIN_H_
|
||||
#define CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_WIN_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "scoped_print_handle_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
class ScopedPrintHandleWin : public ScopedPrintHandleBase {
|
||||
public:
|
||||
ScopedPrintHandleWin(const ContentAnalysisRequest::PrintData& print_data);
|
||||
~ScopedPrintHandleWin() override;
|
||||
|
||||
const char* data() override;
|
||||
private:
|
||||
void* mapped_ = nullptr;
|
||||
HANDLE handle_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_WIN_H_
|
||||
84
third_party/content_analysis_sdk/browser/include/content_analysis/sdk/analysis_client.h
vendored
Normal file
84
third_party/content_analysis_sdk/browser/include/content_analysis/sdk/analysis_client.h
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_BROWSER_INCLUDE_CONTENT_ANALYSIS_SDK_ANALYSIS_CLIENT_H_
|
||||
#define CONTENT_ANALYSIS_BROWSER_INCLUDE_CONTENT_ANALYSIS_SDK_ANALYSIS_CLIENT_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "content_analysis/sdk/analysis.pb.h"
|
||||
|
||||
// This is the main include file for code using Content Analysis Connector
|
||||
// Client SDK. No other include is needed.
|
||||
//
|
||||
// A browser begins by creating an instance of Client using the factory
|
||||
// function Client::Create(). This instance should live as long as the browser
|
||||
// intends to send content analysis requests to the content analysis agent.
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Represents information about one instance of a content analysis agent
|
||||
// process that is connected to the browser.
|
||||
struct AgentInfo {
|
||||
unsigned long pid = 0; // Process ID of content analysis agent process.
|
||||
std::string binary_path; // The full path to the process's main binary.
|
||||
};
|
||||
|
||||
// Represents a client that can request content analysis for locally running
|
||||
// agent. This class holds the client endpoint that the browser connects
|
||||
// with when content analysis is required.
|
||||
//
|
||||
// See the demo directory for an example of how to use this class.
|
||||
class Client {
|
||||
public:
|
||||
// Configuration options where creating an agent. `name` is used to create
|
||||
// a channel between the agent and Google Chrome.
|
||||
struct Config {
|
||||
// Used to create a channel between the agent and Google Chrome. Both must
|
||||
// use the same name to properly rendezvous with each other. The channel
|
||||
// is platform specific.
|
||||
std::string name;
|
||||
|
||||
// Set to true if there is a different agent instance per OS user. Defaults
|
||||
// to false.
|
||||
bool user_specific = false;
|
||||
};
|
||||
|
||||
// Returns a new client instance and calls Start().
|
||||
static std::unique_ptr<Client> Create(Config config);
|
||||
|
||||
virtual ~Client() = default;
|
||||
|
||||
// Returns the configuration parameters used to create the client.
|
||||
virtual const Config& GetConfig() const = 0;
|
||||
|
||||
// Retrives information about the agent that is connected to the browser.
|
||||
virtual const AgentInfo& GetAgentInfo() const = 0;
|
||||
|
||||
// Sends an analysis request to the agent and waits for a response.
|
||||
virtual int Send(ContentAnalysisRequest request,
|
||||
ContentAnalysisResponse* response) = 0;
|
||||
|
||||
// Sends an response acknowledgment back to the agent.
|
||||
virtual int Acknowledge(const ContentAnalysisAcknowledgement& ack) = 0;
|
||||
|
||||
// Ask the agent to cancel all requests matching the criteria in `cancel`.
|
||||
// This is a best effort only, the agent may cancel some, all, or no requests
|
||||
// that match.
|
||||
virtual int CancelRequests(const ContentAnalysisCancelRequests& cancel) = 0;
|
||||
|
||||
protected:
|
||||
Client() = default;
|
||||
Client(const Client& rhs) = delete;
|
||||
Client(Client&& rhs) = delete;
|
||||
Client& operator=(const Client& rhs) = delete;
|
||||
Client& operator=(Client&& rhs) = delete;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_BROWSER_INCLUDE_CONTENT_ANALYSIS_SDK_ANALYSIS_CLIENT_H_
|
||||
21
third_party/content_analysis_sdk/browser/src/client_base.cc
vendored
Normal file
21
third_party/content_analysis_sdk/browser/src/client_base.cc
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "client_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
ClientBase::ClientBase(Config config) : config_(config) {}
|
||||
|
||||
const Client::Config& ClientBase::GetConfig() const {
|
||||
return config_;
|
||||
}
|
||||
|
||||
const AgentInfo& ClientBase::GetAgentInfo() const {
|
||||
return agent_info_;
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
34
third_party/content_analysis_sdk/browser/src/client_base.h
vendored
Normal file
34
third_party/content_analysis_sdk/browser/src/client_base.h
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_BASE_H_
|
||||
#define CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_BASE_H_
|
||||
|
||||
#include "content_analysis/sdk/analysis_client.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Base Client class with code common to all platforms.
|
||||
class ClientBase : public Client {
|
||||
public:
|
||||
// Client:
|
||||
const Config& GetConfig() const override;
|
||||
const AgentInfo& GetAgentInfo() const override;
|
||||
|
||||
protected:
|
||||
ClientBase(Config config);
|
||||
|
||||
const Config& configuration() const { return config_; }
|
||||
AgentInfo& agent_info() { return agent_info_; }
|
||||
|
||||
private:
|
||||
Config config_;
|
||||
AgentInfo agent_info_;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_BASE_H_
|
||||
33
third_party/content_analysis_sdk/browser/src/client_mac.cc
vendored
Normal file
33
third_party/content_analysis_sdk/browser/src/client_mac.cc
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "client_mac.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// static
|
||||
std::unique_ptr<Client> Client::Create(Config config) {
|
||||
return std::make_unique<ClientMac>(std::move(config));
|
||||
}
|
||||
|
||||
ClientMac::ClientMac(Config config) : ClientBase(std::move(config)) {}
|
||||
|
||||
int ClientMac::Send(ContentAnalysisRequest request,
|
||||
ContentAnalysisResponse* response) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ClientMac::Acknowledge(const ContentAnalysisAcknowledgement& ack) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ClientMac::CancelRequests(const ContentAnalysisCancelRequests& cancel) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
28
third_party/content_analysis_sdk/browser/src/client_mac.h
vendored
Normal file
28
third_party/content_analysis_sdk/browser/src/client_mac.h
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_MAC_H_
|
||||
#define CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_MAC_H_
|
||||
|
||||
#include "client_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Client implementaton for macOS.
|
||||
class ClientMac : public ClientBase {
|
||||
public:
|
||||
ClientMac(Config config);
|
||||
|
||||
// Client:
|
||||
int Send(ContentAnalysisRequest request,
|
||||
ContentAnalysisResponse* response) override;
|
||||
int Acknowledge(const ContentAnalysisAcknowledgement& ack) override;
|
||||
int CancelRequests(const ContentAnalysisCancelRequests& cancel) override;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_MAC_H_
|
||||
33
third_party/content_analysis_sdk/browser/src/client_posix.cc
vendored
Normal file
33
third_party/content_analysis_sdk/browser/src/client_posix.cc
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "client_posix.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// static
|
||||
std::unique_ptr<Client> Client::Create(Config config) {
|
||||
return std::make_unique<ClientPosix>(std::move(config));
|
||||
}
|
||||
|
||||
ClientPosix::ClientPosix(Config config) : ClientBase(std::move(config)) {}
|
||||
|
||||
int ClientPosix::Send(ContentAnalysisRequest request,
|
||||
ContentAnalysisResponse* response) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ClientPosix::Acknowledge(const ContentAnalysisAcknowledgement& ack) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ClientPosix::CancelRequests(const ContentAnalysisCancelRequests& cancel) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
28
third_party/content_analysis_sdk/browser/src/client_posix.h
vendored
Normal file
28
third_party/content_analysis_sdk/browser/src/client_posix.h
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_POSIX_H_
|
||||
#define CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_POSIX_H_
|
||||
|
||||
#include "client_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Client implementaton for Posix.
|
||||
class ClientPosix : public ClientBase {
|
||||
public:
|
||||
ClientPosix(Config config);
|
||||
|
||||
// Client:
|
||||
int Send(ContentAnalysisRequest request,
|
||||
ContentAnalysisResponse* response) override;
|
||||
int Acknowledge(const ContentAnalysisAcknowledgement& ack) override;
|
||||
int CancelRequests(const ContentAnalysisCancelRequests& cancel) override;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_POSIX_H_
|
||||
428
third_party/content_analysis_sdk/browser/src/client_win.cc
vendored
Normal file
428
third_party/content_analysis_sdk/browser/src/client_win.cc
vendored
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/utils_win.h"
|
||||
|
||||
#include "client_win.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
const DWORD kBufferSize = 4096;
|
||||
|
||||
// Use the same default timeout value (50ms) as CreateNamedPipeA(), expressed
|
||||
// in 100ns intervals.
|
||||
constexpr LONGLONG kDefaultTimeout = 500000;
|
||||
|
||||
// The following #defines and struct are copied from the official Microsoft
|
||||
// Windows Driver Kit headers because they are not available in the official
|
||||
// Microsoft Windows user mode SDK headers.
|
||||
|
||||
#define FSCTL_PIPE_WAIT 0x110018
|
||||
#define STATUS_SUCCESS ((NTSTATUS)0)
|
||||
#define STATUS_PIPE_NOT_AVAILABLE ((NTSTATUS)0xc00000ac)
|
||||
#define STATUS_IO_TIMEOUT ((NTSTATUS)0xc00000b5)
|
||||
|
||||
typedef struct _FILE_PIPE_WAIT_FOR_BUFFER {
|
||||
LARGE_INTEGER Timeout;
|
||||
ULONG NameLength;
|
||||
BOOLEAN TimeoutSpecified;
|
||||
WCHAR Name[1];
|
||||
} FILE_PIPE_WAIT_FOR_BUFFER, *PFILE_PIPE_WAIT_FOR_BUFFER;
|
||||
|
||||
namespace {
|
||||
|
||||
using NtCreateFileFn = decltype(&::NtCreateFile);
|
||||
|
||||
NtCreateFileFn GetNtCreateFileFn() {
|
||||
static NtCreateFileFn fnNtCreateFile = []() {
|
||||
NtCreateFileFn fn = nullptr;
|
||||
HMODULE h = LoadLibraryA("NtDll.dll");
|
||||
if (h != nullptr) {
|
||||
fn = reinterpret_cast<NtCreateFileFn>(GetProcAddress(h, "NtCreateFile"));
|
||||
FreeLibrary(h);
|
||||
}
|
||||
return fn;
|
||||
}();
|
||||
|
||||
return fnNtCreateFile;
|
||||
}
|
||||
|
||||
|
||||
using NtFsControlFileFn = NTSTATUS (NTAPI *)(
|
||||
HANDLE FileHandle,
|
||||
HANDLE Event,
|
||||
PIO_APC_ROUTINE ApcRoutine,
|
||||
PVOID ApcContext,
|
||||
PIO_STATUS_BLOCK IoStatusBlock,
|
||||
ULONG IoControlCode,
|
||||
PVOID InputBuffer,
|
||||
ULONG InputBufferLength,
|
||||
PVOID OutputBuffer,
|
||||
ULONG OutputBufferLength);
|
||||
|
||||
NtFsControlFileFn GetNtFsControlFileFn() {
|
||||
static NtFsControlFileFn fnNtFsControlFile = []() {
|
||||
NtFsControlFileFn fn = nullptr;
|
||||
HMODULE h = LoadLibraryA("NtDll.dll");
|
||||
if (h != nullptr) {
|
||||
fn = reinterpret_cast<NtFsControlFileFn>(GetProcAddress(h, "NtFsControlFile"));
|
||||
FreeLibrary(h);
|
||||
}
|
||||
return fn;
|
||||
}();
|
||||
|
||||
return fnNtFsControlFile;
|
||||
}
|
||||
|
||||
NTSTATUS WaitForPipeAvailability(const UNICODE_STRING& path) {
|
||||
NtCreateFileFn fnNtCreateFile = GetNtCreateFileFn();
|
||||
if (fnNtCreateFile == nullptr) {
|
||||
return false;
|
||||
}
|
||||
NtFsControlFileFn fnNtFsControlFile = GetNtFsControlFileFn();
|
||||
if (fnNtFsControlFile == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build the device name. This is the initial part of `path` which is
|
||||
// assumed to start with the string `kPipePrefixForClient`. The `Length`
|
||||
// field is measured in bytes, not characters, and does not include the null
|
||||
// terminator. It's important that the device name ends with a trailing
|
||||
// backslash.
|
||||
size_t device_name_char_length = std::strlen(internal::kPipePrefixForClient);
|
||||
UNICODE_STRING device_name;
|
||||
device_name.Buffer = path.Buffer;
|
||||
device_name.Length = device_name_char_length * sizeof(wchar_t);
|
||||
device_name.MaximumLength = device_name.Length;
|
||||
|
||||
// Build the pipe name. This is the remaining part of `path` after the device
|
||||
// name.
|
||||
UNICODE_STRING pipe_name;
|
||||
pipe_name.Buffer = path.Buffer + device_name_char_length;
|
||||
pipe_name.Length = path.Length - device_name.Length;
|
||||
pipe_name.MaximumLength = pipe_name.Length;
|
||||
|
||||
// Build the ioctl input buffer. This buffer is the size of
|
||||
// FILE_PIPE_WAIT_FOR_BUFFER plus the length of the pipe name. Since
|
||||
// FILE_PIPE_WAIT_FOR_BUFFER includes one WCHAR this includes space for
|
||||
// the terminating null character of the name which wcsncpy() copies.
|
||||
size_t buffer_size = sizeof(FILE_PIPE_WAIT_FOR_BUFFER) + pipe_name.Length;
|
||||
std::vector<char> buffer(buffer_size);
|
||||
FILE_PIPE_WAIT_FOR_BUFFER* wait_buffer =
|
||||
reinterpret_cast<FILE_PIPE_WAIT_FOR_BUFFER*>(buffer.data());
|
||||
wait_buffer->Timeout.QuadPart = kDefaultTimeout;
|
||||
wait_buffer->NameLength = pipe_name.Length;
|
||||
wait_buffer->TimeoutSpecified = TRUE;
|
||||
std::wcsncpy(wait_buffer->Name, pipe_name.Buffer, wait_buffer->NameLength /
|
||||
sizeof(wchar_t));
|
||||
|
||||
OBJECT_ATTRIBUTES attr;
|
||||
InitializeObjectAttributes(&attr, &device_name, OBJ_CASE_INSENSITIVE, nullptr,
|
||||
nullptr);
|
||||
|
||||
IO_STATUS_BLOCK io;
|
||||
HANDLE h = INVALID_HANDLE_VALUE;
|
||||
NTSTATUS sts = fnNtCreateFile(&h, GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
|
||||
&attr, &io, /*AllocationSize=*/nullptr, FILE_ATTRIBUTE_NORMAL,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN,
|
||||
FILE_SYNCHRONOUS_IO_NONALERT, /*EaBuffer=*/nullptr, /*EaLength=*/0);
|
||||
if (sts != STATUS_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IO_STATUS_BLOCK io2;
|
||||
sts = fnNtFsControlFile(h, /*Event=*/nullptr, /*ApcRoutine=*/nullptr,
|
||||
/*ApcContext*/nullptr, &io2, FSCTL_PIPE_WAIT, buffer.data(),
|
||||
buffer.size(), nullptr, 0);
|
||||
CloseHandle(h);
|
||||
return sts;
|
||||
}
|
||||
|
||||
// Reads the next message from the pipe and returns a buffer of chars.
|
||||
// This function is synchronous.
|
||||
std::vector<char> ReadNextMessageFromPipe(
|
||||
HANDLE pipe,
|
||||
OVERLAPPED* overlapped) {
|
||||
DWORD err = ERROR_SUCCESS;
|
||||
std::vector<char> buffer(kBufferSize);
|
||||
char* p = buffer.data();
|
||||
int final_size = 0;
|
||||
while (true) {
|
||||
DWORD read;
|
||||
|
||||
// Even though the pipe is opened for overlapped IO, the read operation
|
||||
// could still completely synchronously. For example, a server's response
|
||||
// message could already be available in the pipe's internal buffer.
|
||||
// If ReadFile() does complete synchronously, TRUE is returned. In this
|
||||
// case update the final size and exit the loop.
|
||||
if (ReadFile(pipe, p, kBufferSize, &read, overlapped)) {
|
||||
final_size += read;
|
||||
break;
|
||||
} else {
|
||||
// Reaching here means that ReadFile() will either complete async or
|
||||
// an error has occurred. The former case is detected if the error code
|
||||
// is "IO pending", in which case GetOverlappedResult() is called to wait
|
||||
// for the IO to complete. If that function returns TRUE then the read
|
||||
// operation completed successfully and the code simply updates the final
|
||||
// size and exits the loop.
|
||||
err = GetLastError();
|
||||
if (err == ERROR_IO_PENDING) {
|
||||
if (GetOverlappedResult(pipe, overlapped, &read, /*wait=*/TRUE)) {
|
||||
final_size += read;
|
||||
break;
|
||||
} else {
|
||||
err = GetLastError();
|
||||
}
|
||||
}
|
||||
|
||||
// Reaching here means an error has occurred. One error is recoverable:
|
||||
// "more data". For any other type of error break out of the loop.
|
||||
if (err != ERROR_MORE_DATA) {
|
||||
final_size = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Reaching here means the error is "more data", that is, the buffer
|
||||
// specified in ReadFile() was too small to contain the entire response
|
||||
// message from the server. ReadFile() has placed the start of the
|
||||
// message in the specified buffer but ReadFile() needs to be called
|
||||
// again to read the remaining part.
|
||||
//
|
||||
// The buffer size is increased and the current pointer into the buffer
|
||||
// `p` is adjusted so that when the loop re-runs, it calls ReadFile()
|
||||
// with the correct point in the buffer. It's possible that this loop
|
||||
// might have to run many times if the response message is rather large.
|
||||
buffer.resize(buffer.size() + kBufferSize);
|
||||
p = buffer.data() + buffer.size() - kBufferSize;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.resize(final_size);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Writes a string to the pipe. Returns true if successful, false otherwise.
|
||||
// This function is synchronous.
|
||||
bool WriteMessageToPipe(
|
||||
HANDLE pipe,
|
||||
const std::string& message,
|
||||
OVERLAPPED* overlapped) {
|
||||
if (message.empty())
|
||||
return false;
|
||||
|
||||
// Even though the pipe is opened for overlapped IO, the write operation
|
||||
// could still completely synchronously. If it does, TRUE is returned.
|
||||
// In this case the function is done.
|
||||
bool ok = WriteFile(pipe, message.data(), message.size(), nullptr, overlapped);
|
||||
if (!ok) {
|
||||
// Reaching here means that WriteFile() will either complete async or
|
||||
// an error has occurred. The former case is detected if the error code
|
||||
// is "IO pending", in which case GetOverlappedResult() is called to wait
|
||||
// for the IO to complete. Whether the operation completes sync or async,
|
||||
// return true if the operation succeeded and false otherwise.
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_IO_PENDING) {
|
||||
DWORD written;
|
||||
ok = GetOverlappedResult(pipe, overlapped, &written, /*wait=*/TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
std::unique_ptr<Client> Client::Create(Config config) {
|
||||
int rc;
|
||||
auto client = std::make_unique<ClientWin>(std::move(config), &rc);
|
||||
return rc == 0 ? std::move(client) : nullptr;
|
||||
}
|
||||
|
||||
ClientWin::ClientWin(Config config, int* rc) : ClientBase(std::move(config)) {
|
||||
*rc = -1;
|
||||
|
||||
std::string pipename =
|
||||
internal::GetPipeNameForClient(configuration().name,
|
||||
configuration().user_specific);
|
||||
if (!pipename.empty()) {
|
||||
unsigned long pid = 0;
|
||||
if (ConnectToPipe(pipename, &hPipe_) == ERROR_SUCCESS &&
|
||||
GetNamedPipeServerProcessId(hPipe_, &pid)) {
|
||||
agent_info().pid = pid;
|
||||
|
||||
// Getting the process path is best effort.
|
||||
*rc = 0;
|
||||
std::string binary_path;
|
||||
if (internal::GetProcessPath(pid, &binary_path)) {
|
||||
agent_info().binary_path = std::move(binary_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (*rc != 0) {
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
ClientWin::~ClientWin() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
int ClientWin::Send(ContentAnalysisRequest request,
|
||||
ContentAnalysisResponse* response) {
|
||||
ChromeToAgent chrome_to_agent;
|
||||
*chrome_to_agent.mutable_request() = std::move(request);
|
||||
|
||||
internal::ScopedOverlapped overlapped;
|
||||
if (!overlapped.is_valid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool success = WriteMessageToPipe(hPipe_,
|
||||
chrome_to_agent.SerializeAsString(),
|
||||
overlapped);
|
||||
if (success) {
|
||||
std::vector<char> buffer = ReadNextMessageFromPipe(hPipe_, overlapped);
|
||||
AgentToChrome agent_to_chrome;
|
||||
success = buffer.size() > 0 &&
|
||||
agent_to_chrome.ParseFromArray(buffer.data(), buffer.size());
|
||||
if (success) {
|
||||
*response = std::move(*agent_to_chrome.mutable_response());
|
||||
}
|
||||
}
|
||||
|
||||
return success ? 0 : -1;
|
||||
}
|
||||
|
||||
int ClientWin::Acknowledge(const ContentAnalysisAcknowledgement& ack) {
|
||||
// TODO: could avoid a copy by changing argument to be
|
||||
// `ContentAnalysisAcknowledgement ack` and then using std::move() below and
|
||||
// at call site.
|
||||
ChromeToAgent chrome_to_agent;
|
||||
*chrome_to_agent.mutable_ack() = ack;
|
||||
|
||||
internal::ScopedOverlapped overlapped;
|
||||
if (!overlapped.is_valid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return WriteMessageToPipe(hPipe_, chrome_to_agent.SerializeAsString(), overlapped)
|
||||
? 0 : -1;
|
||||
}
|
||||
|
||||
int ClientWin::CancelRequests(const ContentAnalysisCancelRequests& cancel) {
|
||||
// TODO: could avoid a copy by changing argument to be
|
||||
// `ContentAnalysisCancelRequests cancel` and then using std::move() below and
|
||||
// at call site.
|
||||
ChromeToAgent chrome_to_agent;
|
||||
*chrome_to_agent.mutable_cancel() = cancel;
|
||||
|
||||
internal::ScopedOverlapped overlapped;
|
||||
if (!overlapped.is_valid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return WriteMessageToPipe(hPipe_, chrome_to_agent.SerializeAsString(), overlapped)
|
||||
? 0 : -1;
|
||||
}
|
||||
|
||||
// static
|
||||
DWORD ClientWin::ConnectToPipe(const std::string& pipename, HANDLE* handle) {
|
||||
// Get pointers to the Ntxxx functions. This is required to use absolute
|
||||
// pipe names from the Windows NT Object Manager's namespace. This protects
|
||||
// against the "\\.\pipe" symlink being redirected.
|
||||
|
||||
NtCreateFileFn fnNtCreateFile = GetNtCreateFileFn();
|
||||
if (fnNtCreateFile == nullptr) {
|
||||
return ERROR_INVALID_FUNCTION;
|
||||
}
|
||||
|
||||
// Convert the path to a wchar_t string. Pass pipename.size() as the
|
||||
// `cbMultiByte` argument instead of -1 since the terminating null should not
|
||||
// be counted. NtCreateFile() does not expect the object name to be
|
||||
// terminated. Note that `buffer` and hence `name` created from it are both
|
||||
// unterminated strings.
|
||||
int wlen = MultiByteToWideChar(CP_ACP, 0, pipename.c_str(), pipename.size(),
|
||||
nullptr, 0);
|
||||
if (wlen == 0) {
|
||||
return GetLastError();
|
||||
}
|
||||
std::vector<wchar_t> buffer(wlen);
|
||||
MultiByteToWideChar(CP_ACP, 0, pipename.c_str(), pipename.size(),
|
||||
buffer.data(), wlen);
|
||||
|
||||
UNICODE_STRING name;
|
||||
name.Buffer = buffer.data();
|
||||
name.Length = wlen * sizeof(wchar_t); // Length in bytes, not characters.
|
||||
name.MaximumLength = name.Length;
|
||||
|
||||
OBJECT_ATTRIBUTES attr;
|
||||
InitializeObjectAttributes(&attr, &name, OBJ_CASE_INSENSITIVE, nullptr,
|
||||
nullptr);
|
||||
|
||||
// Open the named pipe for overlapped IO, i.e. do not specify either of the
|
||||
// FILE_SYNCHRONOUS_IO_xxxALERT in the creation option flags. If the pipe
|
||||
// is not opened for overlapped IO, then the Send() method will block if
|
||||
// called from different threads since only one read or write operation would
|
||||
// be allowed at a time.
|
||||
IO_STATUS_BLOCK io;
|
||||
HANDLE h = INVALID_HANDLE_VALUE;
|
||||
NTSTATUS sts = STATUS_IO_TIMEOUT;
|
||||
while (sts == STATUS_IO_TIMEOUT) {
|
||||
sts = fnNtCreateFile(&h, GENERIC_READ | GENERIC_WRITE |
|
||||
SYNCHRONIZE, &attr, &io, /*AllocationSize=*/nullptr,
|
||||
FILE_ATTRIBUTE_NORMAL, /*ShareAccess=*/0, FILE_OPEN,
|
||||
FILE_NON_DIRECTORY_FILE,
|
||||
/*EaBuffer=*/nullptr, /*EaLength=*/0);
|
||||
if (sts != STATUS_SUCCESS) {
|
||||
if (sts != STATUS_PIPE_NOT_AVAILABLE) {
|
||||
break;
|
||||
}
|
||||
|
||||
sts = WaitForPipeAvailability(name);
|
||||
if (sts != STATUS_SUCCESS && sts != STATUS_IO_TIMEOUT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sts != STATUS_SUCCESS) {
|
||||
return ERROR_PIPE_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
// Change to message read mode to match server side. Max connection count
|
||||
// and timeout must be null if client and server are on the same machine.
|
||||
DWORD mode = PIPE_READMODE_MESSAGE;
|
||||
if (!SetNamedPipeHandleState(h, &mode,
|
||||
/*maxCollectionCount=*/nullptr,
|
||||
/*connectionTimeout=*/nullptr)) {
|
||||
DWORD err = GetLastError();
|
||||
CloseHandle(h);
|
||||
return err;
|
||||
}
|
||||
|
||||
*handle = h;
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
void ClientWin::Shutdown() {
|
||||
if (hPipe_ != INVALID_HANDLE_VALUE) {
|
||||
FlushFileBuffers(hPipe_);
|
||||
CloseHandle(hPipe_);
|
||||
hPipe_ = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
39
third_party/content_analysis_sdk/browser/src/client_win.h
vendored
Normal file
39
third_party/content_analysis_sdk/browser/src/client_win.h
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_WIN_H_
|
||||
#define CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_WIN_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "client_base.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
|
||||
// Client implementaton for Windows.
|
||||
class ClientWin : public ClientBase {
|
||||
public:
|
||||
ClientWin(Config config, int* rc);
|
||||
~ClientWin() override;
|
||||
|
||||
// Client:
|
||||
int Send(ContentAnalysisRequest request,
|
||||
ContentAnalysisResponse* response) override;
|
||||
int Acknowledge(const ContentAnalysisAcknowledgement& ack) override;
|
||||
int CancelRequests(const ContentAnalysisCancelRequests& cancel) override;
|
||||
|
||||
private:
|
||||
static DWORD ConnectToPipe(const std::string& pipename, HANDLE* handle);
|
||||
|
||||
// Performs a clean shutdown of the client.
|
||||
void Shutdown();
|
||||
|
||||
HANDLE hPipe_ = INVALID_HANDLE_VALUE;
|
||||
};
|
||||
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_BROWSER_SRC_CLIENT_WIN_H_
|
||||
174
third_party/content_analysis_sdk/common/utils_win.cc
vendored
Normal file
174
third_party/content_analysis_sdk/common/utils_win.cc
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <windows.h>
|
||||
#include <sddl.h>
|
||||
|
||||
#include "utils_win.h"
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
namespace internal {
|
||||
|
||||
std::string GetUserSID() {
|
||||
std::string sid;
|
||||
|
||||
HANDLE handle;
|
||||
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &handle) &&
|
||||
!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &handle)) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
DWORD length = 0;
|
||||
std::vector<char> buffer;
|
||||
if (!GetTokenInformation(handle, TokenUser, nullptr, 0, &length)) {
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_INSUFFICIENT_BUFFER) {
|
||||
buffer.resize(length);
|
||||
}
|
||||
}
|
||||
if (GetTokenInformation(handle, TokenUser, buffer.data(), buffer.size(),
|
||||
&length)) {
|
||||
TOKEN_USER* info = reinterpret_cast<TOKEN_USER*>(buffer.data());
|
||||
char* sid_string;
|
||||
if (ConvertSidToStringSidA(info->User.Sid, &sid_string)) {
|
||||
sid = sid_string;
|
||||
LocalFree(sid_string);
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(handle);
|
||||
return sid;
|
||||
}
|
||||
|
||||
std::string BuildPipeName(const char* prefix,
|
||||
const std::string& base,
|
||||
bool user_specific) {
|
||||
std::string pipename = prefix;
|
||||
|
||||
// If the agent is not user-specific, the assumption is that it runs with
|
||||
// administrator privileges. Create the pipe in a location only available
|
||||
// to administrators.
|
||||
if (!user_specific)
|
||||
pipename += "ProtectedPrefix\\Administrators\\";
|
||||
|
||||
pipename += base;
|
||||
|
||||
if (user_specific) {
|
||||
std::string sid = GetUserSID();
|
||||
if (sid.empty())
|
||||
return std::string();
|
||||
|
||||
pipename += "." + sid;
|
||||
}
|
||||
|
||||
return pipename;
|
||||
}
|
||||
|
||||
std::string GetPipeNameForAgent(const std::string& base, bool user_specific) {
|
||||
return BuildPipeName(kPipePrefixForAgent, base, user_specific);
|
||||
}
|
||||
|
||||
std::string GetPipeNameForClient(const std::string& base, bool user_specific) {
|
||||
return BuildPipeName(kPipePrefixForClient, base, user_specific);
|
||||
}
|
||||
|
||||
DWORD CreatePipe(
|
||||
const std::string& name,
|
||||
bool user_specific,
|
||||
bool is_first_pipe,
|
||||
HANDLE* handle) {
|
||||
DWORD err = ERROR_SUCCESS;
|
||||
DWORD mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED;
|
||||
|
||||
// Create DACL for pipe to allow users on the local system to read and write
|
||||
// to the pipe. The creator and owner as well as administrator always get
|
||||
// full control of the pipe.
|
||||
//
|
||||
// If `user_specific` is true, a different agent instance is used for each
|
||||
// OS user, so only allow the interactive logged on user to reads and write
|
||||
// messages to the pipe. Otherwise only one agent instance used used for all
|
||||
// OS users and all authenticated logged on users can reads and write
|
||||
// messages.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language
|
||||
// for a description of this string format.
|
||||
constexpr char kDaclEveryone[] = "D:"
|
||||
"(A;OICI;GA;;;CO)" // Allow full control to creator owner.
|
||||
"(A;OICI;GA;;;BA)" // Allow full control to admins.
|
||||
"(A;OICI;GRGW;;;WD)"; // Allow read and write to everyone.
|
||||
constexpr char kDaclUserSpecific[] = "D:"
|
||||
"(A;OICI;GA;;;CO)" // Allow full control to creator owner.
|
||||
"(A;OICI;GA;;;BA)" // Allow full control to admins.
|
||||
"(A;OICI;GRGW;;;IU)"; // Allow read and write to interactive user.
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = FALSE;
|
||||
if (!ConvertStringSecurityDescriptorToSecurityDescriptorA(
|
||||
user_specific ? kDaclUserSpecific : kDaclEveryone, SDDL_REVISION_1,
|
||||
&sa.lpSecurityDescriptor, /*outSdSize=*/nullptr)) {
|
||||
err = GetLastError();
|
||||
return err;
|
||||
}
|
||||
|
||||
// When `is_first_pipe` is true, the agent expects there is no process that
|
||||
// is currently listening on this pipe. If there is, CreateNamedPipeA()
|
||||
// returns with ERROR_ACCESS_DENIED. This is used to detect if another
|
||||
// process is listening for connections when there shouldn't be.
|
||||
if (is_first_pipe) {
|
||||
mode |= FILE_FLAG_FIRST_PIPE_INSTANCE;
|
||||
}
|
||||
*handle = CreateNamedPipeA(name.c_str(), mode,
|
||||
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT |
|
||||
PIPE_REJECT_REMOTE_CLIENTS, PIPE_UNLIMITED_INSTANCES, kBufferSize,
|
||||
kBufferSize, 0, &sa);
|
||||
if (*handle == INVALID_HANDLE_VALUE) {
|
||||
err = GetLastError();
|
||||
}
|
||||
|
||||
// Free the security descriptor as it is no longer needed once
|
||||
// CreateNamedPipeA() returns.
|
||||
LocalFree(sa.lpSecurityDescriptor);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
bool GetProcessPath(unsigned long pid, std::string* binary_path) {
|
||||
HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
||||
if (hProc == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char path[MAX_PATH];
|
||||
DWORD size = sizeof(path);
|
||||
DWORD length = QueryFullProcessImageNameA(hProc, /*flags=*/0, path, &size);
|
||||
CloseHandle(hProc);
|
||||
if (length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*binary_path = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopedOverlapped::ScopedOverlapped() {
|
||||
memset(&overlapped_, 0, sizeof(overlapped_));
|
||||
overlapped_.hEvent = CreateEvent(/*securityAttr=*/nullptr,
|
||||
/*manualReset=*/TRUE,
|
||||
/*initialState=*/FALSE,
|
||||
/*name=*/nullptr);
|
||||
}
|
||||
|
||||
ScopedOverlapped::~ScopedOverlapped() {
|
||||
if (overlapped_.hEvent != nullptr) {
|
||||
CloseHandle(overlapped_.hEvent);
|
||||
}
|
||||
}
|
||||
|
||||
} // internal
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
76
third_party/content_analysis_sdk/common/utils_win.h
vendored
Normal file
76
third_party/content_analysis_sdk/common/utils_win.h
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Utility and helper functions common to both the agent and browser code.
|
||||
// This code is not publicly exposed from the SDK.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_COMMON_UTILS_WIN_H_
|
||||
#define CONTENT_ANALYSIS_COMMON_UTILS_WIN_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace content_analysis {
|
||||
namespace sdk {
|
||||
namespace internal {
|
||||
|
||||
// The default size of the buffer used to hold messages received from
|
||||
// Google Chrome.
|
||||
const DWORD kBufferSize = 4096;
|
||||
|
||||
// Named pipe prefixes used for agent and client side of pipe.
|
||||
constexpr char kPipePrefixForAgent[] = R"(\\.\\pipe\)";
|
||||
constexpr char kPipePrefixForClient[] = R"(\Device\NamedPipe\)";
|
||||
|
||||
// Returns the user SID of the thread or process that calls thie function.
|
||||
// Returns an empty string on error.
|
||||
std::string GetUserSID();
|
||||
|
||||
// Returns the name of the pipe that should be used to communicate between
|
||||
// the agent and Google Chrome. If `sid` is non-empty, make the pip name
|
||||
// specific to that user.
|
||||
//
|
||||
// GetPipeNameForAgent() is meant to be used in the agent. The returned
|
||||
// path can be used with CreatePipe() below. GetPipeNameForClient() is meant
|
||||
// to be used in the client. The returned path can only be used with
|
||||
// NtCreateFile() and not CreateFile().
|
||||
std::string GetPipeNameForAgent(const std::string& base, bool user_specific);
|
||||
std::string GetPipeNameForClient(const std::string& base, bool user_specific);
|
||||
|
||||
// Creates a named pipe with the give name. If `is_first_pipe` is true,
|
||||
// fail if this is not the first pipe using this name.
|
||||
//
|
||||
// This function create a pipe whose DACL allow full control to the creator
|
||||
// owner and administrators. If `user_specific` the DACL only allows the
|
||||
// logged on user to read from and write to the pipe. Otherwise anyone logged
|
||||
// in can read from and write to the pipe.
|
||||
//
|
||||
// A handle to the pipe is retuned in `handle`.
|
||||
DWORD CreatePipe(const std::string& name,
|
||||
bool user_specific,
|
||||
bool is_first_pipe,
|
||||
HANDLE* handle);
|
||||
|
||||
// Returns the full path to the main binary file of the process with the given
|
||||
// process ID.
|
||||
bool GetProcessPath(unsigned long pid, std::string* binary_path);
|
||||
|
||||
// A class that scopes the creation and destruction of an OVERLAPPED structure
|
||||
// used for async IO.
|
||||
class ScopedOverlapped {
|
||||
public:
|
||||
ScopedOverlapped();
|
||||
~ScopedOverlapped();
|
||||
|
||||
bool is_valid() { return overlapped_.hEvent != nullptr; }
|
||||
operator OVERLAPPED*() { return &overlapped_; }
|
||||
|
||||
private:
|
||||
OVERLAPPED overlapped_;
|
||||
};
|
||||
|
||||
} // internal
|
||||
} // namespace sdk
|
||||
} // namespace content_analysis
|
||||
|
||||
#endif // CONTENT_ANALYSIS_COMMON_UTILS_WIN_H_
|
||||
16
third_party/content_analysis_sdk/demo/README.md
vendored
Normal file
16
third_party/content_analysis_sdk/demo/README.md
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Google Chrome Content Analysis Connector Agent SDK Demo
|
||||
|
||||
This directory holds the Google Chrome Content Analysis Connector Agent SDK Demo.
|
||||
It contains an example of how to use the SDK.
|
||||
|
||||
Build instructions are available in the main project `README.md`.
|
||||
|
||||
## Demo agent permissions
|
||||
On Microsoft Windows, if the demo agent is run without the `--user` command line
|
||||
argument it must have Administrator privileges in order to properly create the
|
||||
pipe used to communicate with the browser. The demo browser must also be run
|
||||
without the `--user` command line argument.
|
||||
|
||||
Otherwise the agent may run as any user, with or without Administrator
|
||||
privileges. The demo browser must also be run with the `--user` command line
|
||||
argument and run as the same user.
|
||||
117
third_party/content_analysis_sdk/demo/agent.cc
vendored
Normal file
117
third_party/content_analysis_sdk/demo/agent.cc
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "content_analysis/sdk/analysis_agent.h"
|
||||
#include "demo/handler.h"
|
||||
|
||||
// Different paths are used depending on whether this agent should run as a
|
||||
// use specific agent or not. These values are chosen to match the test
|
||||
// values in chrome browser.
|
||||
constexpr char kPathUser[] = "path_user";
|
||||
constexpr char kPathSystem[] = "brcm_chrm_cas";
|
||||
|
||||
// Global app config.
|
||||
std::string path = kPathSystem;
|
||||
bool use_queue = false;
|
||||
bool user_specific = false;
|
||||
unsigned long delay = 0; // In seconds.
|
||||
unsigned long num_threads = 8u;
|
||||
std::string save_print_data_path = "";
|
||||
|
||||
// Command line parameters.
|
||||
constexpr const char* kArgDelaySpecific = "--delay=";
|
||||
constexpr const char* kArgPath = "--path=";
|
||||
constexpr const char* kArgQueued = "--queued";
|
||||
constexpr const char* kArgThreads = "--threads=";
|
||||
constexpr const char* kArgUserSpecific = "--user";
|
||||
constexpr const char* kArgHelp = "--help";
|
||||
constexpr const char* kArgSavePrintRequestDataTo = "--save-print-request-data-to=";
|
||||
|
||||
bool ParseCommandLine(int argc, char* argv[]) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
if (arg.find(kArgUserSpecific) == 0) {
|
||||
// If kArgPath was already used, abort.
|
||||
if (path != kPathSystem) {
|
||||
std::cout << std::endl << "ERROR: use --path=<path> after --user";
|
||||
return false;
|
||||
}
|
||||
path = kPathUser;
|
||||
user_specific = true;
|
||||
} else if (arg.find(kArgDelaySpecific) == 0) {
|
||||
delay = std::stoul(arg.substr(strlen(kArgDelaySpecific)));
|
||||
if (delay > 30) {
|
||||
delay = 30;
|
||||
}
|
||||
} else if (arg.find(kArgPath) == 0) {
|
||||
path = arg.substr(strlen(kArgPath));
|
||||
} else if (arg.find(kArgQueued) == 0) {
|
||||
use_queue = true;
|
||||
} else if (arg.find(kArgThreads) == 0) {
|
||||
num_threads = std::stoul(arg.substr(strlen(kArgThreads)));
|
||||
} else if (arg.find(kArgHelp) == 0) {
|
||||
return false;
|
||||
} else if (arg.find(kArgSavePrintRequestDataTo) == 0) {
|
||||
int arg_len = strlen(kArgSavePrintRequestDataTo);
|
||||
save_print_data_path = arg.substr(arg_len);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrintHelp() {
|
||||
std::cout
|
||||
<< std::endl << std::endl
|
||||
<< "Usage: agent [OPTIONS]" << std::endl
|
||||
<< "A simple agent to process content analysis requests." << std::endl
|
||||
<< "Data containing the string 'block' blocks the request data from being used." << std::endl
|
||||
<< std::endl << "Options:" << std::endl
|
||||
<< kArgDelaySpecific << "<delay> : Add a delay to request processing in seconds (max 30)." << std::endl
|
||||
<< kArgPath << " <path> : Used the specified path instead of default. Must come after --user." << std::endl
|
||||
<< kArgQueued << " : Queue requests for processing in a background thread" << std::endl
|
||||
<< kArgThreads << " : When queued, number of threads in the request processing thread pool" << std::endl
|
||||
<< kArgUserSpecific << " : Make agent OS user specific." << std::endl
|
||||
<< kArgHelp << " : prints this help message" << std::endl
|
||||
<< kArgSavePrintRequestDataTo << " : saves the PDF data to the given file path for print requests";
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (!ParseCommandLine(argc, argv)) {
|
||||
PrintHelp();
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto handler = use_queue
|
||||
? std::make_unique<QueuingHandler>(num_threads, delay, save_print_data_path)
|
||||
: std::make_unique<Handler>(delay, save_print_data_path);
|
||||
|
||||
// Each agent uses a unique name to identify itself with Google Chrome.
|
||||
content_analysis::sdk::ResultCode rc;
|
||||
auto agent = content_analysis::sdk::Agent::Create(
|
||||
{path, user_specific}, std::move(handler), &rc);
|
||||
if (!agent || rc != content_analysis::sdk::ResultCode::OK) {
|
||||
std::cout << "[Demo] Error starting agent: "
|
||||
<< content_analysis::sdk::ResultCodeToString(rc)
|
||||
<< std::endl;
|
||||
return 1;
|
||||
};
|
||||
|
||||
std::cout << "[Demo] " << agent->DebugString() << std::endl;
|
||||
|
||||
// Blocks, sending events to the handler until agent->Stop() is called.
|
||||
rc = agent->HandleEvents();
|
||||
if (rc != content_analysis::sdk::ResultCode::OK) {
|
||||
std::cout << "[Demo] Error from handling events: "
|
||||
<< content_analysis::sdk::ResultCodeToString(rc)
|
||||
<< std::endl;
|
||||
std::cout << "[Demo] " << agent->DebugString() << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
29
third_party/content_analysis_sdk/demo/atomic_output.h
vendored
Normal file
29
third_party/content_analysis_sdk/demo/atomic_output.h
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
// Utility class to atomically write outout to std::cout. All data streamed
|
||||
// the class is automatically sent to std::cout in the dtor. This is useful
|
||||
// to keep the output of multiple threads writing to std::Cout from
|
||||
// interleaving.
|
||||
|
||||
class AtomicCout {
|
||||
public:
|
||||
~AtomicCout() {
|
||||
flush();
|
||||
}
|
||||
|
||||
std::stringstream& stream() { return stream_; }
|
||||
|
||||
void flush() {
|
||||
std::cout << stream_.str();
|
||||
stream_.str(std::string());
|
||||
}
|
||||
|
||||
private:
|
||||
std::stringstream stream_;
|
||||
};
|
||||
411
third_party/content_analysis_sdk/demo/client.cc
vendored
Normal file
411
third_party/content_analysis_sdk/demo/client.cc
vendored
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "content_analysis/sdk/analysis_client.h"
|
||||
#include "demo/atomic_output.h"
|
||||
|
||||
using content_analysis::sdk::Client;
|
||||
using content_analysis::sdk::ContentAnalysisRequest;
|
||||
using content_analysis::sdk::ContentAnalysisResponse;
|
||||
using content_analysis::sdk::ContentAnalysisAcknowledgement;
|
||||
|
||||
// Different paths are used depending on whether this agent should run as a
|
||||
// use specific agent or not. These values are chosen to match the test
|
||||
// values in chrome browser.
|
||||
constexpr char kPathUser[] = "path_user";
|
||||
constexpr char kPathSystem[] = "brcm_chrm_cas";
|
||||
|
||||
// Global app config.
|
||||
std::string path = kPathSystem;
|
||||
bool user_specific = false;
|
||||
bool group = false;
|
||||
std::unique_ptr<Client> client;
|
||||
|
||||
// Paramters used to build the request.
|
||||
content_analysis::sdk::AnalysisConnector connector =
|
||||
content_analysis::sdk::FILE_ATTACHED;
|
||||
time_t request_token_number = time(nullptr);
|
||||
std::string request_token;
|
||||
std::string tag = "dlp";
|
||||
bool threaded = false;
|
||||
std::string digest = "sha256-123456";
|
||||
std::string url = "https://upload.example.com";
|
||||
std::string email = "me@example.com";
|
||||
std::string machine_user = "DOMAIN\\me";
|
||||
std::vector<std::string> datas;
|
||||
|
||||
// When grouping, remember the tokens of all requests/responses in order to
|
||||
// acknowledge them all with the same final action.
|
||||
//
|
||||
// This global state. It may be access from multiple thread so must be
|
||||
// accessed from a critical section.
|
||||
std::mutex global_mutex;
|
||||
ContentAnalysisAcknowledgement::FinalAction global_final_action =
|
||||
ContentAnalysisAcknowledgement::ALLOW;
|
||||
std::vector<std::string> request_tokens;
|
||||
|
||||
// Command line parameters.
|
||||
constexpr const char* kArgConnector = "--connector=";
|
||||
constexpr const char* kArgDigest = "--digest=";
|
||||
constexpr const char* kArgEmail = "--email=";
|
||||
constexpr const char* kArgGroup = "--group";
|
||||
constexpr const char* kArgMachineUser = "--machine-user=";
|
||||
constexpr const char* kArgPath = "--path=";
|
||||
constexpr const char* kArgRequestToken = "--request-token=";
|
||||
constexpr const char* kArgTag = "--tag=";
|
||||
constexpr const char* kArgThreaded = "--threaded";
|
||||
constexpr const char* kArgUrl = "--url=";
|
||||
constexpr const char* kArgUserSpecific = "--user";
|
||||
constexpr const char* kArgHelp = "--help";
|
||||
|
||||
bool ParseCommandLine(int argc, char* argv[]) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
if (arg.find(kArgConnector) == 0) {
|
||||
std::string connector_str = arg.substr(strlen(kArgConnector));
|
||||
if (connector_str == "download") {
|
||||
connector = content_analysis::sdk::FILE_DOWNLOADED;
|
||||
} else if (connector_str == "attach") {
|
||||
connector = content_analysis::sdk::FILE_ATTACHED;
|
||||
} else if (connector_str == "bulk-data-entry") {
|
||||
connector = content_analysis::sdk::BULK_DATA_ENTRY;
|
||||
} else if (connector_str == "print") {
|
||||
connector = content_analysis::sdk::PRINT;
|
||||
} else if (connector_str == "file-transfer") {
|
||||
connector = content_analysis::sdk::FILE_TRANSFER;
|
||||
} else {
|
||||
std::cout << "[Demo] Incorrect command line arg: " << arg << std::endl;
|
||||
return false;
|
||||
}
|
||||
} else if (arg.find(kArgRequestToken) == 0) {
|
||||
request_token = arg.substr(strlen(kArgRequestToken));
|
||||
} else if (arg.find(kArgTag) == 0) {
|
||||
tag = arg.substr(strlen(kArgTag));
|
||||
} else if (arg.find(kArgThreaded) == 0) {
|
||||
threaded = true;
|
||||
} else if (arg.find(kArgDigest) == 0) {
|
||||
digest = arg.substr(strlen(kArgDigest));
|
||||
} else if (arg.find(kArgUrl) == 0) {
|
||||
url = arg.substr(strlen(kArgUrl));
|
||||
} else if (arg.find(kArgMachineUser) == 0) {
|
||||
machine_user = arg.substr(strlen(kArgMachineUser));
|
||||
} else if (arg.find(kArgEmail) == 0) {
|
||||
email = arg.substr(strlen(kArgEmail));
|
||||
} else if (arg.find(kArgPath) == 0) {
|
||||
path = arg.substr(strlen(kArgPath));
|
||||
} else if (arg.find(kArgUserSpecific) == 0) {
|
||||
// If kArgPath was already used, abort.
|
||||
if (path != kPathSystem) {
|
||||
std::cout << std::endl << "ERROR: use --path=<path> after --user";
|
||||
return false;
|
||||
}
|
||||
path = kPathUser;
|
||||
user_specific = true;
|
||||
} else if (arg.find(kArgGroup) == 0) {
|
||||
group = true;
|
||||
} else if (arg.find(kArgHelp) == 0) {
|
||||
return false;
|
||||
} else {
|
||||
datas.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrintHelp() {
|
||||
std::cout
|
||||
<< std::endl << std::endl
|
||||
<< "Usage: client [OPTIONS] [@]content_or_file ..." << std::endl
|
||||
<< "A simple client to send content analysis requests to a running agent." << std::endl
|
||||
<< "Without @ the content to analyze is the argument itself." << std::endl
|
||||
<< "Otherwise the content is read from a file called 'content_or_file'." << std::endl
|
||||
<< "Multiple [@]content_or_file arguments may be specified, each generates one request." << std::endl
|
||||
<< std::endl << "Options:" << std::endl
|
||||
<< kArgConnector << "<connector> : one of 'download', 'attach' (default), 'bulk-data-entry', 'print', or 'file-transfer'" << std::endl
|
||||
<< kArgRequestToken << "<unique-token> : defaults to 'req-<number>' which auto increments" << std::endl
|
||||
<< kArgTag << "<tag> : defaults to 'dlp'" << std::endl
|
||||
<< kArgThreaded << " : handled multiple requests using threads" << std::endl
|
||||
<< kArgUrl << "<url> : defaults to 'https://upload.example.com'" << std::endl
|
||||
<< kArgMachineUser << "<machine-user> : defaults to 'DOMAIN\\me'" << std::endl
|
||||
<< kArgEmail << "<email> : defaults to 'me@example.com'" << std::endl
|
||||
<< kArgPath << " <path> : Used the specified path instead of default. Must come after --user." << std::endl
|
||||
<< kArgUserSpecific << " : Connects to an OS user specific agent" << std::endl
|
||||
<< kArgDigest << "<digest> : defaults to 'sha256-123456'" << std::endl
|
||||
<< kArgGroup << " : Generate the same final action for all requests" << std::endl
|
||||
<< kArgHelp << " : prints this help message" << std::endl;
|
||||
}
|
||||
|
||||
std::string GenerateRequestToken() {
|
||||
std::stringstream stm;
|
||||
stm << "req-" << request_token_number++;
|
||||
return stm.str();
|
||||
}
|
||||
|
||||
ContentAnalysisRequest BuildRequest(const std::string& data) {
|
||||
std::string filepath;
|
||||
std::string filename;
|
||||
if (data[0] == '@') {
|
||||
filepath = data.substr(1);
|
||||
filename = filepath.substr(filepath.find_last_of("/\\") + 1);
|
||||
}
|
||||
|
||||
ContentAnalysisRequest request;
|
||||
|
||||
// Set request to expire 5 minutes into the future.
|
||||
request.set_expires_at(time(nullptr) + 5 * 60);
|
||||
request.set_analysis_connector(connector);
|
||||
request.set_request_token(!request_token.empty()
|
||||
? request_token : GenerateRequestToken());
|
||||
*request.add_tags() = tag;
|
||||
|
||||
auto request_data = request.mutable_request_data();
|
||||
request_data->set_url(url);
|
||||
request_data->set_email(email);
|
||||
request_data->set_digest(digest);
|
||||
if (!filename.empty()) {
|
||||
request_data->set_filename(filename);
|
||||
}
|
||||
|
||||
auto client_metadata = request.mutable_client_metadata();
|
||||
auto browser = client_metadata->mutable_browser();
|
||||
browser->set_machine_user(machine_user);
|
||||
|
||||
if (!filepath.empty()) {
|
||||
request.set_file_path(filepath);
|
||||
} else if (!data.empty()) {
|
||||
request.set_text_content(data);
|
||||
} else {
|
||||
std::cout << "[Demo] Specify text content or a file path." << std::endl;
|
||||
PrintHelp();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
// Gets the most severe action within the result.
|
||||
ContentAnalysisResponse::Result::TriggeredRule::Action
|
||||
GetActionFromResult(const ContentAnalysisResponse::Result& result) {
|
||||
auto action =
|
||||
ContentAnalysisResponse::Result::TriggeredRule::ACTION_UNSPECIFIED;
|
||||
for (auto rule : result.triggered_rules()) {
|
||||
if (rule.has_action() && rule.action() > action)
|
||||
action = rule.action();
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
// Gets the most severe action within all the the results of a response.
|
||||
ContentAnalysisResponse::Result::TriggeredRule::Action
|
||||
GetActionFromResponse(const ContentAnalysisResponse& response) {
|
||||
auto action =
|
||||
ContentAnalysisResponse::Result::TriggeredRule::ACTION_UNSPECIFIED;
|
||||
for (auto result : response.results()) {
|
||||
auto action2 = GetActionFromResult(result);
|
||||
if (action2 > action)
|
||||
action = action2;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
void DumpResponse(
|
||||
std::stringstream& stream,
|
||||
const ContentAnalysisResponse& response) {
|
||||
for (auto result : response.results()) {
|
||||
auto tag = result.has_tag() ? result.tag() : "<no-tag>";
|
||||
|
||||
auto status = result.has_status()
|
||||
? result.status()
|
||||
: ContentAnalysisResponse::Result::STATUS_UNKNOWN;
|
||||
std::string status_str;
|
||||
switch (status) {
|
||||
case ContentAnalysisResponse::Result::STATUS_UNKNOWN:
|
||||
status_str = "Unknown";
|
||||
break;
|
||||
case ContentAnalysisResponse::Result::SUCCESS:
|
||||
status_str = "Success";
|
||||
break;
|
||||
case ContentAnalysisResponse::Result::FAILURE:
|
||||
status_str = "Failure";
|
||||
break;
|
||||
default:
|
||||
status_str = "<Uknown>";
|
||||
break;
|
||||
}
|
||||
|
||||
auto action = GetActionFromResult(result);
|
||||
std::string action_str;
|
||||
switch (action) {
|
||||
case ContentAnalysisResponse::Result::TriggeredRule::ACTION_UNSPECIFIED:
|
||||
action_str = "allowed";
|
||||
break;
|
||||
case ContentAnalysisResponse::Result::TriggeredRule::REPORT_ONLY:
|
||||
action_str = "reported only";
|
||||
break;
|
||||
case ContentAnalysisResponse::Result::TriggeredRule::WARN:
|
||||
action_str = "warned";
|
||||
break;
|
||||
case ContentAnalysisResponse::Result::TriggeredRule::BLOCK:
|
||||
action_str = "blocked";
|
||||
break;
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
stream << "[Demo] Request " << response.request_token() << " is " << action_str
|
||||
<< " after " << tag
|
||||
<< " analysis, status=" << status_str
|
||||
<< " at " << ctime(&now);
|
||||
}
|
||||
}
|
||||
|
||||
ContentAnalysisAcknowledgement BuildAcknowledgement(
|
||||
const std::string& request_token,
|
||||
ContentAnalysisAcknowledgement::FinalAction final_action) {
|
||||
ContentAnalysisAcknowledgement ack;
|
||||
ack.set_request_token(request_token);
|
||||
ack.set_status(ContentAnalysisAcknowledgement::SUCCESS);
|
||||
ack.set_final_action(final_action);
|
||||
return ack;
|
||||
}
|
||||
|
||||
void HandleRequest(const ContentAnalysisRequest& request) {
|
||||
AtomicCout aout;
|
||||
ContentAnalysisResponse response;
|
||||
int err = client->Send(request, &response);
|
||||
if (err != 0) {
|
||||
aout.stream() << "[Demo] Error sending request " << request.request_token()
|
||||
<< std::endl;
|
||||
} else if (response.results_size() == 0) {
|
||||
aout.stream() << "[Demo] Response " << request.request_token() << " is missing a result"
|
||||
<< std::endl;
|
||||
} else {
|
||||
DumpResponse(aout.stream(), response);
|
||||
|
||||
auto final_action = ContentAnalysisAcknowledgement::ALLOW;
|
||||
switch (GetActionFromResponse(response)) {
|
||||
case ContentAnalysisResponse::Result::TriggeredRule::ACTION_UNSPECIFIED:
|
||||
break;
|
||||
case ContentAnalysisResponse::Result::TriggeredRule::REPORT_ONLY:
|
||||
final_action = ContentAnalysisAcknowledgement::REPORT_ONLY;
|
||||
break;
|
||||
case ContentAnalysisResponse::Result::TriggeredRule::WARN:
|
||||
final_action = ContentAnalysisAcknowledgement::WARN;
|
||||
break;
|
||||
case ContentAnalysisResponse::Result::TriggeredRule::BLOCK:
|
||||
final_action = ContentAnalysisAcknowledgement::BLOCK;
|
||||
break;
|
||||
}
|
||||
|
||||
// If grouping, remember the request's token in order to ack the response
|
||||
// later.
|
||||
if (group) {
|
||||
std::unique_lock<std::mutex> lock(global_mutex);
|
||||
request_tokens.push_back(request.request_token());
|
||||
if (final_action > global_final_action)
|
||||
global_final_action = final_action;
|
||||
} else {
|
||||
int err = client->Acknowledge(
|
||||
BuildAcknowledgement(request.request_token(), final_action));
|
||||
if (err != 0) {
|
||||
aout.stream() << "[Demo] Error sending ack " << request.request_token()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessRequest(size_t i) {
|
||||
auto request = BuildRequest(datas[i]);
|
||||
|
||||
{
|
||||
AtomicCout aout;
|
||||
aout.stream() << "[Demo] Sending request " << request.request_token() << std::endl;
|
||||
}
|
||||
|
||||
HandleRequest(request);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (!ParseCommandLine(argc, argv)) {
|
||||
PrintHelp();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Each client uses a unique name to identify itself with Google Chrome.
|
||||
client = Client::Create({path, user_specific});
|
||||
if (!client) {
|
||||
std::cout << "[Demo] Error starting client" << std::endl;
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto info = client->GetAgentInfo();
|
||||
std::cout << "Agent pid=" << info.pid
|
||||
<< " path=" << info.binary_path << std::endl;
|
||||
|
||||
if (threaded) {
|
||||
std::vector<std::unique_ptr<std::thread>> threads;
|
||||
for (int i = 0; i < datas.size(); ++i) {
|
||||
AtomicCout aout;
|
||||
aout.stream() << "Start thread " << i << std::endl;
|
||||
threads.emplace_back(std::make_unique<std::thread>(ProcessRequest, i));
|
||||
}
|
||||
|
||||
// Make sure all threads have terminated.
|
||||
for (auto& thread : threads) {
|
||||
thread->join();
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (size_t i = 0; i < datas.size(); ++i) {
|
||||
ProcessRequest(i);
|
||||
}
|
||||
}
|
||||
// It's safe to access global state beyond this point without locking since
|
||||
// all no more responses will be touching them.
|
||||
|
||||
if (group) {
|
||||
std::cout << std::endl;
|
||||
std::cout << "[Demo] Final action for all requests is ";
|
||||
switch (global_final_action) {
|
||||
// Google Chrome fails open, so if no action is specified that is the same
|
||||
// as ALLOW.
|
||||
case ContentAnalysisAcknowledgement::ACTION_UNSPECIFIED:
|
||||
case ContentAnalysisAcknowledgement::ALLOW:
|
||||
std::cout << "allowed";
|
||||
break;
|
||||
case ContentAnalysisAcknowledgement::REPORT_ONLY:
|
||||
std::cout << "reported only";
|
||||
break;
|
||||
case ContentAnalysisAcknowledgement::WARN:
|
||||
std::cout << "warned";
|
||||
break;
|
||||
case ContentAnalysisAcknowledgement::BLOCK:
|
||||
std::cout << "blocked";
|
||||
break;
|
||||
}
|
||||
std::cout << std::endl << std::endl;
|
||||
|
||||
for (auto token : request_tokens) {
|
||||
std::cout << "[Demo] Sending group Ack" << std::endl;
|
||||
int err = client->Acknowledge(
|
||||
BuildAcknowledgement(token, global_final_action));
|
||||
if (err != 0) {
|
||||
std::cout << "[Demo] Error sending ack for " << token << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
384
third_party/content_analysis_sdk/demo/handler.h
vendored
Normal file
384
third_party/content_analysis_sdk/demo/handler.h
vendored
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_DEMO_HANDLER_H_
|
||||
#define CONTENT_ANALYSIS_DEMO_HANDLER_H_
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "content_analysis/sdk/analysis_agent.h"
|
||||
#include "demo/atomic_output.h"
|
||||
#include "demo/request_queue.h"
|
||||
|
||||
// An AgentEventHandler that dumps requests information to stdout and blocks
|
||||
// any requests that have the keyword "block" in their data
|
||||
class Handler : public content_analysis::sdk::AgentEventHandler {
|
||||
public:
|
||||
using Event = content_analysis::sdk::ContentAnalysisEvent;
|
||||
|
||||
Handler(unsigned long delay, const std::string& print_data_file_path) :
|
||||
delay_(delay), print_data_file_path_(print_data_file_path) {
|
||||
}
|
||||
|
||||
unsigned long delay() { return delay_; }
|
||||
|
||||
protected:
|
||||
// Analyzes one request from Google Chrome and responds back to the browser
|
||||
// with either an allow or block verdict.
|
||||
void AnalyzeContent(std::stringstream& stream, std::unique_ptr<Event> event) {
|
||||
// An event represents one content analysis request and response triggered
|
||||
// by a user action in Google Chrome. The agent determines whether the
|
||||
// user is allowed to perform the action by examining event->GetRequest().
|
||||
// The verdict, which can be "allow" or "block" is written into
|
||||
// event->GetResponse().
|
||||
|
||||
DumpEvent(stream, event.get());
|
||||
|
||||
bool block = false;
|
||||
bool success = true;
|
||||
|
||||
if (event->GetRequest().has_text_content()) {
|
||||
block = ShouldBlockRequest(
|
||||
event->GetRequest().text_content());
|
||||
} else if (event->GetRequest().has_file_path()) {
|
||||
std::string content;
|
||||
success =
|
||||
ReadContentFromFile(event->GetRequest().file_path(),
|
||||
&content);
|
||||
if (success) {
|
||||
block = ShouldBlockRequest(content);
|
||||
}
|
||||
} else if (event->GetRequest().has_print_data()) {
|
||||
// In the case of print request, normally the PDF bytes would be parsed
|
||||
// for sensitive data violations. To keep this class simple, only the
|
||||
// URL is checked for the word "block".
|
||||
block = ShouldBlockRequest(event->GetRequest().request_data().url());
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
content_analysis::sdk::UpdateResponse(
|
||||
event->GetResponse(),
|
||||
std::string(),
|
||||
content_analysis::sdk::ContentAnalysisResponse::Result::FAILURE);
|
||||
stream << " Verdict: failed to reach verdict: ";
|
||||
stream << event->DebugString() << std::endl;
|
||||
} else if (block) {
|
||||
auto rc = content_analysis::sdk::SetEventVerdictToBlock(event.get());
|
||||
stream << " Verdict: block";
|
||||
if (rc != content_analysis::sdk::ResultCode::OK) {
|
||||
stream << " error: "
|
||||
<< content_analysis::sdk::ResultCodeToString(rc) << std::endl;
|
||||
stream << " " << event->DebugString() << std::endl;
|
||||
}
|
||||
stream << std::endl;
|
||||
} else {
|
||||
stream << " Verdict: allow" << std::endl;
|
||||
}
|
||||
|
||||
stream << std::endl;
|
||||
|
||||
// If a delay is specified, wait that much.
|
||||
if (delay_ > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(delay_));
|
||||
}
|
||||
|
||||
// Send the response back to Google Chrome.
|
||||
auto rc = event->Send();
|
||||
if (rc != content_analysis::sdk::ResultCode::OK) {
|
||||
stream << "[Demo] Error sending response: "
|
||||
<< content_analysis::sdk::ResultCodeToString(rc)
|
||||
<< std::endl;
|
||||
stream << event->DebugString() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void OnBrowserConnected(
|
||||
const content_analysis::sdk::BrowserInfo& info) override {
|
||||
AtomicCout aout;
|
||||
aout.stream() << std::endl << "==========" << std::endl;
|
||||
aout.stream() << "Browser connected pid=" << info.pid
|
||||
<< " path=" << info.binary_path << std::endl;
|
||||
}
|
||||
|
||||
void OnBrowserDisconnected(
|
||||
const content_analysis::sdk::BrowserInfo& info) override {
|
||||
AtomicCout aout;
|
||||
aout.stream() << std::endl << "Browser disconnected pid=" << info.pid << std::endl;
|
||||
aout.stream() << "==========" << std::endl;
|
||||
}
|
||||
|
||||
void OnAnalysisRequested(std::unique_ptr<Event> event) override {
|
||||
// If the agent is capable of analyzing content in the background, the
|
||||
// events may be handled in background threads. Having said that, a
|
||||
// event should not be assumed to be thread safe, that is, it should not
|
||||
// be accessed by more than one thread concurrently.
|
||||
//
|
||||
// In this example code, the event is handled synchronously.
|
||||
AtomicCout aout;
|
||||
aout.stream() << std::endl << "----------" << std::endl << std::endl;
|
||||
AnalyzeContent(aout.stream(), std::move(event));
|
||||
}
|
||||
|
||||
void OnResponseAcknowledged(
|
||||
const content_analysis::sdk::ContentAnalysisAcknowledgement&
|
||||
ack) override {
|
||||
const char* final_action = "<Unknown>";
|
||||
if (ack.has_final_action()) {
|
||||
switch (ack.final_action()) {
|
||||
case content_analysis::sdk::ContentAnalysisAcknowledgement::ACTION_UNSPECIFIED:
|
||||
final_action = "<Unspecified>";
|
||||
break;
|
||||
case content_analysis::sdk::ContentAnalysisAcknowledgement::ALLOW:
|
||||
final_action = "Allow";
|
||||
break;
|
||||
case content_analysis::sdk::ContentAnalysisAcknowledgement::REPORT_ONLY:
|
||||
final_action = "Report only";
|
||||
break;
|
||||
case content_analysis::sdk::ContentAnalysisAcknowledgement::WARN:
|
||||
final_action = "Warn";
|
||||
break;
|
||||
case content_analysis::sdk::ContentAnalysisAcknowledgement::BLOCK:
|
||||
final_action = "Block";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AtomicCout aout;
|
||||
aout.stream() << "Ack: " << ack.request_token() << std::endl;
|
||||
aout.stream() << " Final action: " << final_action << std::endl;
|
||||
}
|
||||
void OnCancelRequests(
|
||||
const content_analysis::sdk::ContentAnalysisCancelRequests& cancel)
|
||||
override {
|
||||
AtomicCout aout;
|
||||
aout.stream() << "Cancel: " << std::endl;
|
||||
aout.stream() << " User action ID: " << cancel.user_action_id() << std::endl;
|
||||
}
|
||||
|
||||
void OnInternalError(
|
||||
const char* context,
|
||||
content_analysis::sdk::ResultCode error) override {
|
||||
AtomicCout aout;
|
||||
aout.stream() << std::endl
|
||||
<< "*ERROR*: context=\"" << context << "\" "
|
||||
<< content_analysis::sdk::ResultCodeToString(error)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void DumpEvent(std::stringstream& stream, Event* event) {
|
||||
time_t now = time(nullptr);
|
||||
stream << "Received at: " << ctime(&now); // Returned string includes \n.
|
||||
|
||||
const content_analysis::sdk::ContentAnalysisRequest& request =
|
||||
event->GetRequest();
|
||||
std::string connector = "<Unknown>";
|
||||
if (request.has_analysis_connector()) {
|
||||
switch (request.analysis_connector())
|
||||
{
|
||||
case content_analysis::sdk::FILE_DOWNLOADED:
|
||||
connector = "download";
|
||||
break;
|
||||
case content_analysis::sdk::FILE_ATTACHED:
|
||||
connector = "attach";
|
||||
break;
|
||||
case content_analysis::sdk::BULK_DATA_ENTRY:
|
||||
connector = "bulk-data-entry";
|
||||
break;
|
||||
case content_analysis::sdk::PRINT:
|
||||
connector = "print";
|
||||
break;
|
||||
case content_analysis::sdk::FILE_TRANSFER:
|
||||
connector = "file-transfer";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string url =
|
||||
request.has_request_data() && request.request_data().has_url()
|
||||
? request.request_data().url() : "<No URL>";
|
||||
|
||||
std::string tab_title =
|
||||
request.has_request_data() && request.request_data().has_tab_title()
|
||||
? request.request_data().tab_title() : "<No tab title>";
|
||||
|
||||
std::string filename =
|
||||
request.has_request_data() && request.request_data().has_filename()
|
||||
? request.request_data().filename() : "<No filename>";
|
||||
|
||||
std::string digest =
|
||||
request.has_request_data() && request.request_data().has_digest()
|
||||
? request.request_data().digest() : "<No digest>";
|
||||
|
||||
std::string file_path =
|
||||
request.has_file_path()
|
||||
? request.file_path() : "None, bulk text entry or print";
|
||||
|
||||
std::string machine_user =
|
||||
request.has_client_metadata() &&
|
||||
request.client_metadata().has_browser() &&
|
||||
request.client_metadata().browser().has_machine_user()
|
||||
? request.client_metadata().browser().machine_user() : "<No machine user>";
|
||||
|
||||
std::string email =
|
||||
request.has_request_data() && request.request_data().has_email()
|
||||
? request.request_data().email() : "<No email>";
|
||||
|
||||
time_t t = request.expires_at();
|
||||
std::string expires_at_str = ctime(&t);
|
||||
// Returned string includes trailing \n, overwrite with null.
|
||||
expires_at_str[expires_at_str.size() - 1] = 0;
|
||||
time_t secs_remaining = t - now;
|
||||
|
||||
std::string user_action_id = request.has_user_action_id()
|
||||
? request.user_action_id() : "<No user action id>";
|
||||
|
||||
stream << "Request: " << request.request_token() << std::endl;
|
||||
stream << " User action ID: " << user_action_id << std::endl;
|
||||
stream << " Expires at: " << expires_at_str << " ("
|
||||
<< secs_remaining << " seconds from now)" << std::endl;
|
||||
stream << " Connector: " << connector << std::endl;
|
||||
stream << " URL: " << url << std::endl;
|
||||
stream << " Tab title: " << tab_title << std::endl;
|
||||
stream << " Filename: " << filename << std::endl;
|
||||
stream << " Digest: " << digest << std::endl;
|
||||
stream << " Filepath: " << file_path << std::endl;
|
||||
stream << " Machine user: " << machine_user << std::endl;
|
||||
stream << " Email: " << email << std::endl;
|
||||
if (request.has_print_data() && !print_data_file_path_.empty()) {
|
||||
if (request.request_data().has_print_metadata() &&
|
||||
request.request_data().print_metadata().has_printer_name()) {
|
||||
stream << " Printer name: "
|
||||
<< request.request_data().print_metadata().printer_name()
|
||||
<< std::endl;
|
||||
} else {
|
||||
stream << " No printer name in request" << std::endl;
|
||||
}
|
||||
|
||||
stream << " Print data saved to: " << print_data_file_path_
|
||||
<< std::endl;
|
||||
using content_analysis::sdk::ContentAnalysisEvent;
|
||||
auto print_data =
|
||||
content_analysis::sdk::CreateScopedPrintHandle(event->GetRequest(),
|
||||
event->GetBrowserInfo().pid);
|
||||
std::ofstream file(print_data_file_path_,
|
||||
std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
file.write(print_data->data(), print_data->size());
|
||||
file.flush();
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadContentFromFile(const std::string& file_path,
|
||||
std::string* content) {
|
||||
std::ifstream file(file_path,
|
||||
std::ios::in | std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open())
|
||||
return false;
|
||||
|
||||
// Get file size. This example does not handle files larger than 1MB.
|
||||
// Make sure content string can hold the contents of the file.
|
||||
int size = file.tellg();
|
||||
if (size > 1024 * 1024)
|
||||
return false;
|
||||
|
||||
content->resize(size + 1);
|
||||
|
||||
// Read file into string.
|
||||
file.seekg(0, std::ios::beg);
|
||||
file.read(&(*content)[0], size);
|
||||
content->at(size) = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShouldBlockRequest(const std::string& content) {
|
||||
// Determines if the request should be blocked. For this simple example
|
||||
// the content is blocked if the string "block" is found. Otherwise the
|
||||
// content is allowed.
|
||||
return content.find("block") != std::string::npos;
|
||||
}
|
||||
|
||||
unsigned long delay_;
|
||||
std::string print_data_file_path_;
|
||||
};
|
||||
|
||||
// An AgentEventHandler that dumps requests information to stdout and blocks
|
||||
// any requests that have the keyword "block" in their data
|
||||
class QueuingHandler : public Handler {
|
||||
public:
|
||||
QueuingHandler(unsigned long threads, unsigned long delay, const std::string& print_data_file_path)
|
||||
: Handler(delay, print_data_file_path) {
|
||||
StartBackgroundThreads(threads);
|
||||
}
|
||||
|
||||
~QueuingHandler() override {
|
||||
// Abort background process and wait for it to finish.
|
||||
request_queue_.abort();
|
||||
WaitForBackgroundThread();
|
||||
}
|
||||
|
||||
private:
|
||||
void OnAnalysisRequested(std::unique_ptr<Event> event) override {
|
||||
{
|
||||
time_t now = time(nullptr);
|
||||
const content_analysis::sdk::ContentAnalysisRequest& request =
|
||||
event->GetRequest();
|
||||
AtomicCout aout;
|
||||
aout.stream() << std::endl << "Queuing request: " << request.request_token()
|
||||
<< " at " << ctime(&now) << std::endl;
|
||||
}
|
||||
|
||||
request_queue_.push(std::move(event));
|
||||
}
|
||||
|
||||
static void* ProcessRequests(void* qh) {
|
||||
QueuingHandler* handler = reinterpret_cast<QueuingHandler*>(qh);
|
||||
|
||||
while (true) {
|
||||
auto event = handler->request_queue_.pop();
|
||||
if (!event)
|
||||
break;
|
||||
|
||||
AtomicCout aout;
|
||||
aout.stream() << std::endl << "----------" << std::endl;
|
||||
aout.stream() << "Thread: " << std::this_thread::get_id() << std::endl;
|
||||
aout.stream() << "Delaying request processing for "
|
||||
<< handler->delay() << "s" << std::endl << std::endl;
|
||||
aout.flush();
|
||||
|
||||
handler->AnalyzeContent(aout.stream(), std::move(event));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// A list of outstanding content analysis requests.
|
||||
RequestQueue request_queue_;
|
||||
|
||||
void StartBackgroundThreads(unsigned long threads) {
|
||||
threads_.reserve(threads);
|
||||
for (unsigned long i = 0; i < threads; ++i) {
|
||||
threads_.emplace_back(std::make_unique<std::thread>(ProcessRequests, this));
|
||||
}
|
||||
}
|
||||
|
||||
void WaitForBackgroundThread() {
|
||||
for (auto& thread : threads_) {
|
||||
thread->join();
|
||||
}
|
||||
}
|
||||
|
||||
// Thread id of backgrond thread.
|
||||
std::vector<std::unique_ptr<std::thread>> threads_;
|
||||
};
|
||||
|
||||
#endif // CONTENT_ANALYSIS_DEMO_HANDLER_H_
|
||||
70
third_party/content_analysis_sdk/demo/request_queue.h
vendored
Normal file
70
third_party/content_analysis_sdk/demo/request_queue.h
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_ANALYSIS_DEMO_REQUST_QUEUE_H_
|
||||
#define CONTENT_ANALYSIS_DEMO_REQUST_QUEUE_H_
|
||||
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
#include "content_analysis/sdk/analysis_agent.h"
|
||||
|
||||
// This class maintains a list of outstanding content analysis requests to
|
||||
// process. Each request is encapsulated in one ContentAnalysisEvent.
|
||||
// Requests are handled in FIFO order.
|
||||
class RequestQueue {
|
||||
public:
|
||||
using Event = content_analysis::sdk::ContentAnalysisEvent;
|
||||
|
||||
RequestQueue() = default;
|
||||
virtual ~RequestQueue() = default;
|
||||
|
||||
// Push a new content analysis event into the queue.
|
||||
void push(std::unique_ptr<Event> event) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
events_.push(std::move(event));
|
||||
|
||||
// Wake before leaving to prevent unpredicatable scheduling.
|
||||
cv_.notify_one();
|
||||
}
|
||||
|
||||
// Pop the next request from the queue, blocking if necessary until an event
|
||||
// is available. Returns a nullptr if the queue will produce no more
|
||||
// events.
|
||||
std::unique_ptr<Event> pop() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
while (!abort_ && events_.size() == 0)
|
||||
cv_.wait(lock);
|
||||
|
||||
std::unique_ptr<Event> event;
|
||||
if (!abort_) {
|
||||
event = std::move(events_.front());
|
||||
events_.pop();
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
// Marks the queue as aborted. pop() will now return nullptr.
|
||||
void abort() {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
|
||||
abort_ = true;
|
||||
|
||||
// Wake before leaving to prevent unpredicatable scheduling.
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<std::unique_ptr<Event>> events_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cv_;
|
||||
bool abort_ = false;
|
||||
};
|
||||
|
||||
#endif // CONTENT_ANALYSIS_DEMO_REQUST_QUEUE_H_
|
||||
94
third_party/content_analysis_sdk/docs/code-of-conduct.md
vendored
Normal file
94
third_party/content_analysis_sdk/docs/code-of-conduct.md
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of
|
||||
experience, education, socio-economic status, nationality, personal appearance,
|
||||
race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, or to ban temporarily or permanently any
|
||||
contributor for other behaviors that they deem inappropriate, threatening,
|
||||
offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
This Code of Conduct also applies outside the project spaces when the Project
|
||||
Steward has a reasonable belief that an individual's behavior may have a
|
||||
negative impact on the project or its community.
|
||||
|
||||
## Conflict Resolution
|
||||
|
||||
We do not believe that all conflict is bad; healthy debate and disagreement
|
||||
often yield positive results. However, it is never okay to be disrespectful or
|
||||
to engage in behavior that violates the project’s code of conduct.
|
||||
|
||||
If you see someone violating the code of conduct, you are encouraged to address
|
||||
the behavior directly with those involved. Many issues can be resolved quickly
|
||||
and easily, and this gives people more control over the outcome of their
|
||||
dispute. If you are unable to resolve the matter for any reason, or if the
|
||||
behavior is threatening or harassing, report it. We are dedicated to providing
|
||||
an environment where participants feel welcome and safe.
|
||||
|
||||
Reports should be directed to *community@chromium.org*, the
|
||||
Project Steward(s) for *content_analysis_sdk*. It is the Project Steward’s duty to
|
||||
receive and address reported violations of the code of conduct. They will then
|
||||
work with a committee consisting of representatives from the Open Source
|
||||
Programs Office and the Google Open Source Strategy team. If for any reason you
|
||||
are uncomfortable reaching out to the Project Steward, please email
|
||||
opensource@google.com.
|
||||
|
||||
We will investigate every complaint, but you may not receive a direct response.
|
||||
We will use our discretion in determining when and how to follow up on reported
|
||||
incidents, which may range from not taking action to permanent expulsion from
|
||||
the project and project-sponsored spaces. We will notify the accused of the
|
||||
report and provide them an opportunity to discuss it before any action is taken.
|
||||
The identity of the reporter will be omitted from the details of the report
|
||||
supplied to the accused. In potentially harmful situations, such as ongoing
|
||||
harassment or threats to anyone's safety, we may take action without notice.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
|
||||
available at
|
||||
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
29
third_party/content_analysis_sdk/docs/contributing.md
vendored
Normal file
29
third_party/content_analysis_sdk/docs/contributing.md
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# How to Contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
just a few small guidelines you need to follow.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement. You (or your employer) retain the copyright to your contribution;
|
||||
this simply gives us permission to use and redistribute your contributions as
|
||||
part of the project. Head over to <https://cla.developers.google.com/> to see
|
||||
your current agreements on file or to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code Reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
|
||||
## Community Guidelines
|
||||
|
||||
This project follows [Google's Open Source Community
|
||||
Guidelines](https://opensource.google/conduct/).
|
||||
|
||||
48
third_party/content_analysis_sdk/prepare_build
vendored
Normal file
48
third_party/content_analysis_sdk/prepare_build
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2022 The Chromium Authors.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
# This script is meant to be run once to setup the example demo agent.
|
||||
# Run it with one command line argument: the path to a directory where the
|
||||
# demo agent will be built. This should be a directory outside the SDK
|
||||
# directory tree. By default, if no directory is supplied, a directory
|
||||
# named `build` in the project root will be used.
|
||||
#
|
||||
# Once the build is prepared, the demo binary is built using the command
|
||||
# `cmake --build <build-dir>`, where <build-dir> is the same argument given
|
||||
# to this script.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export ROOT_DIR=$(realpath $(dirname $0))
|
||||
export DEMO_DIR=$(realpath $ROOT_DIR/demo)
|
||||
export PROTO_DIR=$(realpath $ROOT_DIR/proto)
|
||||
# Defaults to $ROOT_DIR/build if no argument is provided.
|
||||
export BUILD_DIR=$(realpath ${1:-$ROOT_DIR/build})
|
||||
|
||||
echo Root dir: $ROOT_DIR
|
||||
echo Build dir: $BUILD_DIR
|
||||
echo Demo dir: $DEMO_DIR
|
||||
echo Proto dir: $PROTO_DIR
|
||||
|
||||
# Prepare build directory
|
||||
mkdir -p $BUILD_DIR
|
||||
# Prepare protobuf out directory
|
||||
mkdir -p $BUILD_DIR/gen
|
||||
# Enter build directory
|
||||
cd $BUILD_DIR
|
||||
|
||||
# Install vcpkg and use it to install Google Protocol Buffers.
|
||||
test -d vcpkg || (
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
./vcpkg/bootstrap-vcpkg.sh -disableMetrics
|
||||
)
|
||||
# Install any packages we want from vcpkg.
|
||||
./vcpkg/vcpkg install protobuf
|
||||
./vcpkg/vcpkg install gtest
|
||||
|
||||
# Generate the build files.
|
||||
export CMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake
|
||||
cmake $ROOT_DIR
|
||||
|
||||
68
third_party/content_analysis_sdk/prepare_build.bat
vendored
Normal file
68
third_party/content_analysis_sdk/prepare_build.bat
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
REM Copyright 2022 The Chromium Authors.
|
||||
REM Use of this source code is governed by a BSD-style license that can be
|
||||
REM found in the LICENSE file.
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
REM This script is meant to be run once to setup the example demo agent.
|
||||
REM Run it with one command line argument: the path to a directory where the
|
||||
REM demo agent will be built. This should be a directory outside the SDK
|
||||
REM directory tree. By default, if no directory is supplied, a directory
|
||||
REM named `build` in the project root will be used.
|
||||
REM
|
||||
REM Once the build is prepared, the demo binary is built using the command
|
||||
REM `cmake --build <build-dir>`, where <build-dir> is the same argument given
|
||||
REM to this script.
|
||||
|
||||
set ROOT_DIR=%~dp0
|
||||
call :ABSPATH "%ROOT_DIR%\demo" DEMO_DIR
|
||||
call :ABSPATH "%ROOT_DIR%\proto" PROTO_DIR
|
||||
|
||||
REM BUILD_DIR defaults to $ROOT_DIR/build if no argument is provided.
|
||||
IF "%1" == "" (
|
||||
call :ABSPATH "%ROOT_DIR%\build" BUILD_DIR
|
||||
) ELSE (
|
||||
set BUILD_DIR=%~f1
|
||||
)
|
||||
|
||||
echo .
|
||||
echo Root dir: %ROOT_DIR%
|
||||
echo Build dir: %BUILD_DIR%
|
||||
echo Demo dir: %DEMO_DIR%
|
||||
echo Proto dir: %PROTO_DIR%
|
||||
echo .
|
||||
|
||||
REM Prepare build directory
|
||||
mkdir "%BUILD_DIR%"
|
||||
REM Prepare protobuf out directory
|
||||
mkdir "%BUILD_DIR%\gen"
|
||||
REM Enter build directory
|
||||
cd /d "%BUILD_DIR%"
|
||||
|
||||
REM Install vcpkg and use it to install Google Protocol Buffers.
|
||||
IF NOT exist .\vcpkg\ (
|
||||
cmd/c git clone https://github.com/microsoft/vcpkg
|
||||
cmd/c .\vcpkg\bootstrap-vcpkg.bat -disableMetrics
|
||||
) ELSE (
|
||||
echo vcpkg is already installed.
|
||||
)
|
||||
REM Install any packages we want from vcpkg.
|
||||
cmd/c .\vcpkg\vcpkg install protobuf:x64-windows
|
||||
cmd/c .\vcpkg\vcpkg install gtest:x64-windows
|
||||
|
||||
REM Generate the build files.
|
||||
set CMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake
|
||||
cmake %ROOT_DIR%
|
||||
|
||||
echo.
|
||||
echo.
|
||||
echo To build, type: cmake --build "%BUILD_DIR%"
|
||||
echo.
|
||||
|
||||
exit /b
|
||||
|
||||
REM Resolve relative path in %1 and set it into variable %2.
|
||||
:ABSPATH
|
||||
set %2=%~f1
|
||||
exit /b
|
||||
|
||||
255
third_party/content_analysis_sdk/proto/content_analysis/sdk/analysis.proto
vendored
Normal file
255
third_party/content_analysis_sdk/proto/content_analysis/sdk/analysis.proto
vendored
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
// Copyright 2022 The Chromium Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
package content_analysis.sdk;
|
||||
|
||||
// The values in this enum can be extended in future versions of Chrome to
|
||||
// support new analysis triggers.
|
||||
enum AnalysisConnector {
|
||||
ANALYSIS_CONNECTOR_UNSPECIFIED = 0;
|
||||
FILE_DOWNLOADED = 1;
|
||||
FILE_ATTACHED = 2;
|
||||
BULK_DATA_ENTRY = 3;
|
||||
PRINT = 4;
|
||||
// This value is not yet implemented in the SDK. It is kept for consistency with the Chromium code.
|
||||
FILE_TRANSFER = 5;
|
||||
}
|
||||
|
||||
message ContentMetaData {
|
||||
// The URL containing the file download/upload or to which web content is
|
||||
// being uploaded.
|
||||
optional string url = 1;
|
||||
|
||||
// Name of file on user system (if applicable).
|
||||
optional string filename = 2;
|
||||
|
||||
// Sha256 digest of file.
|
||||
optional string digest = 3;
|
||||
|
||||
// Specifically for the download case.
|
||||
optional ClientDownloadRequest csd = 4;
|
||||
|
||||
// Optional email address of user. This field may be empty if the user
|
||||
// is not signed in.
|
||||
optional string email = 5;
|
||||
|
||||
// Name of tab title.
|
||||
optional string tab_title = 9;
|
||||
|
||||
// Empty for non-print actions.
|
||||
message PrintMetadata {
|
||||
optional string printer_name = 1;
|
||||
|
||||
enum PrinterType {
|
||||
UNKNOWN = 0;
|
||||
CLOUD = 1;
|
||||
LOCAL = 2;
|
||||
}
|
||||
optional PrinterType printer_type = 2;
|
||||
}
|
||||
optional PrintMetadata print_metadata = 11;
|
||||
|
||||
reserved 6 to 8, 10;
|
||||
}
|
||||
|
||||
message ClientMetadata {
|
||||
// Describes the browser uploading a scan request.
|
||||
message Browser {
|
||||
// This is omitted on scans triggered at the profile level.
|
||||
optional string machine_user = 4;
|
||||
|
||||
reserved 1 to 3;
|
||||
};
|
||||
optional Browser browser = 1;
|
||||
|
||||
reserved 2 to 3;
|
||||
};
|
||||
|
||||
message ClientDownloadRequest {
|
||||
// Type of the resources stored below.
|
||||
enum ResourceType {
|
||||
// The final URL of the download payload. The resource URL should
|
||||
// correspond to the URL field above.
|
||||
DOWNLOAD_URL = 0;
|
||||
// A redirect URL that was fetched before hitting the final DOWNLOAD_URL.
|
||||
DOWNLOAD_REDIRECT = 1;
|
||||
// The final top-level URL of the tab that triggered the download.
|
||||
TAB_URL = 2;
|
||||
// A redirect URL thas was fetched before hitting the final TAB_URL.
|
||||
TAB_REDIRECT = 3;
|
||||
// The document URL for a PPAPI plugin instance that initiated the download.
|
||||
// This is the document.url for the container element for the plugin
|
||||
// instance.
|
||||
PPAPI_DOCUMENT = 4;
|
||||
// The plugin URL for a PPAPI plugin instance that initiated the download.
|
||||
PPAPI_PLUGIN = 5;
|
||||
}
|
||||
|
||||
message Resource {
|
||||
required string url = 1;
|
||||
required ResourceType type = 2;
|
||||
|
||||
reserved 3 to 4;
|
||||
}
|
||||
|
||||
repeated Resource resources = 4;
|
||||
|
||||
reserved 1 to 3, 5 to 84;
|
||||
}
|
||||
|
||||
|
||||
// Analysis request sent from chrome to backend.
|
||||
// The proto in the Chromium codebase is the source of truth, the version here
|
||||
// should always be in sync with it (https://osscs.corp.google.com/chromium/chromium/src/+/main:components/enterprise/common/proto/connectors.proto;l=87;drc=a8fb6888aff535f27654f03cd1643868ba066de9).
|
||||
message ContentAnalysisRequest {
|
||||
// Token used to correlate requests and responses. This is different than the
|
||||
// FCM token in that it is unique for each request.
|
||||
optional string request_token = 5;
|
||||
|
||||
// Which enterprise connector fired this request.
|
||||
optional AnalysisConnector analysis_connector = 9;
|
||||
|
||||
// Information about the data that triggered the content analysis request.
|
||||
optional ContentMetaData request_data = 10;
|
||||
|
||||
// The tags configured for the URL that triggered the content analysis.
|
||||
repeated string tags = 11;
|
||||
|
||||
// Additional information about the browser, device or profile so events can
|
||||
// be reported with device/user identifiable information.
|
||||
optional ClientMetadata client_metadata = 12;
|
||||
|
||||
// Data used to transmit print data from the browser.
|
||||
message PrintData {
|
||||
// A platform-specific handle that can be used to access the printed document.
|
||||
optional int64 handle = 1;
|
||||
|
||||
// The size of the data to be printed.
|
||||
optional int64 size = 2;
|
||||
}
|
||||
|
||||
oneof content_data {
|
||||
// The text content to analyze in local content analysis request.
|
||||
string text_content = 13;
|
||||
|
||||
// The full path to the file to analyze in local content analysis request.
|
||||
// The path is expressed in a platform dependent way.
|
||||
string file_path = 14;
|
||||
|
||||
// The to-be-printed page/document in PDF format.
|
||||
PrintData print_data = 18;
|
||||
}
|
||||
|
||||
// The absolute deadline (seconds since the UTC Epoch time) that Chrome will
|
||||
// wait until a response from the agent is received.
|
||||
optional int64 expires_at = 15;
|
||||
|
||||
// ID for keeping track of analysis requests that belong to the same user
|
||||
// action.
|
||||
optional string user_action_id = 16;
|
||||
|
||||
// Count of analysis requests that belong to the same user action.
|
||||
optional int64 user_action_requests_count = 17;
|
||||
|
||||
// Reserved to make sure there is no overlap with DeepScanningClientRequest.
|
||||
reserved 1 to 4, 6 to 8;
|
||||
}
|
||||
|
||||
// Verdict response sent from agent to Google Chrome.
|
||||
message ContentAnalysisResponse {
|
||||
// Token used to correlate requests and responses. Corresponds to field in
|
||||
// ContentAnalysisRequest with the same name.
|
||||
optional string request_token = 1;
|
||||
|
||||
// Represents the analysis result from a given tag.
|
||||
message Result {
|
||||
optional string tag = 1;
|
||||
|
||||
// The status of this result.
|
||||
enum Status {
|
||||
STATUS_UNKNOWN = 0;
|
||||
SUCCESS = 1;
|
||||
FAILURE = 2;
|
||||
}
|
||||
optional Status status = 2;
|
||||
|
||||
// Identifies the detection rules that were triggered by the analysis.
|
||||
// Only relevant when status is SUCCESS.
|
||||
message TriggeredRule {
|
||||
enum Action {
|
||||
ACTION_UNSPECIFIED = 0;
|
||||
REPORT_ONLY = 1;
|
||||
WARN = 2;
|
||||
BLOCK = 3;
|
||||
}
|
||||
optional Action action = 1;
|
||||
optional string rule_name = 2;
|
||||
optional string rule_id = 3;
|
||||
reserved 4;
|
||||
}
|
||||
repeated TriggeredRule triggered_rules = 3;
|
||||
|
||||
reserved 4 to 7;
|
||||
}
|
||||
repeated Result results = 4;
|
||||
|
||||
reserved 2 to 3;
|
||||
}
|
||||
|
||||
// An Acknowledgement is sent by the browser following the receipt of a response
|
||||
// from the agent.
|
||||
message ContentAnalysisAcknowledgement {
|
||||
// Token used to correlate with the corresponding request and response.
|
||||
optional string request_token = 1;
|
||||
|
||||
// The action taken by google Chrome with the content analysis response.
|
||||
enum Status {
|
||||
// The response was handled as specified by the agent.
|
||||
SUCCESS = 1;
|
||||
|
||||
// The response from the agent was not properly formatted.
|
||||
INVALID_RESPONSE = 2;
|
||||
|
||||
// The response from the agent was too late and Google Chrome took the
|
||||
// default action.
|
||||
TOO_LATE = 3;
|
||||
};
|
||||
optional Status status = 2;
|
||||
|
||||
// The final action that chrome took with this request. This may be different
|
||||
// from the action specified in the response if the response was too late or
|
||||
// if the original request was part of a user action whose overall final
|
||||
// differed from the action of this particular request.
|
||||
enum FinalAction {
|
||||
ACTION_UNSPECIFIED = 0;
|
||||
ALLOW = 1;
|
||||
REPORT_ONLY = 2;
|
||||
WARN = 3;
|
||||
BLOCK = 4;
|
||||
};
|
||||
optional FinalAction final_action = 3;
|
||||
}
|
||||
|
||||
// A message that asks the agent to cancel all requests with the given user
|
||||
// action id. Note that more that content analysis request may have the given
|
||||
// user action id.
|
||||
message ContentAnalysisCancelRequests {
|
||||
optional string user_action_id = 1;
|
||||
}
|
||||
|
||||
// Generic message sent from Chrome to Agent.
|
||||
message ChromeToAgent {
|
||||
optional ContentAnalysisRequest request = 1;
|
||||
optional ContentAnalysisAcknowledgement ack = 2;
|
||||
optional ContentAnalysisCancelRequests cancel = 3;
|
||||
}
|
||||
|
||||
// Generic message sent from Agent to Chrome.
|
||||
message AgentToChrome {
|
||||
optional ContentAnalysisResponse response = 1;
|
||||
}
|
||||
3
third_party/moz.build
vendored
3
third_party/moz.build
vendored
|
|
@ -7,6 +7,9 @@ with Files('moz.build'):
|
|||
with Files('aom/**'):
|
||||
BUG_COMPONENT = ('Core', 'Audio/Video: Playback')
|
||||
|
||||
with Files('content_analysis_sdk/**'):
|
||||
BUG_COMPONENT = ('Firefox', 'Data Loss Prevention')
|
||||
|
||||
with Files('cups/**'):
|
||||
BUG_COMPONENT = ('Core', 'Printing: Setup')
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue