summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/chrome_tests.gypi2
-rw-r--r--chrome/test/webdriver/commands/file_upload_command.cc63
-rw-r--r--chrome/test/webdriver/commands/file_upload_command.h36
-rw-r--r--chrome/test/webdriver/webdriver_capabilities_parser.cc48
-rw-r--r--chrome/test/webdriver/webdriver_capabilities_parser.h7
-rw-r--r--chrome/test/webdriver/webdriver_server.cc2
-rw-r--r--chrome/test/webdriver/webdriver_session.cc10
-rw-r--r--chrome/test/webdriver/webdriver_session.h5
-rw-r--r--chrome/test/webdriver/webdriver_util.cc359
-rw-r--r--chrome/test/webdriver/webdriver_util.h22
-rw-r--r--chrome/test/webdriver/webdriver_util_unittest.cc31
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", &copy);
+ 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