1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "xwalk/application/common/application_file_util.h"
11 #include "base/command_line.h"
12 #include "base/files/file_path.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/file_util.h"
15 #include "base/i18n/rtl.h"
16 #include "base/json/json_file_value_serializer.h"
17 #include "base/logging.h"
18 #include "base/metrics/histogram.h"
19 #include "base/path_service.h"
20 #include "base/strings/string16.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/threading/thread_restrictions.h"
24 #include "net/base/escape.h"
25 #include "net/base/file_stream.h"
26 #include "third_party/libxml/src/include/libxml/tree.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "xwalk/application/common/application_data.h"
29 #include "xwalk/application/common/application_manifest_constants.h"
30 #include "xwalk/application/common/constants.h"
31 #include "xwalk/application/common/manifest.h"
32 #include "xwalk/application/common/manifest_handler.h"
35 #include "xwalk/application/common/id_util.h"
38 namespace errors = xwalk::application_manifest_errors;
39 namespace keys = xwalk::application_manifest_keys;
40 namespace widget_keys = xwalk::application_widget_keys;
43 const char kAttributePrefix[] = "@";
44 const char kNamespaceKey[] = "@namespace";
45 const char kTextKey[] = "#text";
47 const char kContentKey[] = "content";
49 const xmlChar kWidgetNodeKey[] = "widget";
50 const xmlChar kNameNodeKey[] = "name";
51 const xmlChar kDescriptionNodeKey[] = "description";
52 const xmlChar kAuthorNodeKey[] = "author";
53 const xmlChar kLicenseNodeKey[] = "license";
54 const xmlChar kVersionAttributeKey[] = "version";
55 const xmlChar kShortAttributeKey[] = "short";
56 const xmlChar kDirAttributeKey[] = "dir";
58 const char kDirLTRKey[] = "ltr";
59 const char kDirRTLKey[] = "rtl";
60 const char kDirLROKey[] = "lro";
61 const char kDirRLOKey[] = "rlo";
63 const char* kSingletonElements[] = {
65 "content-security-policy-report-only",
66 "content-security-policy",
70 inline char* ToCharPointer(void* ptr) {
71 return reinterpret_cast<char *>(ptr);
74 inline const char* ToConstCharPointer(const void* ptr) {
75 return reinterpret_cast<const char*>(ptr);
78 base::string16 ToSting16(const xmlChar* string_ptr) {
79 return base::UTF8ToUTF16(std::string(ToConstCharPointer(string_ptr)));
82 base::string16 GetDirText(const base::string16& text, const std::string& dir) {
83 if (dir == kDirLTRKey)
84 return base::i18n::kLeftToRightEmbeddingMark
86 + base::i18n::kPopDirectionalFormatting;
88 if (dir == kDirRTLKey)
89 return base::i18n::kRightToLeftEmbeddingMark
91 + base::i18n::kPopDirectionalFormatting;
93 if (dir == kDirLROKey)
94 return base::i18n::kLeftToRightOverride
96 + base::i18n::kPopDirectionalFormatting;
98 if (dir == kDirRLOKey)
99 return base::i18n::kRightToLeftOverride
101 + base::i18n::kPopDirectionalFormatting;
106 std::string GetNodeDir(xmlNode* node, const std::string& inherit_dir) {
108 std::string dir(inherit_dir);
110 xmlAttr* prop = NULL;
111 for (prop = node->properties; prop; prop = prop->next) {
112 if (xmlStrEqual(prop->name, kDirAttributeKey)) {
113 char* prop_value = ToCharPointer(xmlNodeListGetString(
114 node->doc, prop->children, 1));
124 base::string16 GetNodeText(xmlNode* root, const std::string& inherit_dir) {
126 if (root->type != XML_ELEMENT_NODE)
127 return base::string16();
129 std::string current_dir(GetNodeDir(root, inherit_dir));
131 for (xmlNode* node = root->children; node; node = node->next) {
132 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
133 text = text + base::i18n::StripWrappingBidiControlCharacters(
134 ToSting16(node->content));
136 text = text + GetNodeText(node, current_dir);
139 return GetDirText(text, current_dir);
142 // According to widget specification, this two prop need to support dir.
143 // see detail on http://www.w3.org/TR/widgets/#the-dir-attribute
144 inline bool IsPropSupportDir(xmlNode* root, xmlAttr* prop) {
145 if (xmlStrEqual(root->name, kWidgetNodeKey)
146 && xmlStrEqual(prop->name, kVersionAttributeKey))
148 if (xmlStrEqual(root->name, kNameNodeKey)
149 && xmlStrEqual(prop->name, kShortAttributeKey))
154 // Only this four items need to support span and ignore other element.
155 // Besides xmlNodeListGetString can not support dir prop of span.
156 // See http://www.w3.org/TR/widgets/#the-span-element-and-its-attributes
157 inline bool IsElementSupportSpanAndDir(xmlNode* root) {
158 if (xmlStrEqual(root->name, kNameNodeKey)
159 || xmlStrEqual(root->name, kDescriptionNodeKey)
160 || xmlStrEqual(root->name, kAuthorNodeKey)
161 || xmlStrEqual(root->name, kLicenseNodeKey))
166 bool IsSingletonElement(const std::string& name) {
167 for (int i = 0; i < arraysize(kSingletonElements); ++i)
168 if (kSingletonElements[i] == name)
169 #if defined(OS_TIZEN)
170 // On Tizen platform, need to check namespace of 'content'
171 // element further, a content element with tizen namespace
172 // will replace the one with widget namespace.
173 return name != kContentKey;
183 namespace application {
185 FileDeleter::FileDeleter(const base::FilePath& path, bool recursive)
187 recursive_(recursive) {}
189 FileDeleter::~FileDeleter() {
190 base::DeleteFile(path_, recursive_);
195 // Load XML node into Dictionary structure.
196 // The keys for the XML node to Dictionary mapping are described below:
198 // <e></e> "e":{"#text": ""}
199 // <e>textA</e> "e":{"#text":"textA"}
200 // <e attr="val">textA</e> "e":{ "@attr":"val", "#text": "textA"}
201 // <e> <a>textA</a> <b>textB</b> </e> "e":{
202 // "a":{"#text":"textA"}
203 // "b":{"#text":"textB"}
205 // <e> <a>textX</a> <a>textY</a> </e> "e":{
206 // "a":[ {"#text":"textX"},
207 // {"#text":"textY"}]
209 // <e> textX <a>textY</a> </e> "e":{ "#text":"textX",
210 // "a":{"#text":"textY"}
213 // For elements that are specified under a namespace, the dictionary
214 // will add '@namespace' key for them, e.g.,
216 // <e xmln="linkA" xmlns:N="LinkB">
217 // <sub-e1> text1 </sub-e>
218 // <N:sub-e2 text2 />
220 // will be saved in Dictionary as,
223 // "@namespace": "linkA"
226 // "@namespace": "linkA"
230 // "@namespace": "linkB"
233 base::DictionaryValue* LoadXMLNode(
234 xmlNode* root, const std::string& inherit_dir = "") {
235 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
236 if (root->type != XML_ELEMENT_NODE)
239 std::string current_dir(GetNodeDir(root, inherit_dir));
241 xmlAttr* prop = NULL;
242 for (prop = root->properties; prop; prop = prop->next) {
243 xmlChar* value_ptr = xmlNodeListGetString(root->doc, prop->children, 1);
244 base::string16 prop_value(ToSting16(value_ptr));
247 if (IsPropSupportDir(root, prop))
248 prop_value = GetDirText(prop_value, current_dir);
251 std::string(kAttributePrefix) + ToConstCharPointer(prop->name),
256 value->SetString(kNamespaceKey, ToConstCharPointer(root->ns->href));
258 for (xmlNode* node = root->children; node; node = node->next) {
259 std::string sub_node_name(ToConstCharPointer(node->name));
260 base::DictionaryValue* sub_value = LoadXMLNode(node, current_dir);
264 if (!value->HasKey(sub_node_name)) {
265 value->Set(sub_node_name, sub_value);
267 } else if (IsSingletonElement(sub_node_name)) {
269 #if defined(OS_TIZEN)
270 } else if (sub_node_name == kContentKey) {
271 std::string current_namespace, new_namespace;
272 base::DictionaryValue* current_value;
273 value->GetDictionary(sub_node_name, ¤t_value);
275 current_value->GetString(kNamespaceKey, ¤t_namespace);
276 sub_value->GetString(kNamespaceKey, &new_namespace);
277 if (current_namespace != new_namespace &&
278 new_namespace == kTizenNamespacePrefix)
279 value->Set(sub_node_name, sub_value);
285 value->Get(sub_node_name, &temp);
288 if (temp->IsType(base::Value::TYPE_LIST)) {
289 base::ListValue* list;
290 temp->GetAsList(&list);
291 list->Append(sub_value);
293 DCHECK(temp->IsType(base::Value::TYPE_DICTIONARY));
294 base::DictionaryValue* dict;
295 temp->GetAsDictionary(&dict);
296 base::DictionaryValue* prev_value(new base::DictionaryValue());
297 prev_value = dict->DeepCopy();
299 base::ListValue* list = new base::ListValue();
300 list->Append(prev_value);
301 list->Append(sub_value);
302 value->Set(sub_node_name, list);
307 if (IsElementSupportSpanAndDir(root)) {
308 text = GetNodeText(root, current_dir);
310 xmlChar* text_ptr = xmlNodeListGetString(root->doc, root->children, 1);
312 text = ToSting16(text_ptr);
318 value->SetString(kTextKey, text);
320 return value.release();
325 template <Manifest::Type>
326 scoped_ptr<Manifest> LoadManifest(
327 const base::FilePath& manifest_path, std::string* error);
330 scoped_ptr<Manifest> LoadManifest<Manifest::TYPE_MANIFEST>(
331 const base::FilePath& manifest_path, std::string* error) {
332 JSONFileValueSerializer serializer(manifest_path);
333 scoped_ptr<base::Value> root(serializer.Deserialize(NULL, error));
335 if (error->empty()) {
336 // If |error| is empty, than the file could not be read.
337 // It would be cleaner to have the JSON reader give a specific error
338 // in this case, but other code tests for a file error with
339 // error->empty(). For now, be consistent.
340 *error = base::StringPrintf("%s", errors::kManifestUnreadable);
342 *error = base::StringPrintf("%s %s",
343 errors::kManifestParseError, error->c_str());
345 return scoped_ptr<Manifest>();
348 if (!root->IsType(base::Value::TYPE_DICTIONARY)) {
349 *error = base::StringPrintf("%s", errors::kManifestUnreadable);
350 return scoped_ptr<Manifest>();
353 scoped_ptr<base::DictionaryValue> dv = make_scoped_ptr(
354 static_cast<base::DictionaryValue*>(root.release()));
355 #if defined(OS_TIZEN)
356 // Ignore any Tizen application ID, as this is automatically generated.
357 dv->Remove(keys::kTizenAppIdKey, NULL);
360 return make_scoped_ptr(new Manifest(dv.Pass(), Manifest::TYPE_MANIFEST));
364 scoped_ptr<Manifest> LoadManifest<Manifest::TYPE_WIDGET>(
365 const base::FilePath& manifest_path,
366 std::string* error) {
368 xmlNode* root_node = NULL;
369 doc = xmlReadFile(manifest_path.MaybeAsASCII().c_str(), NULL, 0);
371 *error = base::StringPrintf("%s", errors::kManifestUnreadable);
372 return scoped_ptr<Manifest>();
374 root_node = xmlDocGetRootElement(doc);
375 base::DictionaryValue* dv = LoadXMLNode(root_node);
376 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue);
378 result->Set(ToConstCharPointer(root_node->name), dv);
380 return make_scoped_ptr(new Manifest(result.Pass(), Manifest::TYPE_WIDGET));
383 scoped_ptr<Manifest> LoadManifest(const base::FilePath& manifest_path,
384 Manifest::Type type, std::string* error) {
385 if (type == Manifest::TYPE_MANIFEST)
386 return LoadManifest<Manifest::TYPE_MANIFEST>(manifest_path, error);
388 if (type == Manifest::TYPE_WIDGET)
389 return LoadManifest<Manifest::TYPE_WIDGET>(manifest_path, error);
391 *error = base::StringPrintf("%s", errors::kManifestUnreadable);
392 return scoped_ptr<Manifest>();
395 base::FilePath GetManifestPath(
396 const base::FilePath& app_directory, Manifest::Type type) {
397 base::FilePath manifest_path;
399 case Manifest::TYPE_WIDGET:
400 manifest_path = app_directory.Append(kManifestWgtFilename);
402 case Manifest::TYPE_MANIFEST:
403 manifest_path = app_directory.Append(kManifestXpkFilename);
409 return manifest_path;
412 scoped_refptr<ApplicationData> LoadApplication(
413 const base::FilePath& app_root, const std::string& app_id,
414 ApplicationData::SourceType source_type, Manifest::Type manifest_type,
415 std::string* error) {
416 base::FilePath manifest_path = GetManifestPath(app_root, manifest_type);
418 scoped_ptr<Manifest> manifest = LoadManifest(
419 manifest_path, manifest_type, error);
423 return ApplicationData::Create(
424 app_root, app_id, source_type, manifest.Pass(), error);
427 base::FilePath ApplicationURLToRelativeFilePath(const GURL& url) {
428 std::string url_path = url.path();
429 if (url_path.empty() || url_path[0] != '/')
430 return base::FilePath();
432 // Drop the leading slashes and convert %-encoded UTF8 to regular UTF8.
433 std::string file_path = net::UnescapeURLComponent(url_path,
434 net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS);
435 size_t skip = file_path.find_first_not_of("/\\");
436 if (skip != file_path.npos)
437 file_path = file_path.substr(skip);
439 base::FilePath path =
440 #if defined(OS_POSIX)
441 base::FilePath(file_path);
442 #elif defined(OS_WIN)
443 base::FilePath(base::UTF8ToWide(file_path));
449 // It's still possible for someone to construct an annoying URL whose path
450 // would still wind up not being considered relative at this point.
451 // For example: app://id/c:////foo.html
452 if (path.IsAbsolute())
453 return base::FilePath();
458 } // namespace application