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 @@
'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',
......
// 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 @@
#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
......@@ -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_;
......
......@@ -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");
......
......@@ -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_;
}
......
......@@ -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.
......
This diff is collapsed.
......@@ -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);
......
// 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
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