fb9a302561ab83cfc8286a4f15f8e6b7514ec4b9
[platform/framework/web/crosswalk.git] / src / xwalk / application / common / application_file_util.cc
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.
4
5 #include "xwalk/application/common/application_file_util.h"
6
7 #include <algorithm>
8 #include <map>
9 #include <vector>
10
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"
33
34 #if defined(OS_TIZEN)
35 #include "xwalk/application/common/id_util.h"
36 #endif
37
38 namespace errors = xwalk::application_manifest_errors;
39 namespace keys = xwalk::application_manifest_keys;
40 namespace widget_keys = xwalk::application_widget_keys;
41
42 namespace {
43 const char kAttributePrefix[] = "@";
44 const char kNamespaceKey[] = "@namespace";
45 const char kTextKey[] = "#text";
46
47 const char kContentKey[] = "content";
48
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";
57
58 const char kDirLTRKey[] = "ltr";
59 const char kDirRTLKey[] = "rtl";
60 const char kDirLROKey[] = "lro";
61 const char kDirRLOKey[] = "rlo";
62
63 const char* kSingletonElements[] = {
64   "allow-navigation",
65   "content-security-policy-report-only",
66   "content-security-policy",
67   "content"
68 };
69
70 inline char* ToCharPointer(void* ptr) {
71   return reinterpret_cast<char *>(ptr);
72 }
73
74 inline const char* ToConstCharPointer(const void* ptr) {
75   return reinterpret_cast<const char*>(ptr);
76 }
77
78 base::string16 ToSting16(const xmlChar* string_ptr) {
79   return base::UTF8ToUTF16(std::string(ToConstCharPointer(string_ptr)));
80 }
81
82 base::string16 GetDirText(const base::string16& text, const std::string& dir) {
83   if (dir == kDirLTRKey)
84     return base::i18n::kLeftToRightEmbeddingMark
85            + text
86            + base::i18n::kPopDirectionalFormatting;
87
88   if (dir == kDirRTLKey)
89     return base::i18n::kRightToLeftEmbeddingMark
90            + text
91            + base::i18n::kPopDirectionalFormatting;
92
93   if (dir == kDirLROKey)
94     return base::i18n::kLeftToRightOverride
95            + text
96            + base::i18n::kPopDirectionalFormatting;
97
98   if (dir == kDirRLOKey)
99     return base::i18n::kRightToLeftOverride
100            + text
101            + base::i18n::kPopDirectionalFormatting;
102
103   return text;
104 }
105
106 std::string GetNodeDir(xmlNode* node, const std::string& inherit_dir) {
107   DCHECK(node);
108   std::string dir(inherit_dir);
109
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));
115       dir = prop_value;
116       xmlFree(prop_value);
117       break;
118     }
119   }
120
121   return dir;
122 }
123
124 base::string16 GetNodeText(xmlNode* root, const std::string& inherit_dir) {
125   DCHECK(root);
126   if (root->type != XML_ELEMENT_NODE)
127     return base::string16();
128
129   std::string current_dir(GetNodeDir(root, inherit_dir));
130   base::string16 text;
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));
135     } else {
136       text = text + GetNodeText(node, current_dir);
137     }
138   }
139   return GetDirText(text, current_dir);
140 }
141
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))
147     return true;
148   if (xmlStrEqual(root->name, kNameNodeKey)
149      && xmlStrEqual(prop->name, kShortAttributeKey))
150     return true;
151   return false;
152 }
153
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))
162     return true;
163   return false;
164 }
165
166 // FIXME: This function is wrong and has to be re-implemented
167 // further (see XWALK-2230)
168 bool GetPackageType(const base::FilePath& path,
169                     xwalk::application::Package::Type* package_type,
170                     std::string* error) {
171   base::FilePath manifest_path;
172
173   manifest_path = path.Append(xwalk::application::kManifestXpkFilename);
174   if (base::PathExists(manifest_path)) {
175     *package_type = xwalk::application::Package::XPK;
176     return true;
177   }
178
179   manifest_path = path.Append(xwalk::application::kManifestWgtFilename);
180   if (base::PathExists(manifest_path)) {
181     *package_type = xwalk::application::Package::WGT;
182     return true;
183   }
184
185   *error = base::StringPrintf("%s", errors::kManifestUnreadable);
186   return false;
187 }
188
189 #if defined(OS_TIZEN)
190 bool GetPackageType(const std::string& application_id,
191                     xwalk::application::Package::Type* package_type,
192                     std::string* error) {
193   if (xwalk::application::IsValidWGTID(application_id)) {
194     *package_type = xwalk::application::Package::WGT;
195     return true;
196   } else if (xwalk::application::IsValidXPKID(application_id)) {
197     *package_type = xwalk::application::Package::XPK;
198     return true;
199   }
200
201   *error = base::StringPrintf("Invalid application id: %s",
202                               application_id.c_str());
203   return false;
204 }
205 #endif
206
207 bool IsSingletonElement(const std::string& name) {
208   for (int i = 0; i < arraysize(kSingletonElements); ++i)
209     if (kSingletonElements[i] == name)
210 #if defined(OS_TIZEN)
211       // On Tizen platform, need to check namespace of 'content'
212       // element further, a content element with tizen namespace
213       // will replace the one with widget namespace.
214       return name != kContentKey;
215 #else
216       return true;
217 #endif
218   return false;
219 }
220
221 }  // namespace
222
223 namespace xwalk {
224 namespace application {
225
226 FileDeleter::FileDeleter(const base::FilePath& path, bool recursive)
227     : path_(path),
228       recursive_(recursive) {}
229
230 FileDeleter::~FileDeleter() {
231   base::DeleteFile(path_, recursive_);
232 }
233
234 // Load XML node into Dictionary structure.
235 // The keys for the XML node to Dictionary mapping are described below:
236 // XML                                 Dictionary
237 // <e></e>                             "e":{"#text": ""}
238 // <e>textA</e>                        "e":{"#text":"textA"}
239 // <e attr="val">textA</e>             "e":{ "@attr":"val", "#text": "textA"}
240 // <e> <a>textA</a> <b>textB</b> </e>  "e":{
241 //                                       "a":{"#text":"textA"}
242 //                                       "b":{"#text":"textB"}
243 //                                     }
244 // <e> <a>textX</a> <a>textY</a> </e>  "e":{
245 //                                       "a":[ {"#text":"textX"},
246 //                                             {"#text":"textY"}]
247 //                                     }
248 // <e> textX <a>textY</a> </e>         "e":{ "#text":"textX",
249 //                                           "a":{"#text":"textY"}
250 //                                     }
251 //
252 // For elements that are specified under a namespace, the dictionary
253 // will add '@namespace' key for them, e.g.,
254 // XML:
255 // <e xmln="linkA" xmlns:N="LinkB">
256 //   <sub-e1> text1 </sub-e>
257 //   <N:sub-e2 text2 />
258 // </e>
259 // will be saved in Dictionary as,
260 // "e":{
261 //   "#text": "",
262 //   "@namespace": "linkA"
263 //   "sub-e1": {
264 //     "#text": "text1",
265 //     "@namespace": "linkA"
266 //   },
267 //   "sub-e2": {
268 //     "#text":"text2"
269 //     "@namespace": "linkB"
270 //   }
271 // }
272 base::DictionaryValue* LoadXMLNode(
273     xmlNode* root, const std::string& inherit_dir = "") {
274   scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
275   if (root->type != XML_ELEMENT_NODE)
276     return NULL;
277
278   std::string current_dir(GetNodeDir(root, inherit_dir));
279
280   xmlAttr* prop = NULL;
281   for (prop = root->properties; prop; prop = prop->next) {
282     xmlChar* value_ptr = xmlNodeListGetString(root->doc, prop->children, 1);
283     base::string16 prop_value(ToSting16(value_ptr));
284     xmlFree(value_ptr);
285
286     if (IsPropSupportDir(root, prop))
287       prop_value = GetDirText(prop_value, current_dir);
288
289     value->SetString(
290         std::string(kAttributePrefix) + ToConstCharPointer(prop->name),
291         prop_value);
292   }
293
294   if (root->ns)
295     value->SetString(kNamespaceKey, ToConstCharPointer(root->ns->href));
296
297   for (xmlNode* node = root->children; node; node = node->next) {
298     std::string sub_node_name(ToConstCharPointer(node->name));
299     base::DictionaryValue* sub_value = LoadXMLNode(node, current_dir);
300     if (!sub_value)
301       continue;
302
303     if (!value->HasKey(sub_node_name)) {
304       value->Set(sub_node_name, sub_value);
305       continue;
306     } else if (IsSingletonElement(sub_node_name)) {
307       continue;
308 #if defined(OS_TIZEN)
309     } else if (sub_node_name == kContentKey) {
310       std::string current_namespace, new_namespace;
311       base::DictionaryValue* current_value;
312       value->GetDictionary(sub_node_name, &current_value);
313
314       current_value->GetString(kNamespaceKey, &current_namespace);
315       sub_value->GetString(kNamespaceKey, &new_namespace);
316       if (current_namespace != new_namespace &&
317           new_namespace == kTizenNamespacePrefix)
318         value->Set(sub_node_name, sub_value);
319       continue;
320 #endif
321     }
322
323     base::Value* temp;
324     value->Get(sub_node_name, &temp);
325     DCHECK(temp);
326
327     if (temp->IsType(base::Value::TYPE_LIST)) {
328       base::ListValue* list;
329       temp->GetAsList(&list);
330       list->Append(sub_value);
331     } else {
332       DCHECK(temp->IsType(base::Value::TYPE_DICTIONARY));
333       base::DictionaryValue* dict;
334       temp->GetAsDictionary(&dict);
335       base::DictionaryValue* prev_value(new base::DictionaryValue());
336       prev_value = dict->DeepCopy();
337
338       base::ListValue* list = new base::ListValue();
339       list->Append(prev_value);
340       list->Append(sub_value);
341       value->Set(sub_node_name, list);
342     }
343   }
344
345   base::string16 text;
346   if (IsElementSupportSpanAndDir(root)) {
347     text = GetNodeText(root, current_dir);
348   } else {
349     xmlChar* text_ptr = xmlNodeListGetString(root->doc, root->children, 1);
350     if (text_ptr) {
351       text = ToSting16(text_ptr);
352       xmlFree(text_ptr);
353     }
354   }
355
356   if (!text.empty())
357     value->SetString(kTextKey, text);
358
359   return value.release();
360 }
361
362 scoped_refptr<ApplicationData> LoadApplication(
363     const base::FilePath& application_path,
364     ApplicationData::SourceType source_type,
365     std::string* error) {
366   Package::Type package_type;
367   if (!GetPackageType(application_path, &package_type, error))
368     return NULL;
369
370   return LoadApplication(application_path, std::string(),
371                          source_type, package_type, error);
372 }
373
374 scoped_refptr<ApplicationData> LoadApplication(
375     const base::FilePath& application_path,
376     const std::string& application_id,
377     ApplicationData::SourceType source_type,
378     std::string* error) {
379   Package::Type package_type;
380 #if defined(OS_TIZEN)
381   if (!GetPackageType(application_id, &package_type, error))
382 #else
383   if (!GetPackageType(application_path, &package_type, error))
384 #endif
385     return NULL;
386
387   return LoadApplication(application_path, application_id,
388                          source_type, package_type, error);
389 }
390
391 scoped_refptr<ApplicationData> LoadApplication(
392     const base::FilePath& application_path,
393     const std::string& application_id,
394     ApplicationData::SourceType source_type,
395     Package::Type package_type,
396     std::string* error) {
397   scoped_ptr<base::DictionaryValue> manifest(
398       LoadManifest(application_path, package_type, error));
399   if (!manifest.get())
400     return NULL;
401
402   scoped_refptr<ApplicationData> application = ApplicationData::Create(
403                                                              application_path,
404                                                              source_type,
405                                                              *manifest,
406                                                              application_id,
407                                                              error);
408   if (!application)
409     return NULL;
410
411   ManifestHandlerRegistry* registry =
412       manifest->HasKey(widget_keys::kWidgetKey)
413       ? ManifestHandlerRegistry::GetInstance(Package::WGT)
414       : ManifestHandlerRegistry::GetInstance(Package::XPK);
415
416   if (!registry->ValidateAppManifest(application, error))
417     return NULL;
418
419   return application;
420 }
421
422 static base::DictionaryValue* LoadManifestXpk(
423     const base::FilePath& manifest_path,
424     std::string* error) {
425   JSONFileValueSerializer serializer(manifest_path);
426   scoped_ptr<base::Value> root(serializer.Deserialize(NULL, error));
427   if (!root.get()) {
428     if (error->empty()) {
429       // If |error| is empty, than the file could not be read.
430       // It would be cleaner to have the JSON reader give a specific error
431       // in this case, but other code tests for a file error with
432       // error->empty().  For now, be consistent.
433       *error = base::StringPrintf("%s", errors::kManifestUnreadable);
434     } else {
435       *error = base::StringPrintf("%s  %s",
436                                   errors::kManifestParseError,
437                                   error->c_str());
438     }
439     return NULL;
440   }
441
442   if (!root->IsType(base::Value::TYPE_DICTIONARY)) {
443     *error = base::StringPrintf("%s", errors::kManifestUnreadable);
444     return NULL;
445   }
446
447   base::DictionaryValue* dv =
448       static_cast<base::DictionaryValue*>(root.release());
449 #if defined(OS_TIZEN)
450   // Ignore any Tizen application ID, as this is automatically generated.
451   dv->Remove(keys::kTizenAppIdKey, NULL);
452 #endif
453
454   return dv;
455 }
456
457 static base::DictionaryValue* LoadManifestWgt(
458     const base::FilePath& manifest_path,
459     std::string* error) {
460   xmlDoc * doc = NULL;
461   xmlNode* root_node = NULL;
462   doc = xmlReadFile(manifest_path.MaybeAsASCII().c_str(), NULL, 0);
463   if (doc == NULL) {
464     *error = base::StringPrintf("%s", errors::kManifestUnreadable);
465     return NULL;
466   }
467   root_node = xmlDocGetRootElement(doc);
468   base::DictionaryValue* dv = LoadXMLNode(root_node);
469   scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue);
470   if (dv)
471     result->Set(ToConstCharPointer(root_node->name), dv);
472
473   return result.release();
474 }
475
476 base::DictionaryValue* LoadManifest(const base::FilePath& application_path,
477                                     Package::Type package_type,
478                                     std::string* error) {
479   base::FilePath manifest_path;
480
481   manifest_path = application_path.Append(kManifestXpkFilename);
482   if (package_type == Package::XPK)
483     return LoadManifestXpk(manifest_path, error);
484
485   manifest_path = application_path.Append(kManifestWgtFilename);
486   if (package_type == Package::WGT)
487     return LoadManifestWgt(manifest_path, error);
488
489   *error = base::StringPrintf("%s", errors::kManifestUnreadable);
490   return NULL;
491 }
492
493 base::FilePath ApplicationURLToRelativeFilePath(const GURL& url) {
494   std::string url_path = url.path();
495   if (url_path.empty() || url_path[0] != '/')
496     return base::FilePath();
497
498   // Drop the leading slashes and convert %-encoded UTF8 to regular UTF8.
499   std::string file_path = net::UnescapeURLComponent(url_path,
500       net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS);
501   size_t skip = file_path.find_first_not_of("/\\");
502   if (skip != file_path.npos)
503     file_path = file_path.substr(skip);
504
505   base::FilePath path =
506 #if defined(OS_POSIX)
507     base::FilePath(file_path);
508 #elif defined(OS_WIN)
509     base::FilePath(base::UTF8ToWide(file_path));
510 #else
511     base::FilePath();
512     NOTIMPLEMENTED();
513 #endif
514
515   // It's still possible for someone to construct an annoying URL whose path
516   // would still wind up not being considered relative at this point.
517   // For example: app://id/c:////foo.html
518   if (path.IsAbsolute())
519     return base::FilePath();
520
521   return path;
522 }
523
524 }  // namespace application
525 }  // namespace xwalk