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