1 /*
2  * Copyright (c) 2023-2024 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 "sandbox_helper.h"
17 
18 #include <iomanip>
19 #include <sstream>
20 #include <unordered_set>
21 #include <vector>
22 
23 #include "log.h"
24 #include "json_utils.h"
25 #include "uri.h"
26 
27 using namespace std;
28 
29 namespace OHOS {
30 namespace AppFileService {
31 namespace {
32     const string PACKAGE_NAME_FLAG = "<PackageName>";
33     const string CURRENT_USER_ID_FLAG = "<currentUserId>";
34     const string PHYSICAL_PATH_KEY = "src-path";
35     const string SANDBOX_PATH_KEY = "sandbox-path";
36     const string MOUNT_PATH_MAP_KEY = "mount-path-map";
37     const string SANDBOX_JSON_FILE_PATH = "/etc/app_file_service/file_share_sandbox.json";
38     const string BACKUP_SANDBOX_JSON_FILE_PATH = "/etc/app_file_service/backup_sandbox.json";
39     const std::string SHAER_PATH_HEAD = "/mnt/hmdfs/";
40     const std::string SHAER_PATH_MID = "/account/cloud_merge_view/files/";
41     const string FILE_MANAGER_URI_HEAD = "/storage/";
42     const string FILE_MANAGER_AUTHORITY = "docs";
43     const string DLP_MANAGER_BUNDLE_NAME = "com.ohos.dlpmanager";
44     const string FUSE_URI_HEAD = "/mnt/data/fuse";
45     const string BACKFLASH = "/";
46     const string MEDIA = "media";
47     const string NETWORK_ID_FLAG = "<networkId>";
48     const string LOCAL = "local";
49     const int ASSET_IN_BUCKET_NUM_MAX = 1000;
50     const int ASSET_DIR_START_NUM = 16;
51     const int DECODE_FORMAT_NUM = 16;
52 }
53 
54 struct MediaUriInfo {
55     string mediaType;
56     string fileId;
57     string realName;
58     string displayName;
59 };
60 
61 std::unordered_map<std::string, std::string> SandboxHelper::sandboxPathMap_;
62 std::unordered_map<std::string, std::string> SandboxHelper::backupSandboxPathMap_;
63 std::mutex SandboxHelper::mapMutex_;
64 
Encode(const string & uri)65 string SandboxHelper::Encode(const string &uri)
66 {
67     const unordered_set<char> uriCompentsSet = {
68         '/', '-', '_', '.', '!',
69         '~', '*', '(', ')', '\''
70     };
71     const int32_t encodeLen = 2;
72     ostringstream outPutStream;
73     outPutStream.fill('0');
74     outPutStream << std::hex;
75 
76     for (unsigned char tmpChar : uri) {
77         if (std::isalnum(tmpChar) || uriCompentsSet.find(tmpChar) != uriCompentsSet.end()) {
78             outPutStream << tmpChar;
79         } else {
80             outPutStream << std::uppercase;
81             outPutStream << '%' << std::setw(encodeLen) << static_cast<unsigned int>(tmpChar);
82             outPutStream << std::nouppercase;
83         }
84     }
85 
86     return outPutStream.str();
87 }
88 
Decode(const string & uri)89 string SandboxHelper::Decode(const string &uri)
90 {
91     std::string outPutStr;
92     const int32_t encodeLen = 2;
93     size_t index = 0;
94     while (index < uri.length()) {
95         if (uri[index] == '%') {
96             std::string inputStr(uri.substr(index + 1, encodeLen));
97             outPutStr += static_cast<char>(strtol(inputStr.c_str(), nullptr, DECODE_FORMAT_NUM));
98             index += encodeLen + 1;
99         } else {
100             outPutStr += uri[index];
101             index++;
102         }
103     }
104 
105     return outPutStr;
106 }
107 
GetLowerPath(string & lowerPathHead,const string & lowerPathTail,const string & userId,const string & bundleName,const string & networkId)108 static string GetLowerPath(string &lowerPathHead, const string &lowerPathTail,
109                            const string &userId, const string &bundleName,
110                            const string &networkId)
111 {
112     if (lowerPathHead.find(CURRENT_USER_ID_FLAG) != string::npos) {
113         lowerPathHead = lowerPathHead.replace(lowerPathHead.find(CURRENT_USER_ID_FLAG),
114                                               CURRENT_USER_ID_FLAG.length(), userId);
115     }
116 
117     if (lowerPathHead.find(PACKAGE_NAME_FLAG) != string::npos) {
118         lowerPathHead = lowerPathHead.replace(lowerPathHead.find(PACKAGE_NAME_FLAG),
119                                               PACKAGE_NAME_FLAG.length(), bundleName);
120     }
121 
122     if (lowerPathHead.find(NETWORK_ID_FLAG) != string::npos) {
123         lowerPathHead = lowerPathHead.replace(lowerPathHead.find(NETWORK_ID_FLAG),
124                                               NETWORK_ID_FLAG.length(), networkId);
125     }
126 
127     return lowerPathHead + lowerPathTail;
128 }
129 
GetSandboxPathMap()130 bool SandboxHelper::GetSandboxPathMap()
131 {
132     lock_guard<mutex> lock(mapMutex_);
133     if (sandboxPathMap_.size() > 0) {
134         return true;
135     }
136 
137     nlohmann::json jsonObj;
138     int ret = JsonUtils::GetJsonObjFromPath(jsonObj, SANDBOX_JSON_FILE_PATH);
139     if (ret != 0) {
140         LOGE("Get json object failed with %{public}d", ret);
141         return false;
142     }
143 
144     if (jsonObj.find(MOUNT_PATH_MAP_KEY) == jsonObj.end()) {
145         LOGE("Json object find mount path map failed");
146         return false;
147     }
148 
149     nlohmann::json mountPathMap = jsonObj[MOUNT_PATH_MAP_KEY];
150     for (size_t i = 0; i < mountPathMap.size(); i++) {
151         string srcPath = mountPathMap[i][PHYSICAL_PATH_KEY];
152         string sandboxPath = mountPathMap[i][SANDBOX_PATH_KEY];
153         sandboxPathMap_[sandboxPath] = srcPath;
154     }
155 
156     if (sandboxPathMap_.size() == 0) {
157         return false;
158     }
159 
160     return true;
161 }
162 
GetBackupSandboxPathMap()163 bool SandboxHelper::GetBackupSandboxPathMap()
164 {
165     lock_guard<mutex> lock(mapMutex_);
166     if (backupSandboxPathMap_.size() > 0) {
167         return true;
168     }
169 
170     nlohmann::json jsonObj;
171     int ret = JsonUtils::GetJsonObjFromPath(jsonObj, BACKUP_SANDBOX_JSON_FILE_PATH);
172     if (ret != 0) {
173         LOGE("Get json object failed with %{public}d", ret);
174         return false;
175     }
176 
177     if (jsonObj.find(MOUNT_PATH_MAP_KEY) == jsonObj.end()) {
178         LOGE("Json object find mount path map failed");
179         return false;
180     }
181 
182     nlohmann::json mountPathMap = jsonObj[MOUNT_PATH_MAP_KEY];
183     for (size_t i = 0; i < mountPathMap.size(); i++) {
184         string srcPath = mountPathMap[i][PHYSICAL_PATH_KEY];
185         string sandboxPath = mountPathMap[i][SANDBOX_PATH_KEY];
186         backupSandboxPathMap_[sandboxPath] = srcPath;
187     }
188 
189     if (backupSandboxPathMap_.size() == 0) {
190         return false;
191     }
192 
193     return true;
194 }
195 
GetPathSuffix(const std::string & path,string & pathSuffix)196 static int32_t GetPathSuffix(const std::string &path, string &pathSuffix)
197 {
198     size_t pos = path.rfind('.');
199     if (pos != string::npos) {
200         pathSuffix = path.substr(pos);
201         return 0;
202     }
203     return -EINVAL;
204 }
205 
CalAssetBucket(const int32_t & fileId)206 static int32_t CalAssetBucket(const int32_t &fileId)
207 {
208     int32_t bucketNum = 0;
209     if (fileId < 0) {
210         LOGE("input fileId %{private}d is invalid", fileId);
211         return -EINVAL;
212     }
213 
214     int32_t start = ASSET_DIR_START_NUM;
215     int32_t divider = ASSET_DIR_START_NUM;
216     while (fileId > start * ASSET_IN_BUCKET_NUM_MAX) {
217         divider = start;
218         start <<= 1;
219     }
220 
221     int32_t fileIdRemainder = fileId % divider;
222     if (fileIdRemainder == 0) {
223         bucketNum = start + fileIdRemainder;
224     } else {
225         bucketNum = (start - divider) + fileIdRemainder;
226     }
227     return bucketNum;
228 }
229 
GetFileIdFromFileName(const std::string & fileName)230 static int32_t GetFileIdFromFileName(const std::string &fileName)
231 {
232     if (fileName.empty()) {
233         return -EINVAL;
234     }
235 
236     string tmpName = fileName;
237     std::replace(tmpName.begin(), tmpName.end(), '_', ' ');
238     stringstream ss;
239     ss << tmpName;
240 
241     string mediaType;
242     string dateTime;
243     string idStr;
244     string other;
245     ss >> mediaType >> dateTime >> idStr >> other;
246     if (idStr.empty()) {
247         return -EINVAL;
248     }
249 
250     if (!std::all_of(idStr.begin(), idStr.end(), ::isdigit)) {
251         return -EINVAL;
252     }
253 
254     return std::stoi(idStr);
255 }
256 
GetBucketNum(const std::string & fileName)257 static int32_t GetBucketNum(const std::string &fileName)
258 {
259     int32_t fileId = GetFileIdFromFileName(fileName);
260     if (fileId < 0) {
261         LOGE("GetFileIdFromFileName failed with %{public}s", fileName.c_str());
262         return fileId;
263     }
264     return CalAssetBucket(fileId);
265 }
266 
ParseMediaSandboxPath(const string & sandboxPath,MediaUriInfo & mediaUriInfo)267 static bool ParseMediaSandboxPath(const string &sandboxPath, MediaUriInfo &mediaUriInfo)
268 {
269     string path = sandboxPath;
270     std::replace(path.begin(), path.end(), '/', ' ');
271     stringstream ss;
272     ss << path;
273     ss >> mediaUriInfo.mediaType >> mediaUriInfo.fileId >> mediaUriInfo.realName >> mediaUriInfo.displayName;
274 
275     string buf;
276     ss >> buf;
277     if (!buf.empty()) {
278         LOGE("media sandboxPath is invalid");
279         return false;
280     }
281 
282     return true;
283 }
284 
GetMediaPhysicalPath(const std::string & sandboxPath,const std::string & userId,std::string & physicalPath)285 static int32_t GetMediaPhysicalPath(const std::string &sandboxPath, const std::string &userId,
286                                     std::string &physicalPath)
287 {
288     MediaUriInfo mediaUriInfo;
289     if (!ParseMediaSandboxPath(sandboxPath, mediaUriInfo)) {
290         return -EINVAL;
291     }
292 
293     int32_t bucketNum = GetBucketNum(mediaUriInfo.realName);
294     if (bucketNum < 0) {
295         return -EINVAL;
296     }
297 
298     std::string mediaSuffix;
299     if (GetPathSuffix(sandboxPath, mediaSuffix) != 0) {
300         LOGE("GetPathSuffix failed");
301         return -EINVAL;
302     }
303 
304     physicalPath = SHAER_PATH_HEAD + userId + SHAER_PATH_MID + mediaUriInfo.mediaType +
305                    BACKFLASH + to_string(bucketNum) + BACKFLASH + mediaUriInfo.realName + mediaSuffix;
306     return 0;
307 }
308 
GetNetworkIdFromUri(const std::string & fileUri,string & networkId)309 static void GetNetworkIdFromUri(const std::string &fileUri, string &networkId)
310 {
311     Uri uri(fileUri);
312     std::string networkIdInfo = uri.GetQuery();
313     if (networkIdInfo.empty()) {
314         return;
315     }
316 
317     size_t posIndex = networkIdInfo.find('=');
318     if (posIndex == string::npos || posIndex == (networkIdInfo.size() - 1)) {
319         return;
320     }
321     networkId = networkIdInfo.substr(posIndex + 1);
322 }
323 
DoGetPhysicalPath(string & lowerPathTail,string & lowerPathHead,const string & sandboxPath,std::unordered_map<std::string,std::string> & sandboxPathMap)324 static void DoGetPhysicalPath(string &lowerPathTail, string &lowerPathHead, const string &sandboxPath,
325     std::unordered_map<std::string, std::string> &sandboxPathMap)
326 {
327     string::size_type curPrefixMatchLen = 0;
328     for (auto it = sandboxPathMap.begin(); it != sandboxPathMap.end(); it++) {
329         string sandboxPathPrefix = it->first;
330         string::size_type prefixMatchLen = sandboxPathPrefix.length();
331         if (sandboxPath.length() >= prefixMatchLen) {
332             string sandboxPathTemp = sandboxPath.substr(0, prefixMatchLen);
333             if (sandboxPathTemp == sandboxPathPrefix && curPrefixMatchLen <= prefixMatchLen) {
334                 curPrefixMatchLen = prefixMatchLen;
335                 lowerPathHead = it->second;
336                 lowerPathTail = sandboxPath.substr(prefixMatchLen);
337             }
338         }
339     }
340 }
341 
GetPhysicalPath(const std::string & fileUri,const std::string & userId,std::string & physicalPath)342 int32_t SandboxHelper::GetPhysicalPath(const std::string &fileUri, const std::string &userId,
343                                        std::string &physicalPath)
344 {
345     Uri uri(fileUri);
346     string bundleName = uri.GetAuthority();
347     if (bundleName == MEDIA) {
348         return GetMediaPhysicalPath(uri.GetPath(), userId, physicalPath);
349     }
350 
351     string sandboxPath = SandboxHelper::Decode(uri.GetPath());
352     if ((sandboxPath.find(FILE_MANAGER_URI_HEAD) == 0 && bundleName != FILE_MANAGER_AUTHORITY) ||
353         (sandboxPath.find(FUSE_URI_HEAD) == 0 && bundleName != DLP_MANAGER_BUNDLE_NAME)) {
354         return -EINVAL;
355     }
356 
357     if (!GetSandboxPathMap()) {
358         LOGE("GetSandboxPathMap failed");
359         return -EINVAL;
360     }
361 
362     string lowerPathTail = "";
363     string lowerPathHead = "";
364     DoGetPhysicalPath(lowerPathTail, lowerPathHead, sandboxPath, sandboxPathMap_);
365 
366     if (lowerPathHead == "") {
367         LOGE("lowerPathHead is invalid");
368         return -EINVAL;
369     }
370 
371     string networkId = LOCAL;
372     GetNetworkIdFromUri(fileUri, networkId);
373 
374     physicalPath = GetLowerPath(lowerPathHead, lowerPathTail, userId, bundleName, networkId);
375     return 0;
376 }
377 
GetBackupPhysicalPath(const std::string & fileUri,const std::string & userId,std::string & physicalPath)378 int32_t SandboxHelper::GetBackupPhysicalPath(const std::string &fileUri, const std::string &userId,
379                                              std::string &physicalPath)
380 {
381     Uri uri(fileUri);
382     string bundleName = uri.GetAuthority();
383     if (bundleName == MEDIA) {
384         return GetMediaPhysicalPath(uri.GetPath(), userId, physicalPath);
385     }
386 
387     string sandboxPath = SandboxHelper::Decode(uri.GetPath());
388     if ((sandboxPath.find(FILE_MANAGER_URI_HEAD) == 0 && bundleName != FILE_MANAGER_AUTHORITY) ||
389         (sandboxPath.find(FUSE_URI_HEAD) == 0 && bundleName != DLP_MANAGER_BUNDLE_NAME)) {
390         return -EINVAL;
391     }
392 
393     if (!GetBackupSandboxPathMap()) {
394         LOGE("GetBackupSandboxPathMap failed");
395         return -EINVAL;
396     }
397 
398     string lowerPathTail = "";
399     string lowerPathHead = "";
400     DoGetPhysicalPath(lowerPathTail, lowerPathHead, sandboxPath, backupSandboxPathMap_);
401 
402     if (lowerPathHead == "") {
403         LOGE("lowerPathHead is invalid");
404         return -EINVAL;
405     }
406 
407     string networkId = LOCAL;
408     GetNetworkIdFromUri(fileUri, networkId);
409 
410     physicalPath = GetLowerPath(lowerPathHead, lowerPathTail, userId, bundleName, networkId);
411     return 0;
412 }
413 
IsValidPath(const std::string & path)414 bool SandboxHelper::IsValidPath(const std::string &path)
415 {
416     if (path.find("/./") != std::string::npos ||
417         path.find("/../") != std::string::npos) {
418         return false;
419     }
420     return true;
421 }
422 
CheckValidPath(const std::string & filePath)423 bool SandboxHelper::CheckValidPath(const std::string &filePath)
424 {
425     if (filePath.empty() || filePath.size() >= PATH_MAX) {
426         return false;
427     }
428 
429     char realPath[PATH_MAX]{'\0'};
430     if (realpath(filePath.c_str(), realPath) == nullptr) {
431         LOGE("realpath failed with %{public}d", errno);
432         return false;
433     }
434 
435     if (strncmp(realPath, filePath.c_str(), filePath.size()) != 0) {
436         LOGE("filePath is not equal to realPath, realPath.size = %{public}zu, filePath.size() = %{public}zu",
437             string_view(realPath).size(), filePath.size());
438         return false;
439     }
440 
441     return true;
442 }
443 } // namespace AppFileService
444 } // namespace OHOS
445 
446