diff options
| -rw-r--r-- | chrome/chrome_tests.gypi | 2 | ||||
| -rw-r--r-- | chrome/test/webdriver/commands/file_upload_command.cc | 63 | ||||
| -rw-r--r-- | chrome/test/webdriver/commands/file_upload_command.h | 36 | ||||
| -rw-r--r-- | chrome/test/webdriver/webdriver_capabilities_parser.cc | 48 | ||||
| -rw-r--r-- | chrome/test/webdriver/webdriver_capabilities_parser.h | 7 | ||||
| -rw-r--r-- | chrome/test/webdriver/webdriver_server.cc | 2 | ||||
| -rw-r--r-- | chrome/test/webdriver/webdriver_session.cc | 10 | ||||
| -rw-r--r-- | chrome/test/webdriver/webdriver_session.h | 5 | ||||
| -rw-r--r-- | chrome/test/webdriver/webdriver_util.cc | 359 | ||||
| -rw-r--r-- | chrome/test/webdriver/webdriver_util.h | 22 | ||||
| -rw-r--r-- | chrome/test/webdriver/webdriver_util_unittest.cc | 31 |
11 files changed, 532 insertions, 53 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 4cea61c..0d7824e 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1018,6 +1018,8 @@ 'test/webdriver/commands/execute_async_script_command.h', 'test/webdriver/commands/execute_command.cc', 'test/webdriver/commands/execute_command.h', + 'test/webdriver/commands/file_upload_command.cc', + 'test/webdriver/commands/file_upload_command.h', 'test/webdriver/commands/find_element_commands.cc', 'test/webdriver/commands/find_element_commands.h', 'test/webdriver/commands/html5_storage_commands.cc', diff --git a/chrome/test/webdriver/commands/file_upload_command.cc b/chrome/test/webdriver/commands/file_upload_command.cc new file mode 100644 index 0000000..25af368 --- /dev/null +++ b/chrome/test/webdriver/commands/file_upload_command.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2012 The Chromium 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 "chrome/test/webdriver/commands/file_upload_command.h" + +#include "base/file_util.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "build/build_config.h" +#include "chrome/test/webdriver/commands/response.h" +#include "chrome/test/webdriver/webdriver_error.h" +#include "chrome/test/webdriver/webdriver_session.h" +#include "chrome/test/webdriver/webdriver_util.h" + +namespace webdriver { + +FileUploadCommand::FileUploadCommand( + const std::vector<std::string>& path_segments, + DictionaryValue* parameters) + : WebDriverCommand(path_segments, parameters) { +} + +FileUploadCommand::~FileUploadCommand() { +} + +bool FileUploadCommand::DoesPost() { + return true; +} + +void FileUploadCommand::ExecutePost(Response* const response) { + std::string base64_zip_data; + if (!GetStringParameter("file", &base64_zip_data)) { + response->SetError(new Error(kUnknownError, "Missing or invalid 'file'")); + return; + } + std::string zip_data; + if (!Base64Decode(base64_zip_data, &zip_data)) { + response->SetError(new Error(kUnknownError, "Unable to decode 'file'")); + return; + } + + FilePath upload_dir; + if (!file_util::CreateTemporaryDirInDir( + session_->temp_dir(), FILE_PATH_LITERAL("upload"), &upload_dir)) { + response->SetError(new Error(kUnknownError, "Failed to create temp dir")); + return; + } + std::string error_msg; + FilePath upload; + if (!UnzipSoleFile(upload_dir, zip_data, &upload, &error_msg)) { + response->SetError(new Error(kUnknownError, error_msg)); + return; + } + +#if defined(OS_WIN) + response->SetValue(new base::StringValue(WideToUTF8(upload.value()))); +#else + response->SetValue(new base::StringValue(upload.value())); +#endif +} + +} // namespace webdriver diff --git a/chrome/test/webdriver/commands/file_upload_command.h b/chrome/test/webdriver/commands/file_upload_command.h new file mode 100644 index 0000000..724f275 --- /dev/null +++ b/chrome/test/webdriver/commands/file_upload_command.h @@ -0,0 +1,36 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_TEST_WEBDRIVER_COMMANDS_FILE_UPLOAD_COMMAND_H_ +#define CHROME_TEST_WEBDRIVER_COMMANDS_FILE_UPLOAD_COMMAND_H_ + +#include <string> +#include <vector> + +#include "chrome/test/webdriver/commands/webdriver_command.h" + +namespace base { +class DictionaryValue; +} + +namespace webdriver { + +class Response; + +class FileUploadCommand : public WebDriverCommand { + public: + FileUploadCommand(const std::vector<std::string>& path_segments, + base::DictionaryValue* parameters); + virtual ~FileUploadCommand(); + + virtual bool DoesPost() OVERRIDE; + virtual void ExecutePost(Response* const response) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(FileUploadCommand); +}; + +} // namespace webdriver + +#endif // CHROME_TEST_WEBDRIVER_COMMANDS_FILE_UPLOAD_COMMAND_H_ diff --git a/chrome/test/webdriver/webdriver_capabilities_parser.cc b/chrome/test/webdriver/webdriver_capabilities_parser.cc index 12d85cd..579d660 100644 --- a/chrome/test/webdriver/webdriver_capabilities_parser.cc +++ b/chrome/test/webdriver/webdriver_capabilities_parser.cc @@ -4,14 +4,12 @@ #include "chrome/test/webdriver/webdriver_capabilities_parser.h" -#include "base/base64.h" #include "base/file_util.h" #include "base/format_macros.h" #include "base/stringprintf.h" #include "base/string_util.h" #include "base/values.h" #include "chrome/common/chrome_switches.h" -#include "chrome/common/zip.h" #include "chrome/test/webdriver/webdriver_error.h" #include "chrome/test/webdriver/webdriver_util.h" @@ -202,13 +200,13 @@ Error* CapabilitiesParser::ParseExtensions(const Value* option) { } FilePath extension = root_.AppendASCII( base::StringPrintf("extension%" PRIuS ".crx", i)); - std::string error_msg; - if (!DecodeAndWriteFile(extension, extension_base64, false /* unzip */, - &error_msg)) { - return new Error( - kUnknownError, - "Error occurred while parsing extension: " + error_msg); - } + std::string decoded_extension; + if (!Base64Decode(extension_base64, &decoded_extension)) + return new Error(kUnknownError, "Failed to base64 decode extension"); + int size = static_cast<int>(decoded_extension.length()); + if (file_util::WriteFile( + extension, decoded_extension.c_str(), size) != size) + return new Error(kUnknownError, "Failed to write extension file"); caps_->extensions.push_back(extension); } return NULL; @@ -273,8 +271,7 @@ Error* CapabilitiesParser::ParseProfile(const Value* option) { return CreateBadInputError("profile", Value::TYPE_STRING, option); std::string error_msg; caps_->profile = root_.AppendASCII("profile"); - if (!DecodeAndWriteFile(caps_->profile, profile_base64, true /* unzip */, - &error_msg)) + if (!Base64DecodeAndUnzip(caps_->profile, profile_base64, &error_msg)) return new Error(kUnknownError, "unable to unpack profile: " + error_msg); return NULL; } @@ -432,33 +429,4 @@ Error* CapabilitiesParser::ParseNoWebsiteTestingDefaults(const Value* option) { return NULL; } -bool CapabilitiesParser::DecodeAndWriteFile( - const FilePath& path, - const std::string& base64, - bool unzip, - std::string* error_msg) { - std::string data; - if (!base::Base64Decode(base64, &data)) { - *error_msg = "Could not decode base64 data"; - return false; - } - if (unzip) { - FilePath temp_file(root_.AppendASCII(GenerateRandomID())); - if (!file_util::WriteFile(temp_file, data.c_str(), data.length())) { - *error_msg = "Could not write file"; - return false; - } - if (!zip::Unzip(temp_file, path)) { - *error_msg = "Could not unzip archive"; - return false; - } - } else { - if (!file_util::WriteFile(path, data.c_str(), data.length())) { - *error_msg = "Could not write file"; - return false; - } - } - return true; -} - } // namespace webdriver diff --git a/chrome/test/webdriver/webdriver_capabilities_parser.h b/chrome/test/webdriver/webdriver_capabilities_parser.h index f2e06d5..aca998f 100644 --- a/chrome/test/webdriver/webdriver_capabilities_parser.h +++ b/chrome/test/webdriver/webdriver_capabilities_parser.h @@ -111,13 +111,6 @@ class CapabilitiesParser { Error* ParseProxyAutoconfigUrl(const base::DictionaryValue* options); Error* ParseProxyServers(const base::DictionaryValue* options); Error* ParseNoWebsiteTestingDefaults(const base::Value* option); - // Decodes the given base64-encoded string, optionally unzips it, and - // writes the result to |path|. - // On error, false will be returned and |error_msg| will be set. - bool DecodeAndWriteFile(const FilePath& path, - const std::string& base64, - bool unzip, - std::string* error_msg); // The capabilities dictionary to parse. const base::DictionaryValue* dict_; diff --git a/chrome/test/webdriver/webdriver_server.cc b/chrome/test/webdriver/webdriver_server.cc index 4b6e005..888038c 100644 --- a/chrome/test/webdriver/webdriver_server.cc +++ b/chrome/test/webdriver/webdriver_server.cc @@ -39,6 +39,7 @@ #include "chrome/test/webdriver/commands/create_session.h" #include "chrome/test/webdriver/commands/execute_async_script_command.h" #include "chrome/test/webdriver/commands/execute_command.h" +#include "chrome/test/webdriver/commands/file_upload_command.h" #include "chrome/test/webdriver/commands/find_element_commands.h" #include "chrome/test/webdriver/commands/html5_storage_commands.h" #include "chrome/test/webdriver/commands/keys_command.h" @@ -148,6 +149,7 @@ void InitCallbacks(Dispatcher* dispatcher, "/session/*/timeouts/async_script"); dispatcher->Add<ImplicitWaitCommand>( "/session/*/timeouts/implicit_wait"); dispatcher->Add<LogCommand>( "/session/*/log"); + dispatcher->Add<FileUploadCommand>( "/session/*/file"); // Cookie functions. dispatcher->Add<CookieCommand>( "/session/*/cookie"); diff --git a/chrome/test/webdriver/webdriver_session.cc b/chrome/test/webdriver/webdriver_session.cc index de1928e..7a45145 100644 --- a/chrome/test/webdriver/webdriver_session.cc +++ b/chrome/test/webdriver/webdriver_session.cc @@ -18,7 +18,6 @@ #include "base/message_loop_proxy.h" #include "base/process.h" #include "base/process_util.h" -#include "base/scoped_temp_dir.h" #include "base/string_number_conversions.h" #include "base/string_split.h" #include "base/string_util.h" @@ -88,8 +87,7 @@ Error* Session::Init(const DictionaryValue* capabilities_dict) { delete this; return new Error(kUnknownError, "Cannot start session thread"); } - ScopedTempDir temp_dir; - if (!temp_dir.CreateUniqueTempDir()) { + if (!temp_dir_.CreateUniqueTempDir()) { delete this; return new Error( kUnknownError, "Unable to create temp directory for unpacking"); @@ -98,7 +96,7 @@ Error* Session::Init(const DictionaryValue* capabilities_dict) { "Initializing session with capabilities " + JsonStringifyForDisplay(capabilities_dict)); CapabilitiesParser parser( - capabilities_dict, temp_dir.path(), logger_, &capabilities_); + capabilities_dict, temp_dir_.path(), logger_, &capabilities_); Error* error = parser.Parse(); if (error) { delete this; @@ -1409,6 +1407,10 @@ const Logger& Session::logger() const { return logger_; } +const FilePath& Session::temp_dir() const { + return temp_dir_.path(); +} + const Capabilities& Session::capabilities() const { return capabilities_; } diff --git a/chrome/test/webdriver/webdriver_session.h b/chrome/test/webdriver/webdriver_session.h index d734d8e..d99a842 100644 --- a/chrome/test/webdriver/webdriver_session.h +++ b/chrome/test/webdriver/webdriver_session.h @@ -12,6 +12,7 @@ #include "base/callback_forward.h" #include "base/file_path.h" #include "base/memory/scoped_ptr.h" +#include "base/scoped_temp_dir.h" #include "base/string16.h" #include "base/threading/thread.h" #include "chrome/common/automation_constants.h" @@ -383,6 +384,8 @@ class Session { const Logger& logger() const; + const FilePath& temp_dir() const; + const Capabilities& capabilities() const; private: @@ -471,6 +474,8 @@ class Session { std::string alert_prompt_text_; bool has_alert_prompt_text_; + // Temporary directory containing session data. + ScopedTempDir temp_dir_; Capabilities capabilities_; // Current state of all modifier keys. diff --git a/chrome/test/webdriver/webdriver_util.cc b/chrome/test/webdriver/webdriver_util.cc index bab42b8..44f7caf 100644 --- a/chrome/test/webdriver/webdriver_util.cc +++ b/chrome/test/webdriver/webdriver_util.cc @@ -4,17 +4,22 @@ #include "chrome/test/webdriver/webdriver_util.h" +#include "base/base64.h" #include "base/basictypes.h" +#include "base/file_util.h" #include "base/format_macros.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/memory/scoped_ptr.h" #include "base/rand_util.h" +#include "base/scoped_temp_dir.h" #include "base/stringprintf.h" #include "base/string_number_conversions.h" #include "base/string_split.h" +#include "base/string_util.h" #include "base/third_party/icu/icu_utf.h" #include "chrome/common/automation_id.h" +#include "chrome/common/zip.h" #include "chrome/test/automation/automation_json_requests.h" using base::DictionaryValue; @@ -31,6 +36,360 @@ std::string GenerateRandomID() { return base::StringPrintf("%016" PRIx64 "%016" PRIx64, msb, lsb); } +bool Base64Decode(const std::string& base64, + std::string* bytes) { + std::string copy = base64; + // Some WebDriver client base64 encoders follow RFC 1521, which require that + // 'encoded lines be no more than 76 characters long'. Just remove any + // newlines. + RemoveChars(copy, "\n", ©); + return base::Base64Decode(copy, bytes); +} + +namespace { + +bool UnzipArchive(const FilePath& unzip_dir, + const std::string& bytes, + std::string* error_msg) { + ScopedTempDir dir; + if (!dir.CreateUniqueTempDir()) { + *error_msg = "Unable to create temp dir"; + return false; + } + FilePath archive = dir.path().AppendASCII("temp.zip"); + int length = bytes.length(); + if (file_util::WriteFile(archive, bytes.c_str(), length) != length) { + *error_msg = "Could not write file to temp dir"; + return false; + } + if (!zip::Unzip(archive, unzip_dir)) { + *error_msg = "Could not unzip archive"; + return false; + } + return true; +} + +} // namespace + +bool Base64DecodeAndUnzip(const FilePath& unzip_dir, + const std::string& base64, + std::string* error_msg) { + std::string zip_data; + if (!Base64Decode(base64, &zip_data)) { + *error_msg = "Could not decode base64 zip data"; + return false; + } + return UnzipArchive(unzip_dir, zip_data, error_msg); +} + +namespace { + +// Stream for writing binary data. +class DataOutputStream { + public: + DataOutputStream() {} + ~DataOutputStream() {} + + void WriteUInt16(uint16 data) { + WriteBytes(&data, sizeof(data)); + } + + void WriteUInt32(uint32 data) { + WriteBytes(&data, sizeof(data)); + } + + void WriteString(const std::string& data) { + WriteBytes(data.c_str(), data.length()); + } + + void WriteBytes(const void* bytes, int size) { + size_t next = buffer_.length(); + buffer_.resize(next + size); + memcpy(&buffer_[next], bytes, size); + } + + const std::string& buffer() const { return buffer_; } + + private: + std::string buffer_; +}; + +// Stream for reading binary data. +class DataInputStream { + public: + DataInputStream(const char* data, int size) + : data_(data), size_(size), iter_(0) {} + ~DataInputStream() {} + + bool ReadUInt16(uint16* data) { + return ReadBytes(data, sizeof(*data)); + } + + bool ReadUInt32(uint32* data) { + return ReadBytes(data, sizeof(*data)); + } + + bool ReadString(std::string* data, int length) { + if (length < 0) + return false; + // Check here to make sure we don't allocate wastefully. + if (iter_ + length > size_) + return false; + data->resize(length); + return ReadBytes(&(*data)[0], length); + } + + bool ReadBytes(void* bytes, int size) { + if (iter_ + size > size_) + return false; + memcpy(bytes, &data_[iter_], size); + iter_ += size; + return true; + } + + int remaining() const { return size_ - iter_; } + + private: + const char* data_; + int size_; + int iter_; +}; + +// A file entry within a zip archive. This may be incomplete and is not +// guaranteed to be able to parse all types of zip entries. +// See http://www.pkware.com/documents/casestudies/APPNOTE.TXT for the zip +// file format. +struct ZipEntry { + // The given bytes must contain the whole zip entry and only the entry, + // although the entry may include a data descriptor. + static bool FromBytes(const std::string& bytes, ZipEntry* zip, + std::string* error_msg) { + DataInputStream stream(bytes.c_str(), bytes.length()); + + uint32 signature; + if (!stream.ReadUInt32(&signature) || signature != kFileHeaderSignature) { + *error_msg = "Invalid file header signature"; + return false; + } + if (!stream.ReadUInt16(&zip->version_needed)) { + *error_msg = "Invalid version"; + return false; + } + if (!stream.ReadUInt16(&zip->bit_flag)) { + *error_msg = "Invalid bit flag"; + return false; + } + if (!stream.ReadUInt16(&zip->compression_method)) { + *error_msg = "Invalid compression method"; + return false; + } + if (!stream.ReadUInt16(&zip->mod_time)) { + *error_msg = "Invalid file last modified time"; + return false; + } + if (!stream.ReadUInt16(&zip->mod_date)) { + *error_msg = "Invalid file last modified date"; + return false; + } + if (!stream.ReadUInt32(&zip->crc)) { + *error_msg = "Invalid crc"; + return false; + } + uint32 compressed_size; + if (!stream.ReadUInt32(&compressed_size)) { + *error_msg = "Invalid compressed size"; + return false; + } + if (!stream.ReadUInt32(&zip->uncompressed_size)) { + *error_msg = "Invalid compressed size"; + return false; + } + uint16 name_length; + if (!stream.ReadUInt16(&name_length)) { + *error_msg = "Invalid name length"; + return false; + } + uint16 field_length; + if (!stream.ReadUInt16(&field_length)) { + *error_msg = "Invalid field length"; + return false; + } + if (!stream.ReadString(&zip->name, name_length)) { + *error_msg = "Invalid name"; + return false; + } + if (!stream.ReadString(&zip->fields, field_length)) { + *error_msg = "Invalid fields"; + return false; + } + if (zip->bit_flag & 0x8) { + // Has compressed data and a separate data descriptor. + if (stream.remaining() < 16) { + *error_msg = "Too small for data descriptor"; + return false; + } + compressed_size = stream.remaining() - 16; + if (!stream.ReadString(&zip->compressed_data, compressed_size)) { + *error_msg = "Invalid compressed data before descriptor"; + return false; + } + if (!stream.ReadUInt32(&signature) || + signature != kDataDescriptorSignature) { + *error_msg = "Invalid data descriptor signature"; + return false; + } + if (!stream.ReadUInt32(&zip->crc)) { + *error_msg = "Invalid crc"; + return false; + } + if (!stream.ReadUInt32(&compressed_size)) { + *error_msg = "Invalid compressed size"; + return false; + } + if (compressed_size != zip->compressed_data.length()) { + *error_msg = "Compressed data does not match data descriptor"; + return false; + } + if (!stream.ReadUInt32(&zip->uncompressed_size)) { + *error_msg = "Invalid compressed size"; + return false; + } + } else { + // Just has compressed data. + if (!stream.ReadString(&zip->compressed_data, compressed_size)) { + *error_msg = "Invalid compressed data"; + return false; + } + if (stream.remaining() != 0) { + *error_msg = "Leftover data after zip entry"; + return false; + } + } + return true; + } + + // Returns bytes for a valid zip file that just contains this zip entry. + std::string ToZip() { + // Write zip entry with no data descriptor. + DataOutputStream stream; + stream.WriteUInt32(kFileHeaderSignature); + stream.WriteUInt16(version_needed); + stream.WriteUInt16(bit_flag); + stream.WriteUInt16(compression_method); + stream.WriteUInt16(mod_time); + stream.WriteUInt16(mod_date); + stream.WriteUInt32(crc); + stream.WriteUInt32(compressed_data.length()); + stream.WriteUInt32(uncompressed_size); + stream.WriteUInt16(name.length()); + stream.WriteUInt16(fields.length()); + stream.WriteString(name); + stream.WriteString(fields); + stream.WriteString(compressed_data); + uint32 entry_size = stream.buffer().length(); + + // Write central directory. + stream.WriteUInt32(kCentralDirSignature); + stream.WriteUInt16(0x14); // Version made by. Unused at version 0. + stream.WriteUInt16(version_needed); + stream.WriteUInt16(bit_flag); + stream.WriteUInt16(compression_method); + stream.WriteUInt16(mod_time); + stream.WriteUInt16(mod_date); + stream.WriteUInt32(crc); + stream.WriteUInt32(compressed_data.length()); + stream.WriteUInt32(uncompressed_size); + stream.WriteUInt16(name.length()); + stream.WriteUInt16(fields.length()); + stream.WriteUInt16(0); // Comment length. + stream.WriteUInt16(0); // Disk number where file starts. + stream.WriteUInt16(0); // Internal file attr. + stream.WriteUInt32(0); // External file attr. + stream.WriteUInt32(0); // Offset to file. + stream.WriteString(name); + stream.WriteString(fields); + uint32 cd_size = stream.buffer().length() - entry_size; + + // End of central directory. + stream.WriteUInt32(kEndOfCentralDirSignature); + stream.WriteUInt16(0); // num of this disk + stream.WriteUInt16(0); // disk where cd starts + stream.WriteUInt16(1); // number of cds on this disk + stream.WriteUInt16(1); // total cds + stream.WriteUInt32(cd_size); // size of cd + stream.WriteUInt32(entry_size); // offset of cd + stream.WriteUInt16(0); // comment len + + return stream.buffer(); + } + + static const uint32 kFileHeaderSignature; + static const uint32 kDataDescriptorSignature; + static const uint32 kCentralDirSignature; + static const uint32 kEndOfCentralDirSignature; + uint16 version_needed; + uint16 bit_flag; + uint16 compression_method; + uint16 mod_time; + uint16 mod_date; + uint32 crc; + uint32 uncompressed_size; + std::string name; + std::string fields; + std::string compressed_data; +}; + +const uint32 ZipEntry::kFileHeaderSignature = 0x04034b50; +const uint32 ZipEntry::kDataDescriptorSignature = 0x08074b50; +const uint32 ZipEntry::kCentralDirSignature = 0x02014b50; +const uint32 ZipEntry::kEndOfCentralDirSignature = 0x06054b50; + +bool UnzipEntry(const FilePath& unzip_dir, + const std::string& bytes, + std::string* error_msg) { + ZipEntry entry; + std::string zip_error_msg; + if (!ZipEntry::FromBytes(bytes, &entry, &zip_error_msg)) { + *error_msg = "Error while reading zip entry: " + zip_error_msg; + return false; + } + std::string archive = entry.ToZip(); + return UnzipArchive(unzip_dir, archive, error_msg); +} + +} // namespace + +bool UnzipSoleFile(const FilePath& unzip_dir, + const std::string& bytes, + FilePath* file, + std::string* error_msg) { + std::string archive_error, entry_error; + if (!UnzipArchive(unzip_dir, bytes, &archive_error) && + !UnzipEntry(unzip_dir, bytes, &entry_error)) { + *error_msg = base::StringPrintf( + "Failed to unzip file: Archive error: (%s) Entry error: (%s)", + archive_error.c_str(), entry_error.c_str()); + return false; + } + + file_util::FileEnumerator enumerator(unzip_dir, false /* recursive */, + static_cast<file_util::FileEnumerator::FileType>( + file_util::FileEnumerator::FILES | + file_util::FileEnumerator::DIRECTORIES)); + FilePath first_file = enumerator.Next(); + if (first_file.empty()) { + *error_msg = "Zip contained 0 files"; + return false; + } + FilePath second_file = enumerator.Next(); + if (!second_file.empty()) { + *error_msg = "Zip contained multiple files"; + return false; + } + *file = first_file; + return true; +} + std::string JsonStringify(const Value* value) { std::string json; base::JSONWriter::Write(value, false, &json); diff --git a/chrome/test/webdriver/webdriver_util.h b/chrome/test/webdriver/webdriver_util.h index 2628e0e..9450c5e 100644 --- a/chrome/test/webdriver/webdriver_util.h +++ b/chrome/test/webdriver/webdriver_util.h @@ -24,6 +24,28 @@ namespace webdriver { // Generates a random, 32-character hexidecimal ID. std::string GenerateRandomID(); +// Decodes the given base64-encoded string, after removing any newlines, +// which are required in some base64 standards. +// Returns true on success. +bool Base64Decode(const std::string& base64, std::string* bytes); + +// Unzip the given zip archive, after base64 decoding, into the given directory. +// Returns true on success. +bool Base64DecodeAndUnzip(const FilePath& unzip_dir, + const std::string& base64, + std::string* error_msg); + +// Unzips the sole file contained in the given zip data |bytes| into +// |unzip_dir|. The zip data may be a normal zip archive or a single zip file +// entry. If the unzip successfully produced one file, returns true and sets +// |file| to the unzipped file. +// TODO(kkania): Remove the ability to parse single zip file entries when +// the current versions of all WebDriver clients send actual zip files. +bool UnzipSoleFile(const FilePath& unzip_dir, + const std::string& bytes, + FilePath* file, + std::string* error_msg); + // Returns the equivalent JSON string for the given value. std::string JsonStringify(const base::Value* value); diff --git a/chrome/test/webdriver/webdriver_util_unittest.cc b/chrome/test/webdriver/webdriver_util_unittest.cc index c80a3d7..7c82483 100644 --- a/chrome/test/webdriver/webdriver_util_unittest.cc +++ b/chrome/test/webdriver/webdriver_util_unittest.cc @@ -1,17 +1,23 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium 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 <set> #include <string> +#include "base/base64.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/scoped_temp_dir.h" #include "chrome/test/webdriver/webdriver_util.h" #include "testing/gtest/include/gtest/gtest.h" +namespace webdriver { + TEST(RandomIDTest, CanGenerateSufficientlyRandomIDs) { std::set<std::string> generated_ids; for (int i = 0; i < 10000; ++i) { - std::string id = webdriver::GenerateRandomID(); + std::string id = GenerateRandomID(); ASSERT_EQ(32u, id.length()); ASSERT_TRUE(generated_ids.end() == generated_ids.find(id)) << "Generated duplicate ID: " << id @@ -19,3 +25,24 @@ TEST(RandomIDTest, CanGenerateSufficientlyRandomIDs) { generated_ids.insert(id); } } + +TEST(ZipFileTest, ZipEntryToZipArchive) { + ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + std::string data; + // A zip entry sent from a Java WebDriver client (v2.20) that contains a + // file with the contents "COW\n". + const char* kBase64ZipEntry = + "UEsDBBQACAAIAJpyXEAAAAAAAAAAAAAAAAAEAAAAdGVzdHP2D+" + "cCAFBLBwi/wAzGBgAAAAQAAAA="; + ASSERT_TRUE(base::Base64Decode(kBase64ZipEntry, &data)); + FilePath file; + std::string error_msg; + ASSERT_TRUE(UnzipSoleFile(temp_dir.path(), data, &file, &error_msg)) + << error_msg; + std::string contents; + ASSERT_TRUE(file_util::ReadFileToString(file, &contents)); + ASSERT_STREQ("COW\n", contents.c_str()); +} + +} // namespace webdriver |
