1 /*
2  * Copyright (C) 2021 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "preferences_xml_utils.h"
17 
18 #include <sys/stat.h>
19 
20 #include <cerrno>
21 #include <cstring>
22 
23 #include "libxml/parser.h"
24 #include "log_print.h"
25 #include "preferences_dfx_adapter.h"
26 #include "preferences_file_lock.h"
27 #include "preferences_file_operation.h"
28 #include "preferences_impl.h"
29 
30 namespace OHOS {
31 namespace NativePreferences {
32 static bool ParseNodeElement(const xmlNode *node, Element &element);
33 static bool ParsePrimitiveNodeElement(const xmlNode *node, Element &element);
34 static bool ParseStringNodeElement(const xmlNode *node, Element &element);
35 static bool ParseArrayNodeElement(const xmlNode *node, Element &element);
36 static xmlNode *CreateElementNode(Element &element);
37 static xmlNode *CreatePrimitiveNode(Element &element);
38 static xmlNode *CreateStringNode(Element &element);
39 static xmlNode *CreateArrayNode(Element &element);
40 
IsFileExist(const std::string & inputPath)41 static bool IsFileExist(const std::string &inputPath)
42 {
43     if (inputPath.length() > PATH_MAX) {
44         return false;
45     }
46     struct stat buffer;
47     return (stat(inputPath.c_str(), &buffer) == 0);
48 }
49 
RemoveBackupFile(const std::string & fileName)50 static void RemoveBackupFile(const std::string &fileName)
51 {
52     std::string backupFileName = MakeFilePath(fileName, STR_BACKUP);
53     if (IsFileExist(backupFileName) && std::remove(backupFileName.c_str())) {
54         LOG_WARN("failed to delete backup file %{public}d.", errno);
55     }
56 }
57 
ReadFile(const std::string & fileName)58 static xmlDoc *ReadFile(const std::string &fileName)
59 {
60     return xmlReadFile(fileName.c_str(), "UTF-8", XML_PARSE_NOBLANKS | XML_PARSE_HUGE);
61 }
62 
RenameFromBackupFile(const std::string & fileName)63 static bool RenameFromBackupFile(const std::string &fileName)
64 {
65     std::string backupFileName = MakeFilePath(fileName, STR_BACKUP);
66     if (!IsFileExist(backupFileName)) {
67         LOG_DEBUG("the backup file does not exist.");
68         return false;
69     }
70     xmlResetLastError();
71     auto bakDoc = std::shared_ptr<xmlDoc>(ReadFile(backupFileName), [](xmlDoc *bakDoc) { xmlFreeDoc(bakDoc); });
72     if (bakDoc == nullptr) {
73         LOG_ERROR("failed to read backup file:%{public}s, errno %{public}d.",
74             ExtractFileName(backupFileName).c_str(), errno);
75         std::remove(backupFileName.c_str());
76         return false;
77     }
78     xmlErrorPtr xmlErr = xmlGetLastError();
79     if (xmlErr != nullptr) { // need to report hisysevent
80         LOG_ERROR("restore XML file: %{public}s failed, errno is %{public}d, error is %{public}s.",
81             ExtractFileName(backupFileName).c_str(), errno, xmlErr->message);
82         std::remove(backupFileName.c_str());
83         return false;
84     }
85     if (std::rename(backupFileName.c_str(), fileName.c_str())) {
86         LOG_ERROR("failed to restore backup errno %{public}d.", errno);
87         return false;
88     }
89     LOG_INFO("restore XML file %{public}s successfully.", ExtractFileName(fileName).c_str());
90     return true;
91 }
92 
RenameFile(const std::string & fileName,const std::string & fileType)93 static bool RenameFile(const std::string &fileName, const std::string &fileType)
94 {
95     std::string name = MakeFilePath(fileName, fileType);
96     if (std::rename(fileName.c_str(), name.c_str())) {
97         LOG_ERROR("failed to rename file to %{public}s file %{public}d.", fileType.c_str(), errno);
98         return false;
99     }
100     return true;
101 }
102 
RenameToBackupFile(const std::string & fileName)103 static bool RenameToBackupFile(const std::string &fileName)
104 {
105     return RenameFile(fileName, STR_BACKUP);
106 }
107 
RenameToBrokenFile(const std::string & fileName,const ReportParam & reportParam)108 static bool RenameToBrokenFile(const std::string &fileName, const ReportParam &reportParam)
109 {
110     PreferencesDfxManager::ReportDbFault(reportParam);
111     return RenameFile(fileName, STR_BROKEN);
112 }
113 
XmlReadFile(const std::string & fileName,const std::string & bundleName,const std::string & dataGroupId)114 static xmlDoc *XmlReadFile(const std::string &fileName, const std::string &bundleName, const std::string &dataGroupId)
115 {
116     xmlDoc *doc = nullptr;
117     PreferencesFileLock fileLock(MakeFilePath(fileName, STR_LOCK), dataGroupId);
118     if (IsFileExist(fileName)) {
119         doc = ReadFile(fileName);
120         if (doc != nullptr) {
121             return doc;
122         }
123         xmlErrorPtr xmlErr = xmlGetLastError();
124         std::string errMessage = (xmlErr != nullptr) ? xmlErr->message : "null";
125         LOG_ERROR("failed to read XML format file: %{public}s, errno is %{public}d, error is %{public}s.",
126             ExtractFileName(fileName).c_str(), errno, errMessage.c_str());
127         ReportParam reportParam = { bundleName, NORMAL_DB,
128             ExtractFileName(fileName), E_ERROR, errno, "operation: failed to read XML format file." };
129         if (!RenameToBrokenFile(fileName, reportParam)) {
130             return doc;
131         }
132     }
133     if (RenameFromBackupFile(fileName)) {
134         return ReadFile(fileName);
135     }
136     return nullptr;
137 }
138 
139 /* static */
ReadSettingXml(const std::string & fileName,const std::string & bundleName,const std::string & dataGroupId,std::vector<Element> & settings)140 bool PreferencesXmlUtils::ReadSettingXml(const std::string &fileName, const std::string &bundleName,
141     const std::string &dataGroupId, std::vector<Element> &settings)
142 {
143     LOG_RECORD_FILE_NAME("Read setting xml start.");
144     if (fileName.size() == 0) {
145         LOG_ERROR("The length of the file name is 0.");
146         return false;
147     }
148     auto doc =
149         std::shared_ptr<xmlDoc>(XmlReadFile(fileName, bundleName, dataGroupId), [](xmlDoc *doc) { xmlFreeDoc(doc); });
150     if (doc == nullptr) {
151         return false;
152     }
153 
154     xmlNode *root = xmlDocGetRootElement(doc.get());
155     if (!root || xmlStrcmp(root->name, reinterpret_cast<const xmlChar *>("preferences"))) {
156         LOG_ERROR("Failed to obtain the XML root element.");
157         return false;
158     }
159 
160     bool success = true;
161     const xmlNode *cur = nullptr;
162     for (cur = root->children; cur != nullptr; cur = cur->next) {
163         Element element;
164 
165         if (ParseNodeElement(cur, element)) {
166             settings.push_back(element);
167         } else {
168             success = false;
169             LOG_ERROR("The error occurred during getting xml child elements.");
170             break;
171         }
172     }
173 
174     /* free the document */
175     LOG_RECORD_FILE_NAME("Read setting xml end.");
176     return success;
177 }
178 
179 /* static */
ParseNodeElement(const xmlNode * node,Element & element)180 bool ParseNodeElement(const xmlNode *node, Element &element)
181 {
182     if (!xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("int"))
183         || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("long"))
184         || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("bool"))
185         || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("float"))
186         || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("double"))
187         || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("uint64_t"))) {
188         return ParsePrimitiveNodeElement(node, element);
189     }
190 
191     if (!xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("string"))
192         || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("uint8Array"))
193         || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("object"))) {
194         return ParseStringNodeElement(node, element);
195     }
196 
197     if (!xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("boolArray"))
198         || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("stringArray"))
199         || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("doubleArray"))
200         || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("BigInt"))
201         || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("set"))) {
202         return ParseArrayNodeElement(node, element);
203     }
204 
205     LOG_ERROR("An unsupported element type was encountered in parsing = %{public}s.", node->name);
206     return false;
207 }
208 
209 /* static */
ParsePrimitiveNodeElement(const xmlNode * node,Element & element)210 bool ParsePrimitiveNodeElement(const xmlNode *node, Element &element)
211 {
212     xmlChar *key = xmlGetProp(node, reinterpret_cast<const xmlChar *>("key"));
213     xmlChar *value = xmlGetProp(node, reinterpret_cast<const xmlChar *>("value"));
214 
215     bool success = false;
216     if (value != nullptr) {
217         element.tag_ = std::string(reinterpret_cast<const char *>(node->name));
218         if (key != nullptr) {
219             element.key_ = std::string(reinterpret_cast<char *>(key));
220         }
221         element.value_ = std::string(reinterpret_cast<char *>(value));
222         success = true;
223     } else {
224         LOG_ERROR("Failed to obtain a valid key or value when parsing %{public}s.", node->name);
225     }
226 
227     if (key != nullptr) {
228         xmlFree(key);
229     }
230     if (value != nullptr) {
231         xmlFree(value);
232     }
233     return success;
234 }
235 
236 /* static */
ParseStringNodeElement(const xmlNode * node,Element & element)237 bool ParseStringNodeElement(const xmlNode *node, Element &element)
238 {
239     xmlChar *key = xmlGetProp(node, (const xmlChar *)"key");
240     xmlChar *text = xmlNodeGetContent(node);
241 
242     bool success = false;
243     if (text != nullptr) {
244         element.tag_ = std::string(reinterpret_cast<const char *>(node->name));
245         if (key != nullptr) {
246             element.key_ = std::string(reinterpret_cast<char *>(key));
247         }
248         element.value_ = std::string(reinterpret_cast<char *>(text));
249         success = true;
250     } else {
251         LOG_ERROR("Failed to obtain a valid key or value when parsing string element.");
252     }
253 
254     if (key != nullptr) {
255         xmlFree(key);
256     }
257     if (text != nullptr) {
258         xmlFree(text);
259     }
260     return success;
261 }
262 
263 /* static */
ParseArrayNodeElement(const xmlNode * node,Element & element)264 bool ParseArrayNodeElement(const xmlNode *node, Element &element)
265 {
266     xmlChar *key = xmlGetProp(node, (const xmlChar *)"key");
267     const xmlNode *children = node->children;
268 
269     bool success = false;
270     if (key != nullptr) {
271         element.tag_ = std::string(reinterpret_cast<const char *>(node->name));
272         element.key_ = std::string(reinterpret_cast<char *>(key));
273 
274         const xmlNode *cur = nullptr;
275         bool finishTravelChild = true;
276         for (cur = children; cur != nullptr; cur = cur->next) {
277             Element child;
278             if (ParseNodeElement(cur, child)) {
279                 element.children_.push_back(child);
280             } else {
281                 finishTravelChild = false;
282                 LOG_ERROR("Failed to parse the Array element and could not be completed successfully.");
283                 break;
284             }
285         }
286         success = finishTravelChild;
287     } else {
288         LOG_ERROR("Failed to obtain a valid key or value when parsing a Array element.");
289     }
290 
291     if (key != nullptr) {
292         xmlFree(key);
293     }
294     return success;
295 }
296 
SaveFormatFileEnc(const std::string & fileName,xmlDoc * doc)297 static bool SaveFormatFileEnc(const std::string &fileName, xmlDoc *doc)
298 {
299     return xmlSaveFormatFileEnc(fileName.c_str(), doc, "UTF-8", 1) > 0;
300 }
301 
XmlSaveFormatFileEnc(const std::string & fileName,const std::string & bundleName,const std::string & dataGroupId,xmlDoc * doc)302 bool XmlSaveFormatFileEnc(
303     const std::string &fileName, const std::string &bundleName, const std::string &dataGroupId, xmlDoc *doc)
304 {
305     PreferencesFileLock fileLock(MakeFilePath(fileName, STR_LOCK), dataGroupId);
306     LOG_INFO("save xml file:%{public}s.", ExtractFileName(fileName).c_str());
307     if (IsFileExist(fileName) && !RenameToBackupFile(fileName)) {
308         return false;
309     }
310 
311     if (!SaveFormatFileEnc(fileName, doc)) {
312         xmlErrorPtr xmlErr = xmlGetLastError();
313         std::string errMessage = (xmlErr != nullptr) ? xmlErr->message : "null";
314         LOG_ERROR("failed to save XML format file: %{public}s, errno is %{public}d, error is %{public}s.",
315             ExtractFileName(fileName).c_str(), errno, errMessage.c_str());
316         if (IsFileExist(fileName)) {
317             ReportParam reportParam = { bundleName, NORMAL_DB,
318                 ExtractFileName(fileName), E_ERROR, errno, "operation: failed to save XML format file." };
319             RenameToBrokenFile(fileName, reportParam);
320         }
321         RenameFromBackupFile(fileName);
322         return false;
323     }
324 
325     RemoveBackupFile(fileName);
326     PreferencesXmlUtils::LimitXmlPermission(fileName);
327     // make sure the file is written to disk.
328     if (!Fsync(fileName)) {
329         LOG_WARN("failed to write the file to the disk.");
330     }
331     LOG_DEBUG("successfully saved the XML format file");
332     return true;
333 }
334 
335 /* static */
WriteSettingXml(const std::string & fileName,const std::string & bundleName,const std::string & dataGroupId,const std::vector<Element> & settings)336 bool PreferencesXmlUtils::WriteSettingXml(const std::string &fileName, const std::string &bundleName,
337     const std::string &dataGroupId, const std::vector<Element> &settings)
338 {
339     LOG_RECORD_FILE_NAME("Write setting xml start.");
340     if (fileName.size() == 0) {
341         LOG_ERROR("The length of the file name is 0.");
342         return false;
343     }
344 
345     // define doc and root Node
346     auto doc = std::shared_ptr<xmlDoc>(xmlNewDoc(BAD_CAST "1.0"), [](xmlDoc *doc) { xmlFreeDoc(doc); });
347     if (doc == nullptr) {
348         LOG_ERROR("Failed to initialize the xmlDoc.");
349         return false;
350     }
351     xmlNode *rootNode = xmlNewNode(NULL, BAD_CAST "preferences");
352     if (rootNode == nullptr) {
353         LOG_ERROR("The xmlDoc failed to initialize the root node.");
354         return false;
355     }
356     xmlNewProp(rootNode, BAD_CAST "version", BAD_CAST "1.0");
357 
358     // set root node
359     xmlDocSetRootElement(doc.get(), rootNode);
360 
361     // set children node
362     for (Element element : settings) {
363         xmlNode *node = CreateElementNode(element);
364         if (node == nullptr) {
365             LOG_ERROR("The xmlDoc failed to initialize the element node.");
366             return false;
367         }
368         if (xmlAddChild(rootNode, node) == nullptr) {
369             /* free node in case of error */
370             LOG_ERROR("The xmlDoc failed to add the child node.");
371             xmlFreeNode(node);
372             return false;
373         }
374     }
375 
376     /* 1: formatting spaces are added. */
377     bool result = XmlSaveFormatFileEnc(fileName, bundleName, dataGroupId, doc.get());
378     LOG_RECORD_FILE_NAME("Write setting xml end.");
379     return result;
380 }
381 
382 /* static */
CreateElementNode(Element & element)383 xmlNode *CreateElementNode(Element &element)
384 {
385     if ((element.tag_.compare("int") == 0) || (element.tag_.compare("long") == 0)
386         || (element.tag_.compare("float") == 0) || (element.tag_.compare("bool") == 0)
387         || (element.tag_.compare("double") == 0)) {
388         return CreatePrimitiveNode(element);
389     }
390 
391     if (element.tag_.compare("string") == 0 || element.tag_.compare("uint8Array") == 0
392         || element.tag_.compare("object") == 0) {
393         return CreateStringNode(element);
394     }
395 
396     if ((element.tag_.compare("doubleArray") == 0) || (element.tag_.compare("stringArray") == 0)
397         || (element.tag_.compare("boolArray") == 0) || (element.tag_.compare("BigInt") == 0)) {
398         return CreateArrayNode(element);
399     }
400 
401     LOG_ERROR("An unsupported element type was encountered in parsing = %{public}s.", element.tag_.c_str());
402     return nullptr;
403 }
404 
405 /* static */
CreatePrimitiveNode(Element & element)406 xmlNode *CreatePrimitiveNode(Element &element)
407 {
408     xmlNode *node = xmlNewNode(NULL, BAD_CAST element.tag_.c_str());
409     if (node == nullptr) {
410         LOG_ERROR("The xmlDoc failed to initialize the primitive element node.");
411         return nullptr;
412     }
413     if (!element.key_.empty()) {
414         const char *key = element.key_.c_str();
415         xmlNewProp(node, BAD_CAST "key", BAD_CAST key);
416     }
417 
418     const char *value = element.value_.c_str();
419     xmlNewProp(node, BAD_CAST "value", BAD_CAST value);
420     return node;
421 }
422 
CreateStringNode(Element & element)423 xmlNode *CreateStringNode(Element &element)
424 {
425     xmlNode *node = xmlNewNode(NULL, BAD_CAST element.tag_.c_str());
426     if (node == nullptr) {
427         LOG_ERROR("The xmlDoc failed to initialize the string element node.");
428         return nullptr;
429     }
430 
431     if (!element.key_.empty()) {
432         const char *key = element.key_.c_str();
433         xmlNewProp(node, BAD_CAST "key", BAD_CAST key);
434     }
435 
436     const char *value = element.value_.c_str();
437     xmlNodePtr text = xmlNewText(BAD_CAST value);
438     if (xmlAddChild(node, text) == nullptr) {
439         xmlFreeNode(text);
440     }
441     return node;
442 }
443 
CreateArrayNode(Element & element)444 xmlNode *CreateArrayNode(Element &element)
445 {
446     xmlNode *node = xmlNewNode(NULL, BAD_CAST element.tag_.c_str());
447     if (node == nullptr) {
448         LOG_ERROR("The xmlDoc failed to initialize the array element node.");
449         return nullptr;
450     }
451 
452     const char *key = element.key_.c_str();
453     xmlNewProp(node, BAD_CAST "key", BAD_CAST key);
454 
455     if (element.children_.empty()) {
456         return node;
457     }
458     Element flag = element.children_[0];
459     if ((flag.tag_.compare("bool") == 0) || (flag.tag_.compare("double") == 0) ||
460         (flag.tag_.compare("uint64_t") == 0)) {
461         for (Element &child : element.children_) {
462             xmlNode *childNode = CreatePrimitiveNode(child);
463             if (childNode == nullptr) {
464                 continue;
465             }
466             if (xmlAddChild(node, childNode) == nullptr) {
467                 xmlFreeNode(childNode);
468             }
469         }
470     } else if (flag.tag_.compare("string") == 0) {
471         for (Element child : element.children_) {
472             xmlNode *childNode = CreateStringNode(child);
473             if (childNode == nullptr) {
474                 continue;
475             }
476             if (xmlAddChild(node, childNode) == nullptr) {
477                 xmlFreeNode(childNode);
478             }
479         }
480     }
481     return node;
482 }
483 
LimitXmlPermission(const std::string & fileName)484 void PreferencesXmlUtils::LimitXmlPermission(const std::string &fileName)
485 {
486     /* clear execute permission of owner, clear execute permission of group, clear all permission of group. */
487     struct stat fileStat = { 0 };
488     if (stat(fileName.c_str(), &fileStat) != 0) {
489         LOG_ERROR("Failed to obtain stat of file, errno:%{public}d.", errno);
490         return;
491     }
492     if ((fileStat.st_mode & (S_IXUSR | S_IXGRP | S_IRWXO)) != 0) {
493         int result = chmod(fileName.c_str(), fileStat.st_mode & (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP));
494         if (result != 0) {
495             LOG_ERROR("Failed to chmod file, errno:%{public}d.", errno);
496         }
497     }
498 }
499 } // End of namespace NativePreferences
500 } // End of namespace OHOS