6addca69befb2ede9e765080bf4824941b45dee7
[framework/web/wrt-installer.git] / src / jobs / widget_install / task_widget_config.cpp
1 /*
2  * Copyright (c) 2011 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  * @file    task_widget_config.cpp
18  * @author  Przemyslaw Dobrowolski (p.dobrowolsk@samsung.com)
19  * @version 1.0
20  * @brief   Implementation file for installer task widget config
21  */
22 #include <string>
23 #include <sstream>
24 #include <dpl/foreach.h>
25 #include <dpl/errno_string.h>
26 #include <dpl/wrt-dao-rw/feature_dao.h>
27 #include <dpl/wrt-dao-ro/global_config.h>
28 #include <dpl/utils/wrt_utility.h>
29 #include <root_parser.h>
30 #include <widget_parser.h>
31 #include <parser_runner.h>
32 #include <libiriwrapper.h>
33 #include <widget_install/task_widget_config.h>
34 #include <widget_install/job_widget_install.h>
35 #include <widget_install/widget_install_errors.h>
36 #include <widget_install/widget_install_context.h>
37 #include <dpl/utils/file_utils.h>
38 #include <dpl/utils/mime_type_utils.h>
39 #include <sys/stat.h>
40 #include <dpl/utils/wrt_global_settings.h>
41
42 namespace { // anonymous
43 const WidgetHandle WIDGET_HANDLE_START_VALUE = 1000;
44 const DPL::String WIDGET_HEAD = DPL::FromUTF8String("Widget information");
45 const std::string OK_BUTTON_LABEL = "OK";
46 const std::string CANCEL_BUTTON_LABEL = "Cancel";
47 const DPL::String BR = DPL::FromUTF8String("<br>");
48 const DPL::String DOUBLE_BR = DPL::FromUTF8String("<br><br>");
49 const DPL::String FEATURE_HEAD = DPL::FromUTF8String("Feature information");
50 const DPL::String WIDGET_NOT_COMPATIBLE = DPL::FromUTF8String("This widget is "
51         "not compatible with WRT. Do you want install it anyway?");
52 } // namespace anonymous
53
54 namespace Jobs {
55 namespace WidgetInstall {
56 void InstallerTaskWidgetPopupData::PopupData::addWidgetInfo(
57         const DPL::String &head,
58         const DPL::String &info)
59 {
60     widgetInfo += head;
61     widgetInfo += DOUBLE_BR;
62     widgetInfo += info;
63     widgetInfo += BR;
64 }
65
66 TaskWidgetConfig::TaskWidgetConfig(InstallerContext& installContext) :
67     DPL::TaskDecl<TaskWidgetConfig>(this),
68     m_installContext(installContext),
69     m_installCancel(false)
70 {
71     AddStep(&TaskWidgetConfig::StepProcessConfigurationFile);
72     AddStep(&TaskWidgetConfig::ReadLocaleFolders);
73     AddStep(&TaskWidgetConfig::ProcessLocalizedStartFiles);
74     AddStep(&TaskWidgetConfig::ProcessLocalizedIcons);
75     AddStep(&TaskWidgetConfig::StepVerifyFeatures);
76
77     //in case of tests, no popups are shown
78     if (GlobalSettings::GetPopupsEnabledFlag()) {
79         AddStep(&TaskWidgetConfig::StepShowWidgetInfo);
80         AddStep(&TaskWidgetConfig::StepCancelWidgetInstallation);
81         AddStep(&TaskWidgetConfig::StepCheckMinVersionInfo);
82         AddStep(&TaskWidgetConfig::StepCancelWidgetInstallationAfterMinVersion);
83     }
84 }
85
86 void TaskWidgetConfig::StepProcessConfigurationFile()
87 {
88     Try
89     {
90         WidgetConfigurationManagerSingleton::Instance().processFile(
91             m_installContext.tempWidgetPath,
92             m_installContext.widgetConfig);
93     }
94     Catch(WidgetConfigurationManager::Exception::ProcessFailed)
95     {
96         LogError("Parsing failed.");
97         ReThrow(Exceptions::WidgetConfigFileInvalid);
98     }
99     Try
100     {
101         // To get widget type for distribute WAC, TIZEN WebApp
102         setApplicationType();
103     }
104     Catch(WidgetConfigurationManager::Exception::ProcessFailed)
105     {
106         LogError("Config.xml has more than one namespace");
107         ReThrow(Exceptions::WidgetConfigFileInvalid);
108     }
109
110     m_installContext.job->SetProgressFlag(true);
111     m_installContext.job->UpdateProgress(
112         InstallerContext::INSTALL_WIDGET_CONFIG1,
113         "Parsing was suscessfull");
114 }
115
116 void TaskWidgetConfig::ReadLocaleFolders()
117 {
118     LogDebug("Reading locale");
119     //Adding default locale
120     m_localeFolders.insert(L"");
121
122     std::string localePath = m_installContext.tempWidgetPath + "/locales";
123     DIR* localeDir = opendir(localePath.c_str());
124     if (!localeDir) {
125         LogDebug("No /locales directory in the widget package.");
126         return;
127     }
128
129     struct dirent* dirent;
130     struct stat statStruct;
131     do {
132         errno = 0;
133         if ((dirent = readdir(localeDir))) {
134             DPL::String dirName = DPL::FromUTF8String(dirent->d_name);
135             std::string absoluteDirName = localePath + "/";
136             absoluteDirName += dirent->d_name;
137
138             if (stat(absoluteDirName.c_str(), &statStruct) != 0) {
139                 LogError("stat() failed with " << DPL::GetErrnoString());
140                 continue;
141             }
142
143             if (S_ISDIR(statStruct.st_mode)) {
144                 //Yes, we ignore current, parent & hidden directories
145                 if (dirName[0] != L'.') {
146                     LogDebug("Adding locale directory \"" << dirName << "\"");
147                     m_localeFolders.insert(dirName);
148                 }
149             }
150         }
151     }
152     while (dirent);
153
154     if (errno != 0) {
155         LogError("readdir() failed with " << DPL::GetErrnoString());
156     }
157
158     if (closedir(localeDir)) {
159         LogError("closedir() failed with " << DPL::GetErrnoString());
160     }
161 }
162
163 void TaskWidgetConfig::ProcessLocalizedStartFiles()
164 {
165     typedef DPL::String S;
166     ProcessStartFile(
167         m_installContext.widgetConfig.configInfo.startFile,
168         m_installContext.widgetConfig.configInfo.
169             startFileContentType,
170         m_installContext.widgetConfig.configInfo.startFileEncoding,
171         true);
172     ProcessStartFile(S(L"index.htm"), S(L"text/html"));
173     ProcessStartFile(S(L"index.html"), S(L"text/html"));
174     ProcessStartFile(S(L"index.svg"), S(L"image/svg+xml"));
175     ProcessStartFile(S(L"index.xhtml"), S(L"application/xhtml+xml"));
176     ProcessStartFile(S(L"index.xht"), S(L"application/xhtml+xml"));
177     // TODO: (l.wrzosek) we need better check if in current locales widget is valid.
178     FOREACH(it, m_installContext.widgetConfig.localizationData.startFiles) {
179         if (it->propertiesForLocales.size() > 0) {
180             return;
181         }
182     }
183     ThrowMsg(Exceptions::WidgetConfigFileInvalid,
184              L"The Widget has no valid start file");
185 }
186
187 void TaskWidgetConfig::ProcessStartFile(const DPL::OptionalString& path,
188         const DPL::OptionalString& type,
189         const DPL::OptionalString& encoding,
190         bool typeForcedInConfig)
191 {
192     using namespace WrtDB;
193
194     if (!!path) {
195         WidgetRegisterInfo::LocalizedStartFile startFileData;
196         startFileData.path = *path;
197
198         FOREACH(i, m_localeFolders) {
199             DPL::String pathPrefix = *i;
200             if (!pathPrefix.empty()) {
201                 pathPrefix = L"locales/" + pathPrefix + L"/";
202             }
203
204             DPL::String relativePath = pathPrefix + *path;
205             DPL::String absolutePath = DPL::FromUTF8String(
206                     m_installContext.tempWidgetPath) + L"/" + relativePath;
207
208             // get property data from packaged app
209             if (FileUtils::FileExists(absolutePath)) {
210                 WidgetRegisterInfo::StartFileProperties startFileProperties;
211                 if (!!type) {
212                     startFileProperties.type = *type;
213                 } else {
214                     startFileProperties.type =
215                         MimeTypeUtils::identifyFileMimeType(absolutePath);
216                 }
217
218                 //proceed only if MIME type is supported
219                 if (MimeTypeUtils::isMimeTypeSupportedForStartFile(
220                         startFileProperties.type))
221                 {
222                     if (!!encoding) {
223                         startFileProperties.encoding = *encoding;
224                     } else {
225                         MimeTypeUtils::MimeAttributes attributes =
226                             MimeTypeUtils::getMimeAttributes(
227                             startFileProperties.type);
228                         if (attributes.count(L"charset") > 0) {
229                             startFileProperties.encoding =
230                                 attributes[L"charset"];
231                         } else {
232                             startFileProperties.encoding = L"UTF-8";
233                         }
234                     }
235
236                     startFileData.propertiesForLocales[*i] =
237                         startFileProperties;
238                 } else {
239                     //9.1.16.5.content.8
240                     //(there seems to be no similar requirement in .6,
241                     //so let's throw only when mime type is
242                     // provided explcitly in config.xml)
243                     if (typeForcedInConfig) {
244                         ThrowMsg(Exceptions::WidgetConfigFileInvalid,
245                                  "Unsupported MIME type for start file.");
246                     }
247                 }
248             } else {
249                 // set property data for hosted start url
250                 // Hosted start url only support TIZEN WebApp
251                 if (m_installContext.widgetConfig.type ==
252                         APP_TYPE_TIZENWEBAPP)
253                 {
254                     const char *startPath =
255                         DPL::ToUTF8String(startFileData.path).c_str();
256                     if (strstr(startPath, "http") == startPath) {
257                         WidgetRegisterInfo::StartFileProperties
258                             startFileProperties;
259                         if (!!type) {
260                             startFileProperties.type = *type;
261                         }
262                         if (!!encoding) {
263                             startFileProperties.encoding = *encoding;
264                         }
265                         startFileData.propertiesForLocales[*i] =
266                             startFileProperties;
267                     }
268                 }
269             }
270         }
271
272         m_installContext.widgetConfig.localizationData.startFiles.push_back(
273             startFileData);
274     }
275 }
276
277 void TaskWidgetConfig::ProcessLocalizedIcons()
278 {
279     using namespace WrtDB;
280     ProcessIcon(ConfigParserData::Icon(L"icon.svg"));
281     ProcessIcon(ConfigParserData::Icon(L"icon.ico"));
282     ProcessIcon(ConfigParserData::Icon(L"icon.png"));
283     ProcessIcon(ConfigParserData::Icon(L"icon.gif"));
284     ProcessIcon(ConfigParserData::Icon(L"icon.jpg"));
285     ProcessIcon(ConfigParserData::Icon(
286             DPL::FromUTF8String(m_installContext.iconPath)));
287
288     FOREACH(i, m_installContext.widgetConfig.configInfo.iconsList)
289     {
290         ProcessIcon(*i);
291     }
292 }
293
294 void TaskWidgetConfig::ProcessIcon(const WrtDB::ConfigParserData::Icon& icon)
295 {
296     bool isAnyIconExisted = false;
297     //In case a default filename is passed as custom filename in config.xml, we
298     //need to keep a set of already processed filenames to avoid icon duplication
299     //in database.
300
301     using namespace WrtDB;
302
303     if (m_processedIconSet.count(icon.src) > 0) {
304         return;
305     }
306     m_processedIconSet.insert(icon.src);
307
308     LocaleSet localesAvailableForIcon;
309
310     FOREACH(i, m_localeFolders)
311     {
312         DPL::String pathPrefix = *i;
313         if (!pathPrefix.empty()) {
314             pathPrefix = L"locales/" + pathPrefix + L"/";
315         }
316
317         DPL::String relativePath = pathPrefix + icon.src;
318         DPL::String absolutePath = DPL::FromUTF8String(
319                 m_installContext.tempWidgetPath) + L"/" + relativePath;
320
321         if (FileUtils::FileExists(absolutePath)) {
322             isAnyIconExisted = true;
323             DPL::String type = MimeTypeUtils::identifyFileMimeType(absolutePath);
324
325             if (MimeTypeUtils::isMimeTypeSupportedForIcon(type)) {
326                 localesAvailableForIcon.insert(*i);
327             }
328             LogInfo("Icon absolutePath :" << absolutePath <<
329                     ", assigned locale :" << *i);
330         }
331     }
332
333     if(isAnyIconExisted)
334     {
335         WidgetRegisterInfo::LocalizedIcon localizedIcon(icon,
336                                                         localesAvailableForIcon);
337         m_installContext.widgetConfig.localizationData.icons.push_back(
338             localizedIcon);
339     }
340 }
341
342 void TaskWidgetConfig::AnswerCallback(const DPL::Popup::AnswerCallbackData &answer)
343 {
344     LogInfo("Callback called");
345     if (WRT_POPUP_BUTTON_CANCEL == answer.buttonAnswer) {
346         m_installCancel = WRT_POPUP_BUTTON_CANCEL;
347     }
348     m_installContext.job->Resume();
349 }
350
351 void TaskWidgetConfig::StepCancelWidgetInstallation()
352 {
353     if (WRT_POPUP_BUTTON_CANCEL == m_installCancel) {
354         ThrowMsg(Exceptions::NotAllowed, "Widget not allowed");
355     }
356 }
357
358 void TaskWidgetConfig::StepCancelWidgetInstallationAfterMinVersion()
359 {
360     if (WRT_POPUP_BUTTON_CANCEL == m_installCancel) {
361         ThrowMsg(Exceptions::NotAllowed, "WRT version incompatible.");
362     }
363 }
364
365 void TaskWidgetConfig::PopupCreate()
366 {
367     m_installContext.job->Pause();
368     using namespace DPL::Popup;
369     CtrlPopupPtr popup = PopupControllerSingleton::Instance().CreatePopup();
370     popup->SetTitle(DPL::ToUTF8String(WIDGET_HEAD));
371     popup->Append(new PopupObject::Label(
372                       DPL::ToUTF8String(m_popupData.widgetInfo)));
373     m_popupData.widgetInfo.clear();
374     popup->Append(new PopupObject::Button(OK_BUTTON_LABEL,
375                                            WRT_POPUP_BUTTON_OK));
376     popup->Append(new PopupObject::Button(CANCEL_BUTTON_LABEL,
377                                            WRT_POPUP_BUTTON_CANCEL));
378     ListenForAnswer(popup);
379     ShowPopupEvent event(popup, MakeAnswerCallback(
380                              this,
381                              &TaskWidgetConfig::
382                                  AnswerCallback), DPL::Event::UNDEFINED_LOOP_HANDLE);
383     CONTROLLER_POST_EVENT(PopupController, event);
384 }
385
386 void TaskWidgetConfig::PopupMinVersionConfirmationCreate()
387 {
388     m_installContext.job->Pause();
389     LogDebug("Creating minVersion confirmation popup.");
390     using namespace DPL::Popup;
391     CtrlPopupPtr popup = PopupControllerSingleton::Instance().CreatePopup();
392     popup->SetTitle(DPL::ToUTF8String(WIDGET_HEAD));
393     popup->Append(new PopupObject::Label(
394             DPL::ToUTF8String(WIDGET_NOT_COMPATIBLE)));
395     popup->Append(new PopupObject::Button(OK_BUTTON_LABEL,
396                                            WRT_POPUP_BUTTON_OK));
397     popup->Append(new PopupObject::Button(CANCEL_BUTTON_LABEL,
398                                            WRT_POPUP_BUTTON_CANCEL));
399     ListenForAnswer(popup);
400     ShowPopupEvent event(popup,
401             MakeAnswerCallback(this, &TaskWidgetConfig::AnswerCallback),
402             DPL::Event::UNDEFINED_LOOP_HANDLE);
403     CONTROLLER_POST_EVENT(PopupController, event);
404 }
405
406 void TaskWidgetConfig::StepShowWidgetInfo()
407 {
408     if (!m_popupData.widgetInfo.empty()) {
409         PopupCreate();
410         m_installContext.job->UpdateProgress(
411             InstallerContext::INSTALL_WIDGET_CONFIG2,
412             "Show Widget Info Finished");
413     }
414 }
415
416 void TaskWidgetConfig::StepCheckMinVersionInfo()
417 {
418     if (!isWACVersionCompatible(m_installContext.widgetConfig.minVersion)) {
419         PopupMinVersionConfirmationCreate();
420         m_installContext.job->UpdateProgress(
421             InstallerContext::INSTALL_WIDGET_CONFIG2,
422             "Show MinVersion Info Finished");
423     }
424 }
425
426 void TaskWidgetConfig::StepVerifyFeatures()
427 {
428     using namespace WrtDB;
429     ConfigParserData &data = m_installContext.widgetConfig.configInfo;
430     ConfigParserData::FeaturesList list = data.featuresList;
431     ConfigParserData::FeaturesList newList;
432
433     //in case of tests, this variable is unused
434     std::string featureInfo;
435     FOREACH(it, list)
436     {
437         // check feature vender for permission
438         // WAC, TIZEN WebApp cannot use other feature
439         if (!isFeatureAllowed(m_installContext.widgetConfig.type.appType,
440                               it->name))
441         {
442             LogInfo("This application type not allowed to use this feature");
443             ThrowMsg(
444                 Exceptions::WidgetConfigFileInvalid,
445                 "This app type [" <<
446                 m_installContext.widgetConfig.type.getApptypeToString() <<
447                 "] cannot be allowed to use [" <<
448                 DPL::ToUTF8String(it->name) + "] feature");
449         }
450         if (!WrtDB::FeatureDAOReadOnly::isFeatureInstalled(
451                 DPL::ToUTF8String(it->name))) {
452             LogWarning("Feature not found. Checking if required :[" <<
453                        DPL::ToUTF8String(it->name) << "]");
454
455             if (it->required) {
456                 LogWarning(
457                     "Required Features missing, Installation topped: [" <<
458                     DPL::ToUTF8String(it->name) << "]");
459
460                 ThrowMsg(
461                     Exceptions::WidgetConfigFileInvalid,
462                     "Widget cannot be installed, required feature is missing:["
463                     +
464                     DPL::ToUTF8String(it->name) + "]");
465             }
466         } else {
467             newList.insert(*it);
468             featureInfo += DPL::ToUTF8String(it->name);
469             featureInfo += DPL::ToUTF8String(BR);
470         }
471     }
472     data.featuresList = newList;
473     if (!featureInfo.empty()) {
474         m_popupData.addWidgetInfo(FEATURE_HEAD,
475                                   DPL::FromUTF8String(featureInfo));
476     }
477
478     m_installContext.job->UpdateProgress(
479         InstallerContext::INSTALL_WIDGET_CONFIG2,
480         "Widget Config step2 Finished");
481 }
482
483 void TaskWidgetConfig::setApplicationType()
484 {
485     using namespace WrtDB;
486     WidgetRegisterInfo* widgetInfo = &(m_installContext.widgetConfig);
487     ConfigParserData* configInfo = &(widgetInfo->configInfo);
488
489     FOREACH(iterator, configInfo->nameSpaces) {
490         LogInfo("namespace = [" << *iterator << "]");
491         AppType currentAppType = APP_TYPE_UNKNOWN;
492
493         if (*iterator == ConfigurationNamespace::W3CWidgetNamespaceName) {
494             continue;
495         } else if (*iterator ==
496                 ConfigurationNamespace::JilWidgetNamespaceName) {
497             currentAppType = APP_TYPE_WAC10;
498         } else if (
499             *iterator ==
500             ConfigurationNamespace::WacWidgetNamespaceNameForLinkElement ||
501             *iterator ==
502             ConfigurationNamespace::WacWidgetNamespaceName)
503         {
504             currentAppType = APP_TYPE_WAC20;
505         } else if (*iterator == ConfigurationNamespace::TizenWebAppNamespaceName) {
506             currentAppType = APP_TYPE_TIZENWEBAPP;
507         }
508
509         if (widgetInfo->type == APP_TYPE_UNKNOWN) {
510             widgetInfo->type = currentAppType;
511         } else if (widgetInfo->type == currentAppType) {
512             continue;
513         } else {
514             ThrowMsg(Exceptions::WidgetConfigFileInvalid,
515                      "Config.xml has more than one namespace");
516         }
517     }
518
519     // If there is no define, type set to WAC 2.0
520     if (widgetInfo->type == APP_TYPE_UNKNOWN) {
521         widgetInfo->type = APP_TYPE_WAC20;
522     }
523
524     LogInfo("type = [" << widgetInfo->type.getApptypeToString() << "]");
525 }
526
527 bool TaskWidgetConfig::isFeatureAllowed(WrtDB::AppType appType,
528                                         DPL::String featureName)
529 {
530     using namespace WrtDB;
531     LogInfo("AppType = [" <<
532             WidgetType(appType).getApptypeToString() << "]");
533     LogInfo("FetureName = [" << featureName << "]");
534
535     AppType featureType = APP_TYPE_UNKNOWN;
536     const char* feature = DPL::ToUTF8String(featureName).c_str();
537     // check prefix of  feature name
538     if (strstr(feature, PluginsPrefix::TIZENPluginsPrefix) == feature) {
539         // Tizen WebApp feature
540         featureType = APP_TYPE_TIZENWEBAPP;
541     } else if (strstr(feature, PluginsPrefix::WACPluginsPrefix) == feature) {
542         // WAC 2.0 feature
543         featureType = APP_TYPE_WAC20;
544     } else if (strstr(feature, PluginsPrefix::W3CPluginsPrefix) == feature) {
545         // W3C standard feature
546         // Both WAC and TIZEN WebApp are possible to use W3C plugins
547         return true;
548     } else {
549         // unknown feature
550         // unknown feature will be checked next step
551         return true;
552     }
553
554     if (appType == featureType) {
555         return true;
556     }
557     return false;
558 }
559
560 bool TaskWidgetConfig::parseVersionString(const std::string &version,
561         long &majorVersion, long &minorVersion, long &microVersion) const
562 {
563     std::istringstream inputString(version);
564     inputString >> majorVersion;
565     if (inputString.bad() || inputString.fail()) {
566         LogWarning("Invalid minVersion format.");
567         return false;
568     }
569     inputString.get(); // skip period
570     inputString >> minorVersion;
571     if (inputString.bad() || inputString.fail()) {
572         LogWarning("Invalid minVersion format");
573         return false;
574     } else {
575         inputString.get(); // skip period
576         if (inputString.bad() || inputString.fail()) {
577             inputString >> microVersion;
578         }
579     }
580     return true;
581 }
582
583 bool TaskWidgetConfig::isWACVersionCompatible(const DPL::OptionalString
584         &widgetVersion) const
585 {
586     if (widgetVersion.IsNull() || (*widgetVersion).empty())
587     {
588         LogWarning("minVersion attribute is empty. WRT assumes platform "
589                 "supports this widget.");
590         return true;
591     }
592
593     //Parse widget version
594     long majorWidget = 0, minorWidget = 0, microWidget = 0;
595     if (!parseVersionString(DPL::ToUTF8String(*widgetVersion), majorWidget,
596             minorWidget, microWidget)) {
597         LogWarning("Invalid format of widget version string.");
598         return true;
599     }
600
601     //Parse supported version
602     long majorSupported = 0, minorSupported = 0, microSupported = 0;
603     if (!parseVersionString(WrtDB::GlobalConfig::GetWACVersion(),
604             majorSupported, minorSupported, microSupported)) {
605         LogWarning("Invalid format of WAC version string.");
606         return true;
607     }
608
609     if (majorWidget > majorSupported ||
610             minorWidget > minorSupported ||
611             microWidget > microSupported) {
612         LogInfo("Platform doesn't support this widget.");
613         return false;
614     }
615     return true;
616 }
617
618 } //namespace WidgetInstall
619 } //namespace Jobs