diff options
Diffstat (limited to '')
34 files changed, 2786 insertions, 0 deletions
diff --git a/third_party/content_analysis_sdk/agent/README.md b/third_party/content_analysis_sdk/agent/README.md new file mode 100644 index 0000000000..0df623b7b5 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/README.md @@ -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" +```
\ No newline at end of file diff --git a/third_party/content_analysis_sdk/agent/include/content_analysis/sdk/analysis_agent.h b/third_party/content_analysis_sdk/agent/include/content_analysis/sdk/analysis_agent.h new file mode 100644 index 0000000000..6f7617120a --- /dev/null +++ b/third_party/content_analysis_sdk/agent/include/content_analysis/sdk/analysis_agent.h @@ -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_ diff --git a/third_party/content_analysis_sdk/agent/include/content_analysis/sdk/result_codes.h b/third_party/content_analysis_sdk/agent/include/content_analysis/sdk/result_codes.h new file mode 100644 index 0000000000..e2a858c069 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/include/content_analysis/sdk/result_codes.h @@ -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_ diff --git a/third_party/content_analysis_sdk/agent/include/content_analysis/sdk/result_codes.inc b/third_party/content_analysis_sdk/agent/include/content_analysis/sdk/result_codes.inc new file mode 100644 index 0000000000..05155ecb1e --- /dev/null +++ b/third_party/content_analysis_sdk/agent/include/content_analysis/sdk/result_codes.inc @@ -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.") diff --git a/third_party/content_analysis_sdk/agent/src/agent_base.cc b/third_party/content_analysis_sdk/agent/src/agent_base.cc new file mode 100644 index 0000000000..c584dac5b6 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/agent_base.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/agent_base.h b/third_party/content_analysis_sdk/agent/src/agent_base.h new file mode 100644 index 0000000000..8ce7df29ef --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/agent_base.h @@ -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_
\ No newline at end of file diff --git a/third_party/content_analysis_sdk/agent/src/agent_mac.cc b/third_party/content_analysis_sdk/agent/src/agent_mac.cc new file mode 100644 index 0000000000..5bbac5fe49 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/agent_mac.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/agent_mac.h b/third_party/content_analysis_sdk/agent/src/agent_mac.h new file mode 100644 index 0000000000..6fd3d49f7b --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/agent_mac.h @@ -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_ diff --git a/third_party/content_analysis_sdk/agent/src/agent_posix.cc b/third_party/content_analysis_sdk/agent/src/agent_posix.cc new file mode 100644 index 0000000000..c206261213 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/agent_posix.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/agent_posix.h b/third_party/content_analysis_sdk/agent/src/agent_posix.h new file mode 100644 index 0000000000..cd9e67ad25 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/agent_posix.h @@ -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_
\ No newline at end of file diff --git a/third_party/content_analysis_sdk/agent/src/agent_utils_win.cc b/third_party/content_analysis_sdk/agent/src/agent_utils_win.cc new file mode 100644 index 0000000000..5a5f411d84 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/agent_utils_win.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/agent_utils_win.h b/third_party/content_analysis_sdk/agent/src/agent_utils_win.h new file mode 100644 index 0000000000..7368ac794e --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/agent_utils_win.h @@ -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_ diff --git a/third_party/content_analysis_sdk/agent/src/agent_win.cc b/third_party/content_analysis_sdk/agent/src/agent_win.cc new file mode 100644 index 0000000000..5d0b2c62b5 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/agent_win.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/agent_win.h b/third_party/content_analysis_sdk/agent/src/agent_win.h new file mode 100644 index 0000000000..a6a0c73311 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/agent_win.h @@ -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_ diff --git a/third_party/content_analysis_sdk/agent/src/agent_win_unittest.cc b/third_party/content_analysis_sdk/agent/src/agent_win_unittest.cc new file mode 100644 index 0000000000..c0bddf82f4 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/agent_win_unittest.cc @@ -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 + diff --git a/third_party/content_analysis_sdk/agent/src/event_base.cc b/third_party/content_analysis_sdk/agent/src/event_base.cc new file mode 100644 index 0000000000..c8186a4112 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/event_base.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/event_base.h b/third_party/content_analysis_sdk/agent/src/event_base.h new file mode 100644 index 0000000000..3e41c0ea44 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/event_base.h @@ -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_ diff --git a/third_party/content_analysis_sdk/agent/src/event_mac.cc b/third_party/content_analysis_sdk/agent/src/event_mac.cc new file mode 100644 index 0000000000..46822e473d --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/event_mac.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/event_mac.h b/third_party/content_analysis_sdk/agent/src/event_mac.h new file mode 100644 index 0000000000..0d89c47035 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/event_mac.h @@ -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_ diff --git a/third_party/content_analysis_sdk/agent/src/event_mac_unittest.cc b/third_party/content_analysis_sdk/agent/src/event_mac_unittest.cc new file mode 100644 index 0000000000..cf41d6bcdb --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/event_mac_unittest.cc @@ -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 + diff --git a/third_party/content_analysis_sdk/agent/src/event_posix.cc b/third_party/content_analysis_sdk/agent/src/event_posix.cc new file mode 100644 index 0000000000..1b0854af90 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/event_posix.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/event_posix.h b/third_party/content_analysis_sdk/agent/src/event_posix.h new file mode 100644 index 0000000000..f3d0e24330 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/event_posix.h @@ -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_
\ No newline at end of file diff --git a/third_party/content_analysis_sdk/agent/src/event_posix_unittest.cc b/third_party/content_analysis_sdk/agent/src/event_posix_unittest.cc new file mode 100644 index 0000000000..841934c2fd --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/event_posix_unittest.cc @@ -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 + diff --git a/third_party/content_analysis_sdk/agent/src/event_win.cc b/third_party/content_analysis_sdk/agent/src/event_win.cc new file mode 100644 index 0000000000..907bdfb858 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/event_win.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/event_win.h b/third_party/content_analysis_sdk/agent/src/event_win.h new file mode 100644 index 0000000000..f631f693dc --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/event_win.h @@ -0,0 +1,51 @@ +// 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; + std::string SerializeStringToSendToBrowser() { + return agent_to_chrome()->SerializeAsString(); + } + void SetResponseSent() { response_sent_ = true; } + + HANDLE Pipe() const { return hPipe_; } + + 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_
\ No newline at end of file diff --git a/third_party/content_analysis_sdk/agent/src/event_win_unittest.cc b/third_party/content_analysis_sdk/agent/src/event_win_unittest.cc new file mode 100644 index 0000000000..ffc3103ac1 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/event_win_unittest.cc @@ -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 + diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.cc b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.cc new file mode 100644 index 0000000000..802b553b20 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.h b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.h new file mode 100644 index 0000000000..1ae20381d7 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.h @@ -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_ diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.cc b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.cc new file mode 100644 index 0000000000..316576bf90 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.h b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.h new file mode 100644 index 0000000000..d407ef5375 --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.h @@ -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_ diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.cc b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.cc new file mode 100644 index 0000000000..cd2eb2cc8e --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.h b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.h new file mode 100644 index 0000000000..e9125f58eb --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.h @@ -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_ diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.cc b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.cc new file mode 100644 index 0000000000..2573d676ea --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.cc @@ -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 diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.h b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.h new file mode 100644 index 0000000000..31923285be --- /dev/null +++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.h @@ -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_ |