| // 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/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; |
| using base::ListValue; |
| using base::Value; |
| |
| namespace webdriver { |
| |
| SkipParsing* kSkipParsing = NULL; |
| |
| std::string GenerateRandomID() { |
| uint64 msb = base::RandUint64(); |
| uint64 lsb = base::RandUint64(); |
| 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 */, |
| 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, &json); |
| return json; |
| } |
| |
| namespace { |
| |
| // Truncates the given string to 100 characters, adding an ellipsis if |
| // truncation was necessary. |
| void TruncateString(std::string* data) { |
| const size_t kMaxLength = 100; |
| if (data->length() > kMaxLength) { |
| data->resize(kMaxLength); |
| data->replace(kMaxLength - 3, 3, "..."); |
| } |
| } |
| |
| // Truncates all strings contained in the given value. |
| void TruncateContainedStrings(Value* value) { |
| ListValue* list; |
| if (value->IsType(Value::TYPE_DICTIONARY)) { |
| DictionaryValue* dict = static_cast<DictionaryValue*>(value); |
| DictionaryValue::key_iterator key = dict->begin_keys(); |
| for (; key != dict->end_keys(); ++key) { |
| Value* child; |
| if (!dict->GetWithoutPathExpansion(*key, &child)) |
| continue; |
| std::string data; |
| if (child->GetAsString(&data)) { |
| TruncateString(&data); |
| dict->SetWithoutPathExpansion(*key, Value::CreateStringValue(data)); |
| } else { |
| TruncateContainedStrings(child); |
| } |
| } |
| } else if (value->GetAsList(&list)) { |
| for (size_t i = 0; i < list->GetSize(); ++i) { |
| Value* child; |
| if (!list->Get(i, &child)) |
| continue; |
| std::string data; |
| if (child->GetAsString(&data)) { |
| TruncateString(&data); |
| list->Set(i, Value::CreateStringValue(data)); |
| } else { |
| TruncateContainedStrings(child); |
| } |
| } |
| } |
| } |
| |
| } // namespace |
| |
| std::string JsonStringifyForDisplay(const Value* value) { |
| scoped_ptr<Value> copy; |
| if (value->IsType(Value::TYPE_STRING)) { |
| std::string data; |
| value->GetAsString(&data); |
| TruncateString(&data); |
| copy.reset(Value::CreateStringValue(data)); |
| } else { |
| copy.reset(value->DeepCopy()); |
| TruncateContainedStrings(copy.get()); |
| } |
| std::string json; |
| base::JSONWriter::WriteWithOptions(copy.get(), |
| base::JSONWriter::OPTIONS_PRETTY_PRINT, |
| &json); |
| return json; |
| } |
| |
| const char* GetJsonTypeName(Value::Type type) { |
| switch (type) { |
| case Value::TYPE_NULL: |
| return "null"; |
| case Value::TYPE_BOOLEAN: |
| return "boolean"; |
| case Value::TYPE_INTEGER: |
| return "integer"; |
| case Value::TYPE_DOUBLE: |
| return "double"; |
| case Value::TYPE_STRING: |
| return "string"; |
| case Value::TYPE_BINARY: |
| return "binary"; |
| case Value::TYPE_DICTIONARY: |
| return "dictionary"; |
| case Value::TYPE_LIST: |
| return "list"; |
| } |
| return "unknown"; |
| } |
| |
| std::string AutomationIdToString(const AutomationId& id) { |
| return base::StringPrintf("%d-%s", id.type(), id.id().c_str()); |
| } |
| |
| bool StringToAutomationId(const std::string& string_id, AutomationId* id) { |
| std::vector<std::string> split_id; |
| base::SplitString(string_id, '-', &split_id); |
| if (split_id.size() != 2) |
| return false; |
| int type; |
| if (!base::StringToInt(split_id[0], &type)) |
| return false; |
| *id = AutomationId(static_cast<AutomationId::Type>(type), split_id[1]); |
| return true; |
| } |
| |
| std::string WebViewIdToString(const WebViewId& view_id) { |
| return base::StringPrintf( |
| "%s%s", |
| view_id.old_style() ? "t" : "f", |
| AutomationIdToString(view_id.GetId()).c_str()); |
| } |
| |
| bool StringToWebViewId(const std::string& string_id, WebViewId* view_id) { |
| if (string_id.empty() || (string_id[0] != 'f' && string_id[0] != 't')) |
| return false; |
| bool old_style = string_id[0] == 't'; |
| AutomationId id; |
| if (!StringToAutomationId(string_id.substr(1), &id)) |
| return false; |
| |
| if (old_style) { |
| int tab_id; |
| if (!base::StringToInt(id.id(), &tab_id)) |
| return false; |
| *view_id = WebViewId::ForOldStyleTab(tab_id); |
| } else { |
| *view_id = WebViewId::ForView(id); |
| } |
| return true; |
| } |
| |
| Error* FlattenStringArray(const ListValue* src, string16* dest) { |
| string16 keys; |
| for (size_t i = 0; i < src->GetSize(); ++i) { |
| string16 keys_list_part; |
| src->GetString(i, &keys_list_part); |
| for (size_t j = 0; j < keys_list_part.size(); ++j) { |
| if (CBU16_IS_SURROGATE(keys_list_part[j])) { |
| return new Error(kBadRequest, |
| "ChromeDriver only supports characters in the BMP"); |
| } |
| } |
| keys.append(keys_list_part); |
| } |
| *dest = keys; |
| return NULL; |
| } |
| |
| ValueParser::ValueParser() { } |
| |
| ValueParser::~ValueParser() { } |
| |
| } // namespace webdriver |
| |
| bool ValueConversionTraits<webdriver::SkipParsing>::SetFromValue( |
| const Value* value, const webdriver::SkipParsing* t) { |
| return true; |
| } |
| |
| bool ValueConversionTraits<webdriver::SkipParsing>::CanConvert( |
| const Value* value) { |
| return true; |
| } |