| Index: chrome/test/webdriver/webdriver_util.cc
|
| diff --git a/chrome/test/webdriver/webdriver_util.cc b/chrome/test/webdriver/webdriver_util.cc
|
| index bab42b87999ec4de550a5aa5f64be36ad3d50cdf..44f7cafe2a9393b74e873bd560e4b24131275fd7 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);
|
|
|