• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 <libexif/exif-data.h>
17 #include <zlib.h>
18 #include <array>
19 
20 #include "data_buf.h"
21 #include "exif_metadata.h"
22 #include "image_log.h"
23 #include "media_errors.h"
24 #include "metadata_stream.h"
25 #include "png_exif_metadata_accessor.h"
26 #include "png_image_chunk_utils.h"
27 #include "tiff_parser.h"
28 
29 #undef LOG_DOMAIN
30 #define LOG_DOMAIN LOG_TAG_DOMAIN_ID_IMAGE
31 
32 #undef LOG_TAG
33 #define LOG_TAG "PngImageChunkUtils"
34 
35 namespace OHOS {
36 namespace Media {
37 namespace {
38 constexpr auto ASCII_TO_HEX_MAP_SIZE = 103;
39 constexpr auto IMAGE_SEG_MAX_SIZE = 65536;
40 constexpr auto EXIF_HEADER_SIZE = 6;
41 constexpr auto PNG_CHUNK_KEYWORD_EXIF_APP1_SIZE = 21;
42 constexpr auto HEX_BASE = 16;
43 constexpr auto DECIMAL_BASE = 10;
44 constexpr auto PNG_PROFILE_EXIF = "Raw profile type exif";
45 constexpr auto PNG_PROFILE_APP1 = "Raw profile type APP1";
46 constexpr auto CHUNK_COMPRESS_METHOD_VALID = 0;
47 constexpr auto CHUNK_FLAG_COMPRESS_NO = 0;
48 constexpr auto CHUNK_FLAG_COMPRESS_YES = 1;
49 constexpr auto NULL_CHAR_AMOUNT = 2;
50 constexpr auto HEX_STRING_UNIT_SIZE = 2;
51 constexpr auto EXIF_INFO_LENGTH_TWO = 2;
52 constexpr auto CHUNKDATA_KEYSIZE_OFFSET_ONE = 1;
53 constexpr auto CHUNKDATA_KEYSIZE_OFFSET_THREE = 3;
54 }
55 
ParseTextChunk(const DataBuf & chunkData,TextChunkType chunkType,DataBuf & tiffData,bool & isCompressed)56 int PngImageChunkUtils::ParseTextChunk(const DataBuf &chunkData, TextChunkType chunkType,
57     DataBuf &tiffData, bool &isCompressed)
58 {
59     DataBuf keyword = GetKeywordFromChunk(chunkData);
60     if (keyword.Empty()) {
61         IMAGE_LOGE("Failed to read the keyword from the chunk data. Chunk data size: %{public}zu", chunkData.Size());
62         return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
63     }
64 
65     bool foundExifKeyword = FindExifKeyword(keyword.CData(), keyword.Size());
66     if (!foundExifKeyword) {
67         return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
68     }
69 
70     DataBuf rawText = GetRawTextFromChunk(chunkData, keyword.Size(), chunkType, isCompressed);
71     if (rawText.Empty()) {
72         IMAGE_LOGE("Failed to read the raw text from the chunk data. Chunk data size: %{public}zu", chunkData.Size());
73         return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
74     }
75 
76     return GetTiffDataFromRawText(rawText, tiffData);
77 }
78 
GetKeywordFromChunk(const DataBuf & chunkData)79 DataBuf PngImageChunkUtils::GetKeywordFromChunk(const DataBuf &chunkData)
80 {
81     if (chunkData.Size() <= 0) {
82         IMAGE_LOGE("Data size check failed: offset is larger than data size. "
83             "Data size: %{public}zu",
84             chunkData.Size());
85         return {};
86     }
87 
88     auto keyword = std::find(chunkData.CBegin(), chunkData.CEnd(), 0);
89     if (keyword == chunkData.CEnd()) {
90         IMAGE_LOGE("Keyword lookup failed: keyword not found in chunk data. "
91             "Chunk data size: %{public}zu",
92             chunkData.Size());
93         return {};
94     }
95     const size_t keywordLength = static_cast<size_t>(std::distance(chunkData.CBegin(), keyword));
96     return { chunkData.CData(), keywordLength };
97 }
98 
GetRawTextFromZtxtChunk(const DataBuf & chunkData,size_t keySize,DataBuf & rawText,bool & isCompressed)99 DataBuf PngImageChunkUtils::GetRawTextFromZtxtChunk(const DataBuf &chunkData, size_t keySize,
100     DataBuf &rawText, bool &isCompressed)
101 {
102     if (chunkData.Empty() || chunkData.CData() == nullptr) {
103         IMAGE_LOGE("Failed to get raw text from ztxt: chunkData is null. ");
104         return {};
105     }
106     if (chunkData.CData(keySize + CHUNKDATA_KEYSIZE_OFFSET_ONE) == nullptr) {
107         IMAGE_LOGE("Failed to get raw text from itxt:  keySize + 1 greater than chunklength.");
108         return {};
109     }
110     if (*(chunkData.CData(keySize + 1)) != CHUNK_COMPRESS_METHOD_VALID) {
111         IMAGE_LOGE("Metadata corruption detected: Invalid compression method. "
112             "Expected: %{public}d, Found: %{public}d",
113             CHUNK_COMPRESS_METHOD_VALID, *(chunkData.CData(keySize + 1)));
114         return {};
115     }
116 
117     size_t compressedTextSize = chunkData.Size() - keySize - NULL_CHAR_AMOUNT;
118     if (compressedTextSize > 0) {
119         const byte *compressedText = chunkData.CData(keySize + NULL_CHAR_AMOUNT);
120         int ret = DecompressText(compressedText, static_cast<uint32_t>(compressedTextSize), rawText);
121         if (ret != 0) {
122             IMAGE_LOGE("Failed to decompress text. Return code: %{public}d", ret);
123             return {};
124         }
125         isCompressed = true;
126     }
127     return rawText;
128 }
129 
GetRawTextFromTextChunk(const DataBuf & chunkData,size_t keySize,DataBuf & rawText)130 DataBuf PngImageChunkUtils::GetRawTextFromTextChunk(const DataBuf &chunkData, size_t keySize, DataBuf &rawText)
131 {
132     size_t rawTextsize = chunkData.Size() - keySize - 1;
133     if (rawTextsize) {
134         const byte *textPosition = chunkData.CData(keySize + 1);
135         rawText = DataBuf(textPosition, rawTextsize);
136     }
137     return rawText;
138 }
139 
FetchString(const char * chunkData,size_t dataLength)140 std::string FetchString(const char *chunkData, size_t dataLength)
141 {
142     if (chunkData == nullptr) {
143         IMAGE_LOGE("ChunkData is null. Cannot fetch string.");
144         return {};
145     }
146     if (dataLength == 0) {
147         IMAGE_LOGE("Data length is zero. Cannot fetch string.");
148         return {};
149     }
150     const size_t stringLength = strnlen(chunkData, dataLength);
151     return { chunkData, stringLength };
152 }
153 
CheckChunkData(const DataBuf & chunkData,size_t keySize)154 bool CheckChunkData(const DataBuf &chunkData, size_t keySize)
155 {
156     const byte compressionFlag = chunkData.ReadUInt8(keySize + 1);
157     const byte compressionMethod = chunkData.ReadUInt8(keySize + 2);
158     if ((compressionFlag != CHUNK_FLAG_COMPRESS_NO) && (compressionFlag != CHUNK_FLAG_COMPRESS_YES)) {
159         IMAGE_LOGE("Metadata corruption detected: Invalid compression flag. "
160             "Expected: %{public}d or %{public}d, Found: %{public}d",
161             CHUNK_FLAG_COMPRESS_NO, CHUNK_FLAG_COMPRESS_YES, compressionFlag);
162         return false;
163     }
164 
165     if ((compressionFlag == CHUNK_FLAG_COMPRESS_YES) && (compressionMethod != CHUNK_COMPRESS_METHOD_VALID)) {
166         IMAGE_LOGE("Metadata corruption detected: Invalid compression method. "
167             "Expected: %{public}d, Found: %{public}d",
168             CHUNK_COMPRESS_METHOD_VALID, compressionMethod);
169         return false;
170     }
171     return true;
172 }
173 
GetRawTextFromItxtChunk(const DataBuf & chunkData,size_t keySize,DataBuf & rawText,bool & isCompressed)174 DataBuf PngImageChunkUtils::GetRawTextFromItxtChunk(const DataBuf &chunkData, size_t keySize,
175     DataBuf &rawText, bool &isCompressed)
176 {
177     if (chunkData.CData(keySize + CHUNKDATA_KEYSIZE_OFFSET_THREE) == nullptr) {
178         IMAGE_LOGE("Failed to get raw text from itxt:  keySize + 3 greater than chunklength.");
179         return {};
180     }
181     const size_t nullCount = static_cast<size_t>(std::count(chunkData.CData(keySize + 3),
182                                                             chunkData.CData(chunkData.Size() - 1), '\0'));
183     if (nullCount < NULL_CHAR_AMOUNT) {
184         IMAGE_LOGE("Metadata corruption detected: Null character count after "
185             "Language tag is less than 2. Found: %{public}zu",
186             nullCount);
187         return {};
188     }
189     if (!CheckChunkData(chunkData, keySize)) {
190         return {};
191     }
192     const byte compressionFlag = chunkData.ReadUInt8(keySize + 1);
193 
194     const size_t languageTextPos = keySize + 3;
195     const size_t languageTextMaxLen = chunkData.Size() - keySize - 3;
196     std::string languageText =
197         FetchString(reinterpret_cast<const char *>(chunkData.CData(languageTextPos)), languageTextMaxLen);
198     const size_t languageTextLen = languageText.size();
199 
200     const size_t translatedKeyPos = languageTextPos + languageTextLen + 1;
201     std::string translatedKeyText = FetchString(reinterpret_cast<const char *>(chunkData.CData(translatedKeyPos)),
202         chunkData.Size() - translatedKeyPos);
203     const size_t translatedKeyTextLen = translatedKeyText.size();
204 
205     const size_t textLen = chunkData.Size() - (keySize + 3 + languageTextLen + 1 + translatedKeyTextLen + 1);
206     if (textLen == 0) {
207         return {};
208     }
209 
210     const size_t textPosition = translatedKeyPos + translatedKeyTextLen + 1;
211     const byte *textPtr = chunkData.CData(textPosition);
212     if (compressionFlag == CHUNK_FLAG_COMPRESS_NO) {
213         rawText = DataBuf(textPtr, textLen);
214     } else {
215         int ret = DecompressText(textPtr, textLen, rawText);
216         if (ret != 0) {
217             IMAGE_LOGE("Decompress text failed.");
218             return {};
219         }
220         isCompressed = true;
221     }
222     return rawText;
223 }
224 
GetRawTextFromChunk(const DataBuf & chunkData,size_t keySize,TextChunkType chunkType,bool & isCompressed)225 DataBuf PngImageChunkUtils::GetRawTextFromChunk(const DataBuf &chunkData, size_t keySize,
226     TextChunkType chunkType, bool &isCompressed)
227 {
228     DataBuf rawText;
229     isCompressed = false;
230     if (chunkType == zTXtChunk) {
231         GetRawTextFromZtxtChunk(chunkData, keySize, rawText, isCompressed);
232     } else if (chunkType == tEXtChunk) {
233         GetRawTextFromTextChunk(chunkData, keySize, rawText);
234     } else if (chunkType == iTXtChunk) {
235         GetRawTextFromItxtChunk(chunkData, keySize, rawText, isCompressed);
236     } else {
237         IMAGE_LOGE("Unexpected chunk type encountered: %{public}d", chunkType);
238         return {};
239     }
240     return rawText;
241 }
242 
FindExifKeyword(const byte * keyword,size_t size)243 bool PngImageChunkUtils::FindExifKeyword(const byte *keyword, size_t size)
244 {
245     if ((keyword == nullptr) || (size < PNG_CHUNK_KEYWORD_EXIF_APP1_SIZE)) {
246         return false;
247     }
248     if ((memcmp(PNG_PROFILE_EXIF, keyword, PNG_CHUNK_KEYWORD_EXIF_APP1_SIZE) == 0) ||
249         (memcmp(PNG_PROFILE_APP1, keyword, PNG_CHUNK_KEYWORD_EXIF_APP1_SIZE) == 0)) {
250         return true;
251     }
252     return false;
253 }
254 
FindExifFromTxt(DataBuf & chunkData)255 bool PngImageChunkUtils::FindExifFromTxt(DataBuf &chunkData)
256 {
257     DataBuf keyword = GetKeywordFromChunk(chunkData);
258     if (keyword.Empty()) {
259         IMAGE_LOGE("Failed to read the keyword from chunk.");
260         return false;
261     }
262 
263     bool foundExifKeyword = FindExifKeyword(keyword.CData(), keyword.Size());
264     CHECK_INFO_RETURN_RET_LOG(!foundExifKeyword, false, "The text chunk is without exif keyword");
265     return true;
266 }
267 
VerifyExifIdCode(DataBuf & exifInfo,size_t exifInfoLength)268 size_t PngImageChunkUtils::VerifyExifIdCode(DataBuf &exifInfo, size_t exifInfoLength)
269 {
270     static const std::array<byte, EXIF_HEADER_SIZE> exifIdCode { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
271     size_t exifIdPos = std::numeric_limits<size_t>::max();
272 
273     for (size_t i = 0; i < exifInfoLength - exifIdCode.size(); i++) {
274         if (exifInfo.CmpBytes(i, exifIdCode.data(), exifIdCode.size()) == 0) {
275             exifIdPos = i;
276             break;
277         }
278     }
279     return exifIdPos;
280 }
281 
GetTiffDataFromRawText(const DataBuf & rawText,DataBuf & tiffData)282 int PngImageChunkUtils::GetTiffDataFromRawText(const DataBuf &rawText, DataBuf &tiffData)
283 {
284     DataBuf exifInfo = ConvertRawTextToExifInfo(rawText);
285     if (exifInfo.Empty()) {
286         IMAGE_LOGE("Unable to parse Exif metadata: conversion from text to hex failed");
287         return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
288     }
289 
290     size_t exifInfoLength = exifInfo.Size();
291     if (exifInfoLength < EXIF_HEADER_SIZE) {
292         IMAGE_LOGE("Unable to parse Exif metadata: data length insufficient. "
293             "Actual: %{public}zu, Expected: %{public}d",
294             exifInfoLength, EXIF_HEADER_SIZE);
295         return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
296     }
297 
298     size_t exifHeadPos = VerifyExifIdCode(exifInfo, exifInfoLength);
299     if (exifHeadPos == std::numeric_limits<size_t>::max()) {
300         IMAGE_LOGE("Unable to parse metadata: Exif header not found");
301         return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
302     }
303 
304     size_t tiffOffset = EXIF_HEADER_SIZE;
305     tiffData = DataBuf(exifInfo.CData(tiffOffset), exifInfoLength - tiffOffset);
306     if (tiffData.Empty()) {
307         IMAGE_LOGE("Unable to extract Tiff data: data length insufficient. "
308             "Actual: %{public}zu, Expected: %{public}zu",
309             tiffData.Size(), exifInfoLength - tiffOffset);
310         return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
311     }
312     return SUCCESS;
313 }
314 
DecompressText(const byte * sourceData,unsigned int sourceDataLen,DataBuf & textOut)315 int PngImageChunkUtils::DecompressText(const byte *sourceData, unsigned int sourceDataLen, DataBuf &textOut)
316 {
317     if (sourceDataLen > IMAGE_SEG_MAX_SIZE) {
318         IMAGE_LOGE("Decompression failed: data size exceeds limit. "
319             "Data size: %{public}u, Limit: %{public}d",
320             sourceDataLen, IMAGE_SEG_MAX_SIZE);
321         return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
322     }
323     uLongf destDataLen = IMAGE_SEG_MAX_SIZE;
324 
325     textOut.Resize(destDataLen);
326     int result = uncompress(textOut.Data(), &destDataLen, sourceData, sourceDataLen);
327     if (result != Z_OK) {
328         IMAGE_LOGE("Decompression failed: job aborted");
329         return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
330     }
331     textOut.Resize(destDataLen);
332     return SUCCESS;
333 }
334 
StepOverNewLine(const char * sourcePtr,const char * endPtr)335 const char *PngImageChunkUtils::StepOverNewLine(const char *sourcePtr, const char *endPtr)
336 {
337     while (*sourcePtr != '\n') {
338         sourcePtr++;
339         if (sourcePtr == endPtr) {
340             return NULL;
341         }
342     }
343     sourcePtr++;
344     if (sourcePtr == endPtr) {
345         return NULL;
346     }
347     return sourcePtr;
348 }
349 
GetExifInfoLen(const char * sourcePtr,size_t * lengthOut,const char * endPtr)350 const char *PngImageChunkUtils::GetExifInfoLen(const char *sourcePtr, size_t *lengthOut, const char *endPtr)
351 {
352     while ((*sourcePtr == '\0') || (*sourcePtr == ' ') || (*sourcePtr == '\n')) {
353         sourcePtr++;
354         if (sourcePtr == endPtr) {
355             IMAGE_LOGE("Unable to get Exif length: content is blank");
356             return NULL;
357         }
358     }
359 
360     size_t exifLength = 0;
361     while (('0' <= *sourcePtr) && (*sourcePtr <= '9')) {
362         const size_t newlength = (DECIMAL_BASE * exifLength) + (*sourcePtr - '0');
363         exifLength = newlength;
364         sourcePtr++;
365         if (sourcePtr == endPtr) {
366             IMAGE_LOGE("Unable to get Exif length: no digit content found");
367             return NULL;
368         }
369     }
370     sourcePtr++; // ignore the '\n' character
371     if (sourcePtr == endPtr) {
372         IMAGE_LOGE("Unable to get Exif length: Exif info not found");
373         return NULL;
374     }
375     *lengthOut = exifLength;
376     return sourcePtr;
377 }
378 
ConvertAsciiToInt(const char * sourcePtr,size_t exifInfoLength,unsigned char * destPtr)379 int PngImageChunkUtils::ConvertAsciiToInt(const char *sourcePtr, size_t exifInfoLength, unsigned char *destPtr)
380 {
381     if (sourcePtr == nullptr || destPtr == nullptr) {
382         IMAGE_LOGE("The scrPointer or destPointer is not valid.");
383         return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
384     }
385 
386     static const unsigned char hexAsciiToInt[ASCII_TO_HEX_MAP_SIZE] = {
387         0, 0, 0, 0, 0,    0, 0, 0, 0, 0,    0, 0, 0, 0, 0,    0, 0, 0, 0, 0,
388         0, 0, 0, 0, 0,    0, 0, 0, 0, 0,    0, 0, 0, 0, 0,    0, 0, 0, 0, 0,
389         0, 0, 0, 0, 0,    0, 0, 0, 0, 1,    2, 3, 4, 5, 6,    7, 8, 9, 0, 0,
390         0, 0, 0, 0, 0,    0, 0, 0, 0, 0,    0, 0, 0, 0, 0,    0, 0, 0, 0, 0,
391         0, 0, 0, 0, 0,    0, 0, 0, 0, 0,    0, 0, 0, 0, 0,    0, 0, 10, 11, 12,
392         13, 14, 15,
393     };
394 
395     size_t sourceLength = exifInfoLength * 2;
396     size_t sourcePtrCount = 0;
397     for (size_t i = 0; i < sourceLength && sourcePtrCount < sourceLength; i++) {
398         while (sourcePtrCount < sourceLength &&
399             ((*sourcePtr < '0') || ((*sourcePtr > '9') && (*sourcePtr < 'a')) || (*sourcePtr > 'f'))) {
400             if (*sourcePtr == '\0') {
401                 IMAGE_LOGE("Unexpected null character encountered while converting Exif ASCII string. "
402                     "Position: %{public}zu, Expected length: %{public}zu",
403                     i, sourceLength);
404                 return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
405             }
406             sourcePtr++;
407             sourcePtrCount++;
408         }
409 
410         if ((i % HEX_STRING_UNIT_SIZE) == 0) {
411             *destPtr = static_cast<unsigned char>(HEX_BASE * hexAsciiToInt[static_cast<size_t>(*sourcePtr++)]);
412         } else {
413             (*destPtr++) += hexAsciiToInt[static_cast<size_t>(*sourcePtr++)];
414         }
415         sourcePtrCount++;
416     }
417     return SUCCESS;
418 }
419 
ConvertRawTextToExifInfo(const DataBuf & rawText)420 DataBuf PngImageChunkUtils::ConvertRawTextToExifInfo(const DataBuf &rawText)
421 {
422     if (rawText.Size() <= 1) {
423         IMAGE_LOGE("The size of the raw profile text is too small.");
424         return {};
425     }
426     const char *sourcePtr = reinterpret_cast<const char *>(rawText.CData(1));
427     const char *endPtr = reinterpret_cast<const char *>(rawText.CData(rawText.Size() - 1));
428 
429     if (sourcePtr >= endPtr) {
430         IMAGE_LOGE("The source pointer is not valid.");
431         return {};
432     }
433     sourcePtr = StepOverNewLine(sourcePtr, endPtr);
434     if (sourcePtr == NULL) {
435         IMAGE_LOGE("Error encountered when stepping over new line in raw profile text.");
436         return {};
437     }
438 
439     size_t exifInfoLength = 0;
440     sourcePtr = GetExifInfoLen(sourcePtr, &exifInfoLength, endPtr);
441     if (sourcePtr == NULL) {
442         IMAGE_LOGE("Error encountered when getting the length of the string in raw profile text.");
443         return {};
444     }
445 
446     if ((exifInfoLength == 0) || (exifInfoLength > rawText.Size())) {
447         IMAGE_LOGE("Invalid text length in raw profile text.");
448         return {};
449     }
450 
451     DataBuf exifInfo;
452     exifInfo.Resize(exifInfoLength);
453     if (exifInfo.Size() != exifInfoLength) {
454         IMAGE_LOGE("Unable to allocate memory for Exif information.");
455         return {};
456     }
457     if (exifInfo.Empty()) {
458         return exifInfo;
459     }
460     unsigned char *destPtr = exifInfo.Data();
461     if (sourcePtr + EXIF_INFO_LENGTH_TWO * exifInfoLength > endPtr) {
462         IMAGE_LOGE("Invalid text length in raw profile text, it will result in OOB.");
463         return {};
464     }
465     int ret = ConvertAsciiToInt(sourcePtr, exifInfoLength, destPtr);
466     if (ret != 0) {
467         IMAGE_LOGE("Error encountered when converting Exif string ASCII to integer.");
468         return {};
469     }
470 
471     return exifInfo;
472 }
473 } // namespace Media
474 } // namespace OHOS
475