| // Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights |
| // reserved. Use of this source code is governed by a BSD-style license that |
| // can be found in the LICENSE file. |
| |
| #include "tests/cefclient/browser/response_filter_test.h" |
| |
| #include <algorithm> |
| #include <sstream> |
| #include <string> |
| |
| #include "include/base/cef_logging.h" |
| #include "include/cef_command_line.h" |
| #include "tests/cefclient/browser/test_runner.h" |
| #include "tests/shared/common/client_switches.h" |
| |
| namespace client { |
| namespace response_filter_test { |
| |
| namespace { |
| |
| const char kTestUrlPath[] = "/response_filter"; |
| const char kFindString[] = "REPLACE_THIS_STRING"; |
| const char kReplaceString[] = "This is the replaced string!"; |
| |
| // Helper for passing params to Write(). |
| #define WRITE_PARAMS data_out_ptr, data_out_size, data_out_written |
| |
| // Filter the contents of response_filter.html by replacing all instances of |
| // |kFindString| with |kReplaceString|. Pass the `--enable-filter-testing` |
| // command-line flag (which shrinks the buffer size to 32 bytes) to better test |
| // the logic in this implementation. |
| class FindReplaceResponseFilter : public CefResponseFilter { |
| public: |
| FindReplaceResponseFilter() |
| : find_match_offset_(0U), |
| replace_overflow_size_(0U), |
| replace_count_(0U) {} |
| |
| bool InitFilter() OVERRIDE { |
| const size_t find_size = sizeof(kFindString) - 1; |
| const size_t replace_size = sizeof(kReplaceString) - 1; |
| |
| // Determine a reasonable amount of space for find/replace overflow. For |
| // example, the amount of space required if the search string is |
| // found/replaced 10 times (plus space for the count). |
| if (replace_size > find_size) |
| replace_overflow_size_ = (replace_size - find_size + 3) * 10; |
| |
| return true; |
| } |
| |
| FilterStatus Filter(void* data_in, |
| size_t data_in_size, |
| size_t& data_in_read, |
| void* data_out, |
| size_t data_out_size, |
| size_t& data_out_written) OVERRIDE { |
| DCHECK((data_in_size == 0U && !data_in) || (data_in_size > 0U && data_in)); |
| DCHECK_EQ(data_in_read, 0U); |
| DCHECK(data_out); |
| DCHECK_GT(data_out_size, 0U); |
| DCHECK_EQ(data_out_written, 0U); |
| |
| // All data will be read. |
| data_in_read = data_in_size; |
| |
| const size_t find_size = sizeof(kFindString) - 1; |
| |
| const char* data_in_ptr = static_cast<char*>(data_in); |
| char* data_out_ptr = static_cast<char*>(data_out); |
| |
| // Reset the overflow. |
| std::string old_overflow; |
| if (!overflow_.empty()) { |
| old_overflow = overflow_; |
| overflow_.clear(); |
| } |
| |
| const size_t likely_out_size = |
| data_in_size + replace_overflow_size_ + old_overflow.size(); |
| if (data_out_size < likely_out_size) { |
| // We'll likely need to use the overflow buffer. Size it appropriately. |
| overflow_.reserve(likely_out_size - data_out_size); |
| } |
| |
| if (!old_overflow.empty()) { |
| // Write the overflow from last time. |
| Write(old_overflow.c_str(), old_overflow.size(), WRITE_PARAMS); |
| } |
| |
| // Evaluate each character in the input buffer. Track how many characters in |
| // a row match kFindString. If kFindString is completely matched then write |
| // kReplaceString. Otherwise, write the input characters as-is. |
| for (size_t i = 0U; i < data_in_size; ++i) { |
| if (data_in_ptr[i] == kFindString[find_match_offset_]) { |
| // Matched the next character in the find string. |
| if (++find_match_offset_ == find_size) { |
| // Complete match of the find string. Write the replace string. |
| std::stringstream ss; |
| ss << ++replace_count_ << ". " << kReplaceString; |
| const std::string& replace_str = ss.str(); |
| Write(replace_str.c_str(), replace_str.size(), WRITE_PARAMS); |
| |
| // Start over looking for a match. |
| find_match_offset_ = 0; |
| } |
| continue; |
| } |
| |
| // Character did not match the find string. |
| if (find_match_offset_ > 0) { |
| // Write the portion of the find string that has matched so far. |
| Write(kFindString, find_match_offset_, WRITE_PARAMS); |
| |
| // Start over looking for a match. |
| find_match_offset_ = 0; |
| } |
| |
| // Write the current character. |
| Write(&data_in_ptr[i], 1, WRITE_PARAMS); |
| } |
| |
| // If a match is currently in-progress we need more data. Otherwise, we're |
| // done. |
| return find_match_offset_ > 0 ? RESPONSE_FILTER_NEED_MORE_DATA |
| : RESPONSE_FILTER_DONE; |
| } |
| |
| private: |
| inline void Write(const char* str, |
| size_t str_size, |
| char*& data_out_ptr, |
| size_t data_out_size, |
| size_t& data_out_written) { |
| // Number of bytes remaining in the output buffer. |
| const size_t remaining_space = data_out_size - data_out_written; |
| // Maximum number of bytes we can write into the output buffer. |
| const size_t max_write = std::min(str_size, remaining_space); |
| |
| // Write the maximum portion that fits in the output buffer. |
| if (max_write == 1) { |
| // Small optimization for single character writes. |
| *data_out_ptr = str[0]; |
| data_out_ptr += 1; |
| data_out_written += 1; |
| } else if (max_write > 1) { |
| memcpy(data_out_ptr, str, max_write); |
| data_out_ptr += max_write; |
| data_out_written += max_write; |
| } |
| |
| if (max_write < str_size) { |
| // Need to write more bytes than will fit in the output buffer. Store the |
| // remainder in the overflow buffer. |
| overflow_ += std::string(str + max_write, str_size - max_write); |
| } |
| } |
| |
| // The portion of the find string that is currently matching. |
| size_t find_match_offset_; |
| |
| // The likely amount of overflow. |
| size_t replace_overflow_size_; |
| |
| // Overflow from the output buffer. |
| std::string overflow_; |
| |
| // Number of times the the string was found/replaced. |
| size_t replace_count_; |
| |
| IMPLEMENT_REFCOUNTING(FindReplaceResponseFilter); |
| }; |
| |
| // Filter that writes out all of the contents unchanged. |
| class PassThruResponseFilter : public CefResponseFilter { |
| public: |
| PassThruResponseFilter() {} |
| |
| bool InitFilter() OVERRIDE { return true; } |
| |
| FilterStatus Filter(void* data_in, |
| size_t data_in_size, |
| size_t& data_in_read, |
| void* data_out, |
| size_t data_out_size, |
| size_t& data_out_written) OVERRIDE { |
| DCHECK((data_in_size == 0U && !data_in) || (data_in_size > 0U && data_in)); |
| DCHECK_EQ(data_in_read, 0U); |
| DCHECK(data_out); |
| DCHECK_GT(data_out_size, 0U); |
| DCHECK_EQ(data_out_written, 0U); |
| |
| // All data will be read. |
| data_in_read = data_in_size; |
| |
| // Write out the contents unchanged. |
| data_out_written = std::min(data_in_read, data_out_size); |
| if (data_out_written > 0) |
| memcpy(data_out, data_in, data_out_written); |
| |
| return RESPONSE_FILTER_DONE; |
| } |
| |
| private: |
| IMPLEMENT_REFCOUNTING(PassThruResponseFilter); |
| }; |
| |
| // Returns true if |url| starts with the value specified via the `--filter-url` |
| // command-line flag. |
| bool MatchesFilterURL(const std::string& url) { |
| CefRefPtr<CefCommandLine> command_line = |
| CefCommandLine::GetGlobalCommandLine(); |
| if (command_line->HasSwitch(switches::kFilterURL)) { |
| const std::string& filter_url = |
| command_line->GetSwitchValue(switches::kFilterURL); |
| return url.find(filter_url) == 0; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| CefRefPtr<CefResponseFilter> GetResourceResponseFilter( |
| CefRefPtr<CefBrowser> browser, |
| CefRefPtr<CefFrame> frame, |
| CefRefPtr<CefRequest> request, |
| CefRefPtr<CefResponse> response) { |
| // Use the find/replace filter on the test URL. |
| const std::string& url = request->GetURL(); |
| |
| if (test_runner::IsTestURL(url, kTestUrlPath)) |
| return new FindReplaceResponseFilter(); |
| |
| if (MatchesFilterURL(url)) |
| return new PassThruResponseFilter(); |
| |
| return nullptr; |
| } |
| |
| } // namespace response_filter_test |
| } // namespace client |