Commit d1f1b75f authored by kkania@chromium.org's avatar kkania@chromium.org

[chromedriver] Add command for uploading a file to a remote ChromeDriver server,

which can be used when performing a file upload using sendKeys.

Unfortunately, past and current versions of some WebDriver clients,
including java, only upload a zip entry and not an actual zip. This can't be
parsed by chromium's zip utilities or minizip. The WebDriver clients should
be changed to send an actual zip file. In the meantime, we append a central
directory on to the zip entry and then unzip it.
BUG=chromedriver:18
TEST=none


Review URL: http://codereview.chromium.org/9515005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@125004 0039d316-1c4b-4281-b951-d872f2087c98
parent 66495020
...@@ -1018,6 +1018,8 @@ ...@@ -1018,6 +1018,8 @@
'test/webdriver/commands/execute_async_script_command.h', 'test/webdriver/commands/execute_async_script_command.h',
'test/webdriver/commands/execute_command.cc', 'test/webdriver/commands/execute_command.cc',
'test/webdriver/commands/execute_command.h', '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.cc',
'test/webdriver/commands/find_element_commands.h', 'test/webdriver/commands/find_element_commands.h',
'test/webdriver/commands/html5_storage_commands.cc', 'test/webdriver/commands/html5_storage_commands.cc',
......
// 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
// 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_
...@@ -4,14 +4,12 @@ ...@@ -4,14 +4,12 @@
#include "chrome/test/webdriver/webdriver_capabilities_parser.h" #include "chrome/test/webdriver/webdriver_capabilities_parser.h"
#include "base/base64.h"
#include "base/file_util.h" #include "base/file_util.h"
#include "base/format_macros.h" #include "base/format_macros.h"
#include "base/stringprintf.h" #include "base/stringprintf.h"
#include "base/string_util.h" #include "base/string_util.h"
#include "base/values.h" #include "base/values.h"
#include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_switches.h"
#include "chrome/common/zip.h"
#include "chrome/test/webdriver/webdriver_error.h" #include "chrome/test/webdriver/webdriver_error.h"
#include "chrome/test/webdriver/webdriver_util.h" #include "chrome/test/webdriver/webdriver_util.h"
...@@ -202,13 +200,13 @@ Error* CapabilitiesParser::ParseExtensions(const Value* option) { ...@@ -202,13 +200,13 @@ Error* CapabilitiesParser::ParseExtensions(const Value* option) {
} }
FilePath extension = root_.AppendASCII( FilePath extension = root_.AppendASCII(
base::StringPrintf("extension%" PRIuS ".crx", i)); base::StringPrintf("extension%" PRIuS ".crx", i));
std::string error_msg; std::string decoded_extension;
if (!DecodeAndWriteFile(extension, extension_base64, false /* unzip */, if (!Base64Decode(extension_base64, &decoded_extension))
&error_msg)) { return new Error(kUnknownError, "Failed to base64 decode extension");
return new Error( int size = static_cast<int>(decoded_extension.length());
kUnknownError, if (file_util::WriteFile(
"Error occurred while parsing extension: " + error_msg); extension, decoded_extension.c_str(), size) != size)
} return new Error(kUnknownError, "Failed to write extension file");
caps_->extensions.push_back(extension); caps_->extensions.push_back(extension);
} }
return NULL; return NULL;
...@@ -273,8 +271,7 @@ Error* CapabilitiesParser::ParseProfile(const Value* option) { ...@@ -273,8 +271,7 @@ Error* CapabilitiesParser::ParseProfile(const Value* option) {
return CreateBadInputError("profile", Value::TYPE_STRING, option); return CreateBadInputError("profile", Value::TYPE_STRING, option);
std::string error_msg; std::string error_msg;
caps_->profile = root_.AppendASCII("profile"); caps_->profile = root_.AppendASCII("profile");
if (!DecodeAndWriteFile(caps_->profile, profile_base64, true /* unzip */, if (!Base64DecodeAndUnzip(caps_->profile, profile_base64, &error_msg))
&error_msg))
return new Error(kUnknownError, "unable to unpack profile: " + error_msg); return new Error(kUnknownError, "unable to unpack profile: " + error_msg);
return NULL; return NULL;
} }
...@@ -432,33 +429,4 @@ Error* CapabilitiesParser::ParseNoWebsiteTestingDefaults(const Value* option) { ...@@ -432,33 +429,4 @@ Error* CapabilitiesParser::ParseNoWebsiteTestingDefaults(const Value* option) {
return NULL; 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 } // namespace webdriver
...@@ -111,13 +111,6 @@ class CapabilitiesParser { ...@@ -111,13 +111,6 @@ class CapabilitiesParser {
Error* ParseProxyAutoconfigUrl(const base::DictionaryValue* options); Error* ParseProxyAutoconfigUrl(const base::DictionaryValue* options);
Error* ParseProxyServers(const base::DictionaryValue* options); Error* ParseProxyServers(const base::DictionaryValue* options);
Error* ParseNoWebsiteTestingDefaults(const base::Value* option); 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. // The capabilities dictionary to parse.
const base::DictionaryValue* dict_; const base::DictionaryValue* dict_;
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#include "chrome/test/webdriver/commands/create_session.h" #include "chrome/test/webdriver/commands/create_session.h"
#include "chrome/test/webdriver/commands/execute_async_script_command.h" #include "chrome/test/webdriver/commands/execute_async_script_command.h"
#include "chrome/test/webdriver/commands/execute_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/find_element_commands.h"
#include "chrome/test/webdriver/commands/html5_storage_commands.h" #include "chrome/test/webdriver/commands/html5_storage_commands.h"
#include "chrome/test/webdriver/commands/keys_command.h" #include "chrome/test/webdriver/commands/keys_command.h"
...@@ -148,6 +149,7 @@ void InitCallbacks(Dispatcher* dispatcher, ...@@ -148,6 +149,7 @@ void InitCallbacks(Dispatcher* dispatcher,
"/session/*/timeouts/async_script"); "/session/*/timeouts/async_script");
dispatcher->Add<ImplicitWaitCommand>( "/session/*/timeouts/implicit_wait"); dispatcher->Add<ImplicitWaitCommand>( "/session/*/timeouts/implicit_wait");
dispatcher->Add<LogCommand>( "/session/*/log"); dispatcher->Add<LogCommand>( "/session/*/log");
dispatcher->Add<FileUploadCommand>( "/session/*/file");
// Cookie functions. // Cookie functions.
dispatcher->Add<CookieCommand>( "/session/*/cookie"); dispatcher->Add<CookieCommand>( "/session/*/cookie");
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
#include "base/message_loop_proxy.h" #include "base/message_loop_proxy.h"
#include "base/process.h" #include "base/process.h"
#include "base/process_util.h" #include "base/process_util.h"
#include "base/scoped_temp_dir.h"
#include "base/string_number_conversions.h" #include "base/string_number_conversions.h"
#include "base/string_split.h" #include "base/string_split.h"
#include "base/string_util.h" #include "base/string_util.h"
...@@ -88,8 +87,7 @@ Error* Session::Init(const DictionaryValue* capabilities_dict) { ...@@ -88,8 +87,7 @@ Error* Session::Init(const DictionaryValue* capabilities_dict) {
delete this; delete this;
return new Error(kUnknownError, "Cannot start session thread"); return new Error(kUnknownError, "Cannot start session thread");
} }
ScopedTempDir temp_dir; if (!temp_dir_.CreateUniqueTempDir()) {
if (!temp_dir.CreateUniqueTempDir()) {
delete this; delete this;
return new Error( return new Error(
kUnknownError, "Unable to create temp directory for unpacking"); kUnknownError, "Unable to create temp directory for unpacking");
...@@ -98,7 +96,7 @@ Error* Session::Init(const DictionaryValue* capabilities_dict) { ...@@ -98,7 +96,7 @@ Error* Session::Init(const DictionaryValue* capabilities_dict) {
"Initializing session with capabilities " + "Initializing session with capabilities " +
JsonStringifyForDisplay(capabilities_dict)); JsonStringifyForDisplay(capabilities_dict));
CapabilitiesParser parser( CapabilitiesParser parser(
capabilities_dict, temp_dir.path(), logger_, &capabilities_); capabilities_dict, temp_dir_.path(), logger_, &capabilities_);
Error* error = parser.Parse(); Error* error = parser.Parse();
if (error) { if (error) {
delete this; delete this;
...@@ -1409,6 +1407,10 @@ const Logger& Session::logger() const { ...@@ -1409,6 +1407,10 @@ const Logger& Session::logger() const {
return logger_; return logger_;
} }
const FilePath& Session::temp_dir() const {
return temp_dir_.path();
}
const Capabilities& Session::capabilities() const { const Capabilities& Session::capabilities() const {
return capabilities_; return capabilities_;
} }
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/callback_forward.h" #include "base/callback_forward.h"
#include "base/file_path.h" #include "base/file_path.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/scoped_temp_dir.h"
#include "base/string16.h" #include "base/string16.h"
#include "base/threading/thread.h" #include "base/threading/thread.h"
#include "chrome/common/automation_constants.h" #include "chrome/common/automation_constants.h"
...@@ -383,6 +384,8 @@ class Session { ...@@ -383,6 +384,8 @@ class Session {
const Logger& logger() const; const Logger& logger() const;
const FilePath& temp_dir() const;
const Capabilities& capabilities() const; const Capabilities& capabilities() const;
private: private:
...@@ -471,6 +474,8 @@ class Session { ...@@ -471,6 +474,8 @@ class Session {
std::string alert_prompt_text_; std::string alert_prompt_text_;
bool has_alert_prompt_text_; bool has_alert_prompt_text_;
// Temporary directory containing session data.
ScopedTempDir temp_dir_;
Capabilities capabilities_; Capabilities capabilities_;
// Current state of all modifier keys. // Current state of all modifier keys.
......
This diff is collapsed.
...@@ -24,6 +24,28 @@ namespace webdriver { ...@@ -24,6 +24,28 @@ namespace webdriver {
// Generates a random, 32-character hexidecimal ID. // Generates a random, 32-character hexidecimal ID.
std::string GenerateRandomID(); 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. // Returns the equivalent JSON string for the given value.
std::string JsonStringify(const base::Value* value); std::string JsonStringify(const base::Value* value);
......
// 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 // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include <set> #include <set>
#include <string> #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 "chrome/test/webdriver/webdriver_util.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace webdriver {
TEST(RandomIDTest, CanGenerateSufficientlyRandomIDs) { TEST(RandomIDTest, CanGenerateSufficientlyRandomIDs) {
std::set<std::string> generated_ids; std::set<std::string> generated_ids;
for (int i = 0; i < 10000; ++i) { for (int i = 0; i < 10000; ++i) {
std::string id = webdriver::GenerateRandomID(); std::string id = GenerateRandomID();
ASSERT_EQ(32u, id.length()); ASSERT_EQ(32u, id.length());
ASSERT_TRUE(generated_ids.end() == generated_ids.find(id)) ASSERT_TRUE(generated_ids.end() == generated_ids.find(id))
<< "Generated duplicate ID: " << id << "Generated duplicate ID: " << id
...@@ -19,3 +25,24 @@ TEST(RandomIDTest, CanGenerateSufficientlyRandomIDs) { ...@@ -19,3 +25,24 @@ TEST(RandomIDTest, CanGenerateSufficientlyRandomIDs) {
generated_ids.insert(id); 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment