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 "core/image/image_loader.h"
17
18 #include <regex>
19
20 #include "third_party/skia/include/codec/SkCodec.h"
21 #include "third_party/skia/include/utils/SkBase64.h"
22
23 #include "base/network/download_manager.h"
24 #include "base/resource/ace_res_config.h"
25 #include "base/resource/asset_manager.h"
26 #include "base/thread/background_task_executor.h"
27 #include "base/utils/string_utils.h"
28 #include "core/common/ace_application_info.h"
29 #include "core/common/ace_engine.h"
30 #include "core/image/image_cache.h"
31
32 namespace OHOS::Ace {
33 namespace {
34
35 constexpr size_t FILE_HEAD_LENGTH = 7; // 7 is the size of "file://"
36 constexpr size_t MEMORY_HEAD_LENGTH = 9; // 9 is the size of "memory://"
37 constexpr size_t INTERNAL_FILE_HEAD_LENGTH = 15; // 15 is the size of "internal://app/"
38 // regex for "resource://colormode/xxx.type", colormode can be "light" or "dark", xxx represents system resource id,
39 // type can be "jpg", "png", "svg" and so on.
40 const std::regex MEDIA_RES_ID_REGEX(R"(^resource://\w+/([0-9]+)\.\w+$)", std::regex::icase);
41 const std::regex MEDIA_APP_RES_PATH_REGEX(R"(^resource://RAWFILE/(.*)$)");
42 const std::regex MEDIA_APP_RES_ID_REGEX(R"(^resource://.*/([0-9]+)\.\w+$)", std::regex::icase);
43 constexpr uint32_t MEDIA_RESOURCE_MATCH_SIZE = 2;
44
45 #ifdef WINDOWS_PLATFORM
realpath(const char * path,char * resolved_path)46 char* realpath(const char* path, char* resolved_path)
47 {
48 if (strcpy_s(resolved_path, PATH_MAX, path) != 0) {
49 return nullptr;
50 }
51 return resolved_path;
52 }
53 #endif
54
55 } // namespace
56
RemovePathHead(const std::string & uri)57 std::string ImageLoader::RemovePathHead(const std::string& uri)
58 {
59 auto iter = uri.find_first_of(':');
60 if (iter == std::string::npos) {
61 LOGW("No scheme, not a File or Memory path! The path is %{private}s", uri.c_str());
62 return std::string();
63 }
64 std::string head = uri.substr(0, iter);
65 if ((head == "file" && uri.size() > FILE_HEAD_LENGTH) || (head == "memory" && uri.size() > MEMORY_HEAD_LENGTH) ||
66 (head == "internal" && uri.size() > INTERNAL_FILE_HEAD_LENGTH)) {
67 // the file uri format is like "file:///data/data...",
68 // the memory uri format is like "memory://imagename.png" for example,
69 // iter + 3 to get the absolutely file path substring : "/data/data..." or the image name: "imagename.png"
70 return uri.substr(iter + 3);
71 }
72 LOGE("Wrong scheme, not a File!");
73 return std::string();
74 }
75
CreateImageLoader(const ImageSourceInfo & imageSourceInfo)76 RefPtr<ImageLoader> ImageLoader::CreateImageLoader(const ImageSourceInfo& imageSourceInfo)
77 {
78 SrcType srcType = imageSourceInfo.GetSrcType();
79 switch (srcType) {
80 case SrcType::INTERNAL:
81 case SrcType::FILE: {
82 return MakeRefPtr<FileImageLoader>();
83 }
84 case SrcType::NETWORK: {
85 return MakeRefPtr<NetworkImageLoader>();
86 }
87 case SrcType::ASSET: {
88 return MakeRefPtr<AssetImageLoader>();
89 }
90 case SrcType::BASE64: {
91 return MakeRefPtr<Base64ImageLoader>();
92 }
93 case SrcType::RESOURCE: {
94 return MakeRefPtr<ResourceImageLoader>();
95 }
96 case SrcType::DATA_ABILITY: {
97 return MakeRefPtr<DataProviderImageLoader>();
98 }
99 case SrcType::MEMORY: {
100 LOGE("Image source type: shared memory. image data is not come from image loader.");
101 return nullptr;
102 }
103 case SrcType::RESOURCE_ID: {
104 return MakeRefPtr<InternalImageLoader>();
105 }
106 default: {
107 LOGE("Image source type not supported!");
108 return nullptr;
109 }
110 }
111 }
112
LoadDataFromCachedFile(const std::string & uri)113 sk_sp<SkData> ImageLoader::LoadDataFromCachedFile(const std::string& uri)
114 {
115 std::string cacheFilePath = ImageCache::GetImageCacheFilePath(uri);
116 if (cacheFilePath.length() > PATH_MAX) {
117 LOGE("cache file path is too long, cacheFilePath: %{private}s", cacheFilePath.c_str());
118 return nullptr;
119 }
120 bool cacheFileFound = ImageCache::GetFromCacheFile(cacheFilePath);
121 if (!cacheFileFound) {
122 return nullptr;
123 }
124 char realPath[PATH_MAX] = { 0x00 };
125 if (realpath(cacheFilePath.c_str(), realPath) == nullptr) {
126 LOGE("realpath fail! cacheFilePath: %{private}s, fail reason: %{public}s", cacheFilePath.c_str(),
127 strerror(errno));
128 return nullptr;
129 }
130 std::unique_ptr<FILE, decltype(&fclose)> file(fopen(realPath, "rb"), fclose);
131 if (file) {
132 return SkData::MakeFromFILE(file.get());
133 }
134 return nullptr;
135 }
136
LoadImageData(const ImageSourceInfo & imageSourceInfo,const WeakPtr<PipelineContext> context)137 sk_sp<SkData> FileImageLoader::LoadImageData(
138 const ImageSourceInfo& imageSourceInfo, const WeakPtr<PipelineContext> context)
139 {
140 auto src = imageSourceInfo.GetSrc();
141 std::string filePath = RemovePathHead(src);
142 if (imageSourceInfo.GetSrcType() == SrcType::INTERNAL) {
143 // the internal source uri format is like "internal://app/imagename.png", the absolute path of which is like
144 // "/data/data/{bundleName}/files/imagename.png"
145 auto bundleName = AceApplicationInfo::GetInstance().GetPackageName();
146 if (bundleName.empty()) {
147 LOGE("bundleName is empty, LoadImageData for internal source fail!");
148 return nullptr;
149 }
150 if (!StringUtils::StartWith(filePath, "app/")) { // "app/" is infix of internal path
151 LOGE("internal path format is wrong. path is %{private}s", src.c_str());
152 return nullptr;
153 }
154 filePath = std::string("/data/data/") // head of absolute path
155 .append(bundleName)
156 .append("/files/") // infix of absolute path
157 .append(filePath.substr(4)); // 4 is the length of "app/" from "internal://app/"
158 }
159 if (filePath.length() > PATH_MAX) {
160 LOGE("src path is too long");
161 return nullptr;
162 }
163 char realPath[PATH_MAX] = { 0x00 };
164 if (realpath(filePath.c_str(), realPath) == nullptr) {
165 LOGE("realpath fail! filePath: %{private}s, fail reason: %{public}s src:%{public}s", filePath.c_str(),
166 strerror(errno), src.c_str());
167 return nullptr;
168 }
169 std::unique_ptr<FILE, decltype(&fclose)> file(fopen(realPath, "rb"), fclose);
170 if (!file) {
171 LOGE("open file failed, filePath: %{private}s, fail reason: %{public}s", filePath.c_str(), strerror(errno));
172 return nullptr;
173 }
174 return SkData::MakeFromFILE(file.get());
175 }
176
LoadImageData(const ImageSourceInfo & imageSourceInfo,const WeakPtr<PipelineContext> context)177 sk_sp<SkData> DataProviderImageLoader::LoadImageData(
178 const ImageSourceInfo& imageSourceInfo, const WeakPtr<PipelineContext> context)
179 {
180 auto src = imageSourceInfo.GetSrc();
181 auto skData = ImageLoader::LoadDataFromCachedFile(src);
182 if (skData) {
183 return skData;
184 }
185 auto pipeline = context.Upgrade();
186 if (!pipeline) {
187 LOGE("the pipeline context is null");
188 return nullptr;
189 }
190 auto dataProvider = pipeline->GetDataProviderManager();
191 if (!dataProvider) {
192 LOGE("the data provider is null");
193 return nullptr;
194 }
195 auto dataRes = dataProvider->GetDataProviderResFromUri(src);
196 if (!dataRes || dataRes->GetData().size() == 0) {
197 LOGE("fail to get data res is from data provider");
198 return nullptr;
199 }
200 auto imageData = dataRes->GetData();
201 sk_sp<SkData> data = SkData::MakeWithCopy(imageData.data(), imageData.size());
202 BackgroundTaskExecutor::GetInstance().PostTask(
203 [src, imgData = std::move(imageData)]() { ImageCache::WriteCacheFile(src, imgData.data(), imgData.size()); },
204 BgTaskPriority::LOW);
205 return data;
206 }
207
LoadImageData(const ImageSourceInfo & imageSourceInfo,const WeakPtr<PipelineContext> context)208 sk_sp<SkData> AssetImageLoader::LoadImageData(
209 const ImageSourceInfo& imageSourceInfo, const WeakPtr<PipelineContext> context)
210 {
211 auto src = imageSourceInfo.GetSrc();
212 if (src.empty()) {
213 LOGE("image src is empty");
214 return nullptr;
215 }
216
217 std::string assetSrc(src);
218 if (assetSrc[0] == '/') {
219 assetSrc = assetSrc.substr(1); // get the asset src without '/'.
220 } else if (assetSrc[0] == '.' && assetSrc.size() > 2 && assetSrc[1] == '/') {
221 assetSrc = assetSrc.substr(2); // get the asset src without './'.
222 }
223 auto pipelineContext = context.Upgrade();
224 if (!pipelineContext) {
225 LOGE("invalid pipeline context");
226 return nullptr;
227 }
228 auto assetManager = pipelineContext->GetAssetManager();
229 if (!assetManager) {
230 LOGE("No asset manager!");
231 return nullptr;
232 }
233 auto assetData = assetManager->GetAsset(assetSrc);
234 if (!assetData) {
235 LOGE("No asset data!");
236 return nullptr;
237 }
238 const uint8_t* data = assetData->GetData();
239 const size_t dataSize = assetData->GetSize();
240 return SkData::MakeWithCopy(data, dataSize);
241 }
242
LoadJsonData(const std::string & src,const WeakPtr<PipelineContext> context)243 std::string AssetImageLoader::LoadJsonData(const std::string& src, const WeakPtr<PipelineContext> context)
244 {
245 if (src.empty()) {
246 LOGE("image src is empty");
247 return "";
248 }
249
250 std::string assetSrc(src);
251 if (assetSrc[0] == '/') {
252 assetSrc = assetSrc.substr(1); // get the asset src without '/'.
253 } else if (assetSrc[0] == '.' && assetSrc.size() > 2 && assetSrc[1] == '/') {
254 assetSrc = assetSrc.substr(2); // get the asset src without './'.
255 }
256 auto pipelineContext = context.Upgrade();
257 if (!pipelineContext) {
258 LOGE("invalid pipeline context");
259 return "";
260 }
261 auto assetManager = pipelineContext->GetAssetManager();
262 if (!assetManager) {
263 LOGE("No asset manager!");
264 return "";
265 }
266 auto assetData = assetManager->GetAsset(assetSrc);
267 if (!assetData || !assetData->GetData()) {
268 LOGE("No asset data!");
269 return "";
270 }
271 return std::string((char *)assetData->GetData(), assetData->GetSize());
272 }
273
LoadImageData(const ImageSourceInfo & imageSourceInfo,const WeakPtr<PipelineContext> context)274 sk_sp<SkData> NetworkImageLoader::LoadImageData(
275 const ImageSourceInfo& imageSourceInfo, const WeakPtr<PipelineContext> context)
276 {
277 auto uri = imageSourceInfo.GetSrc();
278 // 1. find in cache file path.
279 auto skData = ImageLoader::LoadDataFromCachedFile(uri);
280 if (skData) {
281 return skData;
282 }
283
284 // 2. if not found. download it.
285 std::vector<uint8_t> imageData;
286 if (!DownloadManager::GetInstance().Download(uri, imageData) || imageData.empty()) {
287 LOGE("Download image %{private}s failed!", uri.c_str());
288 return nullptr;
289 }
290 sk_sp<SkData> data = SkData::MakeWithCopy(imageData.data(), imageData.size());
291 // 3. write it into file cache.
292 BackgroundTaskExecutor::GetInstance().PostTask(
293 [uri, imgData = std::move(imageData)]() { ImageCache::WriteCacheFile(uri, imgData.data(), imgData.size()); },
294 BgTaskPriority::LOW);
295 return data;
296 }
297
LoadImageData(const ImageSourceInfo & imageSourceInfo,const WeakPtr<PipelineContext> context)298 sk_sp<SkData> InternalImageLoader::LoadImageData(
299 const ImageSourceInfo& imageSourceInfo, const WeakPtr<PipelineContext> context)
300 {
301 size_t imageSize = 0;
302 const uint8_t* internalData =
303 InternalResource::GetInstance().GetResource(imageSourceInfo.GetResourceId(), imageSize);
304 if (internalData == nullptr) {
305 LOGE("data null, the resource id may be wrong.");
306 return nullptr;
307 }
308 return SkData::MakeWithCopy(internalData, imageSize);
309 }
310
LoadImageData(const ImageSourceInfo & imageSourceInfo,const WeakPtr<PipelineContext> context)311 sk_sp<SkData> Base64ImageLoader::LoadImageData(
312 const ImageSourceInfo& imageSourceInfo, const WeakPtr<PipelineContext> context)
313 {
314 SkBase64 base64Decoder;
315 size_t imageSize = 0;
316 std::string base64Code = GetBase64ImageCode(imageSourceInfo.GetSrc(), imageSize);
317 SkBase64::Error error = base64Decoder.decode(base64Code.c_str(), base64Code.size());
318 if (error != SkBase64::kNoError) {
319 LOGE("error base64 image code!");
320 return nullptr;
321 }
322 auto base64Data = base64Decoder.getData();
323 const uint8_t* imageData = reinterpret_cast<uint8_t*>(base64Data);
324 auto resData = SkData::MakeWithCopy(imageData, imageSize);
325 // in SkBase64, the fData is not deleted after decoded.
326 if (base64Data != nullptr) {
327 delete[] base64Data;
328 base64Data = nullptr;
329 }
330 return resData;
331 }
332
GetBase64ImageCode(const std::string & uri,size_t & imageSize)333 std::string Base64ImageLoader::GetBase64ImageCode(const std::string& uri, size_t& imageSize)
334 {
335 auto iter = uri.find_first_of(',');
336 if (iter == std::string::npos || iter == uri.size() - 1) {
337 LOGE("wrong code format!");
338 imageSize = 0;
339 return std::string();
340 }
341 // iter + 1 to skip the ","
342 std::string code = uri.substr(iter + 1);
343 imageSize = GetBase64ImageSize(code);
344 return code;
345 }
346
GetBase64ImageSize(const std::string & code)347 size_t Base64ImageLoader::GetBase64ImageSize(const std::string& code)
348 {
349 // use base64 code size to calculate image byte size.
350 auto iter = code.rbegin();
351 int32_t count = 0;
352 // skip all '=' in the end.
353 while (*iter == '=') {
354 count++;
355 iter++;
356 }
357 // get the valid code length.
358 size_t codeSize = code.size() - count;
359 // compute the image byte size.
360 return codeSize - (codeSize / 8) * 2;
361 }
362
GetResourceId(const std::string & uri,const RefPtr<ThemeConstants> & themeContants,uint32_t & resId) const363 bool ResourceImageLoader::GetResourceId(const std::string& uri, const RefPtr<ThemeConstants>& themeContants,
364 uint32_t& resId) const
365 {
366 std::smatch matches;
367 if (std::regex_match(uri, matches, MEDIA_RES_ID_REGEX) && matches.size() == MEDIA_RESOURCE_MATCH_SIZE) {
368 resId = static_cast<uint32_t>(std::stoul(matches[1].str()));
369 return true;
370 }
371
372 std::smatch appMatches;
373 if (std::regex_match(uri, appMatches, MEDIA_APP_RES_ID_REGEX) && appMatches.size() == MEDIA_RESOURCE_MATCH_SIZE) {
374 resId = static_cast<uint32_t>(std::stoul(appMatches[1].str()));
375 return true;
376 }
377
378 return false;
379 }
380
GetResourceId(const std::string & uri,const RefPtr<ThemeConstants> & themeContants,std::string & path) const381 bool ResourceImageLoader::GetResourceId(const std::string& uri, const RefPtr<ThemeConstants>& themeContants,
382 std::string& path) const
383 {
384 std::smatch matches;
385 if (std::regex_match(uri, matches, MEDIA_APP_RES_PATH_REGEX) && matches.size() == MEDIA_RESOURCE_MATCH_SIZE) {
386 path = matches[1].str();
387 return true;
388 }
389
390 return false;
391 }
392
LoadImageData(const ImageSourceInfo & imageSourceInfo,const WeakPtr<PipelineContext> context)393 sk_sp<SkData> ResourceImageLoader::LoadImageData(
394 const ImageSourceInfo& imageSourceInfo, const WeakPtr<PipelineContext> context)
395 {
396 auto uri = imageSourceInfo.GetSrc();
397 auto pipelineContext = context.Upgrade();
398 if (!pipelineContext) {
399 LOGE("invalid pipeline context");
400 return nullptr;
401 }
402 auto themeManager = pipelineContext->GetThemeManager();
403 if (!themeManager) {
404 LOGE("get theme manager failed");
405 return nullptr;
406 }
407 auto themeContants = themeManager->GetThemeConstants();
408 if (!themeContants) {
409 LOGE("get theme constants failed");
410 return nullptr;
411 }
412 uint32_t resId = 0;
413 std::string path;
414 std::ostringstream osstream;
415 if (GetResourceId(uri, themeContants, resId)) {
416 auto ret = themeContants->GetMediaResource(resId, osstream);
417 if (!ret) {
418 LOGE("get resId image(%{public}s) from resource manager failed", uri.c_str());
419 return nullptr;
420 }
421 } else if (GetResourceId(uri, themeContants, path)) {
422 auto ret = themeContants->GetMediaResource(path, osstream);
423 if (!ret) {
424 LOGE("get path image(%{public}s) from resource manager failed", uri.c_str());
425 return nullptr;
426 }
427 } else {
428 LOGE("get image resource id failed");
429 return nullptr;
430 }
431
432 const auto& mediaRes = osstream.str();
433 return SkData::MakeWithCopy(mediaRes.c_str(), mediaRes.size());
434 }
435
436 } // namespace OHOS::Ace