Merge pull request #32 from szulak/launch_screen
[platform/framework/web/crosswalk-tizen.git] / common / resource_manager.cc
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 #include "common/resource_manager.h"
18
19 #include <sys/types.h>
20 #include <aul.h>
21 #include <pkgmgr-info.h>
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <web_app_enc.h>
25
26 #include <algorithm>
27 #include <functional>
28 #include <memory>
29 #include <set>
30 #include <sstream>
31 #include <vector>
32
33 #include "common/application_data.h"
34 #include "common/app_control.h"
35 #include "common/file_utils.h"
36 #include "common/locale_manager.h"
37 #include "common/logger.h"
38 #include "common/string_utils.h"
39 #include "common/url.h"
40
41 using wgt::parse::AppControlInfo;
42
43 namespace common {
44
45 namespace {
46
47 typedef std::vector<AppControlInfo> AppControlList;
48
49 // Scheme type
50 const char* kSchemeTypeApp = "app://";
51 const char* kSchemeTypeFile = "file://";
52 const char* kSchemeTypeHttp = "http://";
53 const char* kSchemeTypeHttps = "https://";
54 // lendth of scheme identifier ://
55 const int kSchemeIdLen = 3;
56 // TODO(wy80.choi): comment out below unused const variables if needed.
57 // const char* kSchemeTypeWidget = "widget://";
58
59 // Default Start Files
60 const char* kDefaultStartFiles[] = {
61     "index.htm",
62     "index.html",
63     "index.svg",
64     "index.xhtml",
65     "index.xht"};
66
67 // Default Encoding
68 const char* kDefaultEncoding = "UTF-8";
69
70 // EncryptedFileExtensions
71 const std::set<std::string> kEncryptedFileExtensions{
72     ".html", ".htm", ".css", ".js"};
73
74 static std::string GetMimeFromUri(const std::string& uri) {
75   // checking passed uri is local file
76   std::string file_uri_case(kSchemeTypeFile);
77   int ret = AUL_R_EINVAL;
78   char mimetype[128] = {0, };
79   size_t pos = std::string::npos;
80   if (utils::StartsWith(uri, file_uri_case)) {
81     // case 1. uri = file:///xxxx
82     ret = aul_get_mime_from_file(uri.substr(pos+file_uri_case.length()).c_str(),
83                                  mimetype, sizeof(mimetype));
84   } else if (utils::StartsWith(uri, "/")) {
85     // case 2. uri = /xxxx
86     ret = aul_get_mime_from_file(uri.c_str(),
87                                  mimetype, sizeof(mimetype));
88   }
89
90   if (ret == AUL_R_OK) {
91     return std::string(mimetype);
92   } else {
93     return std::string();
94   }
95 }
96
97 static bool CompareMime(const std::string& info_mime,
98                         const std::string& request_mime) {
99   // suppose that these mimetypes are valid expressions ('type'/'sub-type')
100   if (info_mime == "*" || info_mime == "*/*")
101     return true;
102
103   if (request_mime.empty())
104     return info_mime.empty();
105
106   std::string info_type;
107   std::string info_sub;
108   std::string request_type;
109   std::string request_sub;
110   if (!(utils::SplitString(info_mime, &info_type, &info_sub, '/') &&
111         utils::SplitString(request_mime, &request_type, &request_sub, '/')))
112     return false;
113
114   return info_type == request_type &&
115          (info_sub == "*") ? true : (info_sub == request_sub);
116 }
117
118 static bool CompareUri(const std::string& info_uri,
119                        const std::string& request_uri) {
120   if (request_uri.empty())
121     return info_uri.empty();
122
123   std::string info_scheme = utils::SchemeName(info_uri);
124
125   // if has only scheme or scheme+star. ex) http, http://, http://*
126   if (!info_scheme.empty() &&
127       (info_uri == info_scheme || utils::EndsWith(info_uri, "://")
128         || utils::EndsWith(info_uri, "://*"))) {
129     return utils::SchemeName(request_uri) == info_scheme;
130   }
131
132   if (utils::EndsWith(info_uri, "*")) {
133     return utils::StartsWith(request_uri, info_uri.substr(0,
134                                                           info_uri.length()-1));
135   } else {
136     return request_uri == info_uri;
137   }
138 }
139
140 static std::string InsertPrefixPath(const std::string& start_uri) {
141   if (start_uri.find("://") != std::string::npos)
142     return start_uri;
143   else
144     return std::string(kSchemeTypeFile) + "/" + start_uri;
145 }
146
147 }  // namespace
148
149 ResourceManager::Resource::Resource(const std::string& uri)
150   : uri_(uri), should_reset_(true), encoding_(kDefaultEncoding) {}
151
152 ResourceManager::Resource::Resource(const std::string& uri, bool should_reset)
153   : uri_(uri), should_reset_(should_reset), encoding_(kDefaultEncoding) {}
154
155 ResourceManager::Resource::Resource(const std::string& uri,
156                                     const std::string& mime,
157                                     const std::string& encoding)
158   : uri_(uri), mime_(mime), should_reset_(true), encoding_(encoding) {}
159
160 ResourceManager::Resource::Resource(const ResourceManager::Resource& res) {
161   *this = res;
162 }
163
164 ResourceManager::Resource& ResourceManager::Resource::operator=(
165     const ResourceManager::Resource& res) {
166   this->uri_ = res.uri();
167   this->mime_ = res.mime();
168   this->should_reset_ = res.should_reset();
169   this->encoding_ = res.encoding();
170   return *this;
171 }
172
173 bool ResourceManager::Resource::operator==(const Resource& res) {
174   if (this->uri_ == res.uri() && this->mime_ == res.mime()
175      && this->should_reset_ == res.should_reset()
176      && this->encoding_ == res.encoding()) {
177     return true;
178   } else {
179     return false;
180   }
181 }
182
183 ResourceManager::ResourceManager(ApplicationData* application_data,
184                                  LocaleManager* locale_manager)
185     : application_data_(application_data), locale_manager_(locale_manager) {
186   if (application_data != NULL) {
187     appid_ = application_data->tizen_application_info()->id();
188     if (application_data->csp_info() != NULL ||
189         application_data->csp_report_info() != NULL ||
190         application_data->allowed_navigation_info() != NULL) {
191       security_model_version_ = 2;
192     } else {
193       security_model_version_ = 1;
194     }
195   }
196 }
197
198 std::unique_ptr<ResourceManager::Resource>
199 ResourceManager::GetDefaultResource() {
200   std::string src;
201   std::string type;
202   std::string encoding = kDefaultEncoding;
203   auto content_info = application_data_->content_info();
204   if (content_info) {
205     src = content_info->src();
206     type = content_info->type();
207     encoding = content_info->encoding();
208     LOGGER(DEBUG) << "src: " << src;
209     LOGGER(DEBUG) << "type: " << type;
210     LOGGER(DEBUG) << "encoding: " << encoding;
211   } else {
212     LOGGER(DEBUG) << "content_info is null";
213   }
214
215   // Check that tizen:content src is external page
216   if (content_info && content_info->is_tizen_content()
217       && (utils::StartsWith(src, kSchemeTypeHttp) ||
218           utils::StartsWith(src, kSchemeTypeHttps))) {
219     LOGGER(DEBUG) << "tizen content_info's src is an external page";
220     return std::unique_ptr<Resource>(new Resource(src, type, encoding));
221   }
222
223   // Find based on default start files list, if src is empty or invald
224   if (!content_info || !utils::Exists(resource_base_path_+src)) {
225     for (auto& start_file : kDefaultStartFiles) {
226       if (utils::Exists(resource_base_path_ + start_file)) {
227         src = InsertPrefixPath(start_file);
228         LOGGER(DEBUG) << "start file: " << src;
229         return std::unique_ptr<Resource>(new Resource(src, type, encoding));
230       }
231     }
232     // shouldn't be entered here
233     LOGGER(ERROR) << "it can't enter here. can't find any default start file";
234     return std::unique_ptr<Resource>(new Resource(src, type, encoding));
235   }
236
237   return std::unique_ptr<Resource>(new Resource(InsertPrefixPath(src),
238                                                 type, encoding));
239 }
240
241 std::unique_ptr<ResourceManager::Resource> ResourceManager::GetMatchedResource(
242     const AppControlInfo& app_control_info) {
243   if (!app_control_info.src().empty()) {
244     return std::unique_ptr<Resource>(new Resource(
245       InsertPrefixPath(app_control_info.src()),
246                        app_control_info.reload() == "disable" ? false : true));
247   }
248   return GetDefaultResource();
249 }
250
251 std::unique_ptr<ResourceManager::Resource> ResourceManager::GetStartResource(
252     const AppControl* app_control) {
253   std::string operation = app_control->operation();
254   if (operation.empty()) {
255     LOGGER(ERROR) << "operation(mandatory) is NULL";
256     return GetDefaultResource();
257   }
258
259   std::string mime = app_control->mime();
260   std::string uri = app_control->uri();
261   if (mime.empty() && !uri.empty()) {
262     mime = GetMimeFromUri(uri);
263   }
264
265   LOGGER(DEBUG) << "Passed AppControl data";
266   LOGGER(DEBUG) << " - operation : " << operation;
267   LOGGER(DEBUG) << " - mimetype  : " << mime;
268   LOGGER(DEBUG) << " - uri       : " << uri;
269
270   if (application_data_ == NULL ||
271       application_data_->app_control_info_list() == NULL) {
272     return GetDefaultResource();
273   }
274
275   auto app_control_list =
276     application_data_->app_control_info_list()->controls;
277
278   AppControlList::const_iterator iter = std::find_if(
279     app_control_list.begin(), app_control_list.end(),
280     [&operation, &uri, &mime](AppControlInfo& info) -> bool {
281       return (info.operation() == operation)
282         && CompareMime(info.mime(), mime) && CompareUri(info.uri(), uri); });
283
284   if (iter != app_control_list.end()) {
285     return GetMatchedResource(*iter);
286   } else {
287   return GetDefaultResource();
288   }
289 }
290
291 std::string ResourceManager::GetLocalizedPath(const std::string& origin) {
292   std::string file_scheme = std::string() + kSchemeTypeFile + "/";
293   std::string app_scheme = std::string() + kSchemeTypeApp;
294   std::string locale_path = "locales/";
295   auto find = locale_cache_.find(origin);
296   if (find != locale_cache_.end()) {
297     return find->second;
298   }
299   std::string& result = locale_cache_[origin];
300   result = origin;
301   std::string url = origin;
302
303   std::string suffix;
304   size_t pos = url.find_first_of("#?");
305   if (pos != std::string::npos) {
306     suffix = url.substr(pos);
307     url.resize(pos);
308   }
309
310   if (utils::StartsWith(url, app_scheme)) {
311     // remove "app://"
312     url.erase(0, app_scheme.length());
313
314     // remove app id + /
315     std::string check = appid_ + "/";
316     if (utils::StartsWith(url, check)) {
317       url.erase(0, check.length());
318     } else {
319       LOGGER(ERROR) << "Invalid appid";
320       return result;
321     }
322   } else if (utils::StartsWith(url, file_scheme)) {
323     // remove "file:///"
324     url.erase(0, file_scheme.length());
325   }
326
327   if (!url.empty() && url[url.length()-1] == '/') {
328       url.erase(url.length()-1, 1);
329   }
330
331   if (url.empty()) {
332     LOGGER(ERROR) << "URL Localization error";
333     return result;
334   }
335
336   std::string file_path = utils::UrlDecode(RemoveLocalePath(url));
337   for (auto& locales : locale_manager_->system_locales()) {
338     // check ../locales/
339     std::string app_locale_path = resource_base_path_ + locale_path;
340     if (!Exists(app_locale_path)) {
341       break;
342     }
343
344     // check locale path ../locales/en_us/
345     std::string app_localized_path = app_locale_path + locales + "/";
346     if (!Exists(app_localized_path)) {
347       continue;
348     }
349     std::string resource_path = app_localized_path + file_path;
350     if (Exists(resource_path)) {
351       result = "file://" + resource_path + suffix;
352       return result;
353     }
354   }
355
356   std::string default_locale = resource_base_path_ + file_path;
357   if (Exists(default_locale)) {
358     result = "file://" + default_locale + suffix;
359     return result;
360   }
361
362   LOGGER(ERROR) << "URL Localization error";
363   return result;
364 }
365
366 std::string ResourceManager::RemoveLocalePath(const std::string& path) {
367   std::string locale_path = "locales/";
368   std::string result_path = path.at(0) == '/' ? path : "/" + path;
369   if (!utils::StartsWith(result_path, resource_base_path_)) {
370     return path;
371   }
372
373   result_path = result_path.substr(resource_base_path_.length());
374   if (!utils::StartsWith(result_path, locale_path)) {
375     return result_path;
376   }
377
378   size_t found = result_path.find_first_of('/', locale_path.length());
379   if (found != std::string::npos) {
380     result_path = result_path.substr(found+1);
381   }
382   return result_path;
383 }
384
385 void ResourceManager::set_base_resource_path(const std::string& path) {
386   if (path.empty()) {
387     return;
388   }
389
390   resource_base_path_ = path;
391   if (resource_base_path_[resource_base_path_.length()-1] != '/') {
392     resource_base_path_ += "/";
393   }
394 }
395
396 bool ResourceManager::Exists(const std::string& path) {
397   auto find = file_existed_cache_.find(path);
398   if (find != file_existed_cache_.end()) {
399     return find->second;
400   }
401   bool ret = file_existed_cache_[path] = utils::Exists(path);
402   return ret;
403 }
404
405 bool ResourceManager::AllowNavigation(const std::string& url) {
406   if (security_model_version_ == 2)
407     return CheckAllowNavigation(url);
408   return CheckWARP(url);
409 }
410
411 bool ResourceManager::AllowedResource(const std::string& url) {
412   if (security_model_version_ == 2)
413     return true;
414   return CheckWARP(url);
415 }
416
417 bool ResourceManager::CheckWARP(const std::string& url) {
418   // allow non-external resource
419   if (!utils::StartsWith(url, kSchemeTypeHttp) &&
420       !utils::StartsWith(url, kSchemeTypeHttps)) {
421     return true;
422   }
423
424   auto warp = application_data_->warp_info();
425   if (warp.get() == NULL)
426     return false;
427
428   auto find = warp_cache_.find(url);
429   if (find != warp_cache_.end()) {
430     return find->second;
431   }
432
433   bool& result = warp_cache_[url];
434   result = true;
435
436   URL url_info(url);
437
438   // if didn't have a scheme, it means local resource
439   if (url_info.scheme().empty()) {
440     return true;
441   }
442
443   for (auto& allow : warp->access_map()) {
444     if (allow.first == "*") {
445       return true;
446     } else if (allow.first.empty()) {
447       continue;
448     }
449
450     URL allow_url(allow.first);
451
452     // should be match the scheme and port
453     if (allow_url.scheme() != url_info.scheme() ||
454         allow_url.port() != url_info.port()) {
455       continue;
456     }
457
458     // if domain alos was matched, allow resource
459     if (allow_url.domain() == url_info.domain()) {
460       return true;
461     } else if (allow.second) {
462       // if does not match domain, should be check sub domain
463
464       // filter : test.com , subdomain=true
465       // url : aaa.test.com
466       // check url was ends with ".test.com"
467       if (utils::EndsWith(url_info.domain(), "." + allow_url.domain())) {
468         return true;
469       }
470     }
471   }
472
473   return result = false;
474 }
475
476 bool ResourceManager::CheckAllowNavigation(const std::string& url) {
477   // allow non-external resource
478   if (!utils::StartsWith(url, kSchemeTypeHttp) &&
479       !utils::StartsWith(url, kSchemeTypeHttps)) {
480     return true;
481   }
482
483   auto allow = application_data_->allowed_navigation_info();
484   if (allow.get() == NULL)
485     return false;
486
487   auto find = warp_cache_.find(url);
488   if (find != warp_cache_.end()) {
489     return find->second;
490   }
491
492   bool& result = warp_cache_[url];
493   result = true;
494
495   URL url_info(url);
496
497   // if didn't have a scheme, it means local resource
498   if (url_info.scheme().empty()) {
499     return true;
500   }
501
502   for (auto& allow_domain : allow->GetAllowedDomains()) {
503     URL a_domain_info(allow_domain);
504
505     // check wildcard *
506     if (a_domain_info.domain() == "*") {
507       return true;
508     }
509
510     bool prefix_wild = false;
511     bool suffix_wild = false;
512     std::string a_domain = a_domain_info.domain();
513     if (utils::StartsWith(a_domain, "*.")) {
514       prefix_wild = true;
515       // *.domain.com -> .domain.com
516       a_domain = a_domain.substr(1);
517     }
518     if (utils::EndsWith(a_domain, ".*")) {
519       suffix_wild = true;
520       // domain.* -> domain.
521       a_domain = a_domain.substr(0, a_domain.length() - 1);
522     }
523
524     if (!prefix_wild && !suffix_wild) {
525       // if no wildcard, should be exactly matched
526       if (url_info.domain() == a_domain) {
527         return true;
528       }
529     } else if (prefix_wild && !suffix_wild) {
530       // *.domain.com : it shoud be "domain.com" or end with ".domain.com"
531       if (url_info.domain() == a_domain.substr(1) ||
532           utils::EndsWith(url_info.domain(), a_domain)) {
533         return true;
534       }
535     } else if (!prefix_wild && suffix_wild) {
536       // www.sample.* : it should be starts with "www.sample."
537       if (utils::StartsWith(url_info.domain(), a_domain)) {
538         return true;
539       }
540     } else if (prefix_wild && suffix_wild) {
541       // *.sample.* : it should be starts with sample. or can find ".sample."
542       // in url
543       if (utils::StartsWith(url_info.domain(), a_domain.substr(1)) ||
544           std::string::npos != url_info.domain().find(a_domain)) {
545         return true;
546       }
547     }
548   }
549
550   return result = false;
551 }
552
553 bool ResourceManager::IsEncrypted(const std::string& path) {
554   auto setting = application_data_->setting_info();
555   if (setting.get() == NULL)
556     return false;
557
558   if (setting->encryption_enabled()) {
559     std::string ext = utils::ExtName(path);
560     if (kEncryptedFileExtensions.count(ext) > 0) {
561       return true;
562     }
563   }
564   return false;
565 }
566
567 std::string ResourceManager::DecryptResource(const std::string& path) {
568   // read file and make a buffer
569   std::string src_path(path);
570   if (utils::StartsWith(src_path, kSchemeTypeFile)) {
571     src_path.erase(0, strlen(kSchemeTypeFile));
572   }
573
574   FILE *src = fopen(src_path.c_str(), "rb");
575   if (!src) {
576     LOGGER(ERROR) << "Cannot open file for decryption: " << src_path;
577     return path;
578   }
579
580   // Read filesize
581   fseek(src, 0, SEEK_END);
582   size_t src_len = ftell(src);
583   if (src_len == 0) {
584     // if the file exists and is empty, bypass it
585     fclose(src);
586     return path;
587   }
588   rewind(src);
589
590   // Read buffer from the source file
591   std::unique_ptr<char[]> src_buf(new char[src_len]);
592   if (src_len != fread(src_buf.get(), sizeof(char), src_len, src)) {
593     LOGGER(ERROR) << "Read error, file: " << src_path;
594     fclose(src);
595     return path;
596   }
597   fclose(src);
598
599   // checking web app type
600   static bool inited = false;
601   static bool is_global = false;
602   static bool is_preload = false;
603
604   int ret;
605   std::string pkg_id = application_data_->pkg_id();
606   if (!inited) {
607     inited = true;
608     pkgmgrinfo_pkginfo_h handle;
609     ret = pkgmgrinfo_pkginfo_get_usr_pkginfo(pkg_id.c_str(), getuid(), &handle);
610     if (ret != PMINFO_R_OK) {
611       LOGGER(ERROR) << "Could not get handle for pkginfo : pkg_id = "
612                     << pkg_id;
613       return path;
614     } else {
615       ret = pkgmgrinfo_pkginfo_is_global(handle, &is_global);
616       if (ret != PMINFO_R_OK) {
617         LOGGER(ERROR) << "Could not check is_global : pkg_id = "
618                       << pkg_id;
619         pkgmgrinfo_pkginfo_destroy_pkginfo(handle);
620         return path;
621       }
622       ret = pkgmgrinfo_pkginfo_is_preload(handle, &is_preload);
623       if (ret != PMINFO_R_OK) {
624         LOGGER(ERROR) << "Could not check is_preload : pkg_id = "
625                       << pkg_id;
626         pkgmgrinfo_pkginfo_destroy_pkginfo(handle);
627         return path;
628       }
629       pkgmgrinfo_pkginfo_destroy_pkginfo(handle);
630     }
631   }
632
633   wae_app_type_e app_type = WAE_DOWNLOADED_NORMAL_APP;
634   if (is_global) {
635     app_type = WAE_DOWNLOADED_GLOBAL_APP;
636   } else if (is_preload) {
637     app_type = WAE_PRELOADED_APP;
638   }
639
640   // decrypt buffer with wae functions
641   uint8_t* dst_buf = nullptr;
642   size_t dst_len = 0;
643   ret = wae_decrypt_web_application(pkg_id.c_str(),
644                                     app_type,
645                                     reinterpret_cast<uint8_t*>(src_buf.get()),
646                                     src_len,
647                                     &dst_buf,
648                                     &dst_len);
649   if (WAE_ERROR_NONE != ret) {
650     switch (ret) {
651     case WAE_ERROR_INVALID_PARAMETER:
652       LOGGER(ERROR) << "Error during decryption: WAE_ERROR_INVALID_PARAMETER";
653       break;
654     case WAE_ERROR_PERMISSION_DENIED:
655       LOGGER(ERROR) << "Error during decryption: WAE_ERROR_PERMISSION_DENIED";
656       break;
657     case WAE_ERROR_NO_KEY:
658       LOGGER(ERROR) << "Error during decryption: WAE_ERROR_NO_KEY";
659       break;
660     case WAE_ERROR_KEY_MANAGER:
661       LOGGER(ERROR) << "Error during decryption: WAE_ERROR_KEY_MANAGER";
662       break;
663     case WAE_ERROR_CRYPTO:
664       LOGGER(ERROR) << "Error during decryption: WAE_ERROR_CRYPTO";
665       break;
666     case WAE_ERROR_UNKNOWN:
667       LOGGER(ERROR) << "Error during decryption: WAE_ERROR_UNKNOWN";
668       break;
669     default:
670       LOGGER(ERROR) << "Error during decryption: UNKNOWN";
671       break;
672     }
673     return path;
674   }
675
676   // change to data schem
677   std::stringstream dst_str;
678   std::string content_type = GetMimeFromUri(path);
679   std::string encoded = utils::Base64Encode(dst_buf, dst_len);
680   dst_str << "data:" << content_type << ";base64," << encoded;
681
682   std::free(dst_buf);
683
684   return dst_str.str();
685 }
686
687 }  // namespace common