xref: /aosp_15_r20/external/dynamic_depth/internal/xmpmeta/xmp_parser.cc (revision a62be0856e8e1158f43b03e41bbad10f4d005fde)
1*a62be085SSadaf Ebrahimi #include "xmpmeta/xmp_parser.h"
2*a62be085SSadaf Ebrahimi 
3*a62be085SSadaf Ebrahimi #include <algorithm>
4*a62be085SSadaf Ebrahimi #include <cassert>
5*a62be085SSadaf Ebrahimi #include <cstring>
6*a62be085SSadaf Ebrahimi #include <sstream>
7*a62be085SSadaf Ebrahimi #include <stack>
8*a62be085SSadaf Ebrahimi 
9*a62be085SSadaf Ebrahimi #include "android-base/logging.h"
10*a62be085SSadaf Ebrahimi #include "libxml/parser.h"
11*a62be085SSadaf Ebrahimi #include "strings/case.h"
12*a62be085SSadaf Ebrahimi #include "strings/numbers.h"
13*a62be085SSadaf Ebrahimi #include "xmpmeta/base64.h"
14*a62be085SSadaf Ebrahimi #include "xmpmeta/jpeg_io.h"
15*a62be085SSadaf Ebrahimi #include "xmpmeta/xml/const.h"
16*a62be085SSadaf Ebrahimi #include "xmpmeta/xml/deserializer_impl.h"
17*a62be085SSadaf Ebrahimi #include "xmpmeta/xml/search.h"
18*a62be085SSadaf Ebrahimi #include "xmpmeta/xml/utils.h"
19*a62be085SSadaf Ebrahimi #include "xmpmeta/xmp_const.h"
20*a62be085SSadaf Ebrahimi 
21*a62be085SSadaf Ebrahimi using ::dynamic_depth::xmpmeta::xml::DepthFirstSearch;
22*a62be085SSadaf Ebrahimi using ::dynamic_depth::xmpmeta::xml::DeserializerImpl;
23*a62be085SSadaf Ebrahimi using ::dynamic_depth::xmpmeta::xml::FromXmlChar;
24*a62be085SSadaf Ebrahimi using ::dynamic_depth::xmpmeta::xml::GetFirstDescriptionElement;
25*a62be085SSadaf Ebrahimi 
26*a62be085SSadaf Ebrahimi namespace dynamic_depth {
27*a62be085SSadaf Ebrahimi namespace xmpmeta {
28*a62be085SSadaf Ebrahimi namespace {
29*a62be085SSadaf Ebrahimi 
30*a62be085SSadaf Ebrahimi const char kJpgExtension[] = "jpg";
31*a62be085SSadaf Ebrahimi const char kJpegExtension[] = "jpeg";
32*a62be085SSadaf Ebrahimi 
BoolStringToBool(const string & bool_str,bool * value)33*a62be085SSadaf Ebrahimi bool BoolStringToBool(const string& bool_str, bool* value) {
34*a62be085SSadaf Ebrahimi   if (dynamic_depth::StringCaseEqual(bool_str, "true")) {
35*a62be085SSadaf Ebrahimi     *value = true;
36*a62be085SSadaf Ebrahimi     return true;
37*a62be085SSadaf Ebrahimi   }
38*a62be085SSadaf Ebrahimi   if (dynamic_depth::StringCaseEqual(bool_str, "false")) {
39*a62be085SSadaf Ebrahimi     *value = false;
40*a62be085SSadaf Ebrahimi     return true;
41*a62be085SSadaf Ebrahimi   }
42*a62be085SSadaf Ebrahimi   return false;
43*a62be085SSadaf Ebrahimi }
44*a62be085SSadaf Ebrahimi 
45*a62be085SSadaf Ebrahimi // Converts string_property to the type T.
46*a62be085SSadaf Ebrahimi template <typename T>
47*a62be085SSadaf Ebrahimi bool ConvertStringPropertyToType(const string& string_property, T* value);
48*a62be085SSadaf Ebrahimi 
49*a62be085SSadaf Ebrahimi // Gets the end of the XMP meta content. If there is no packet wrapper, returns
50*a62be085SSadaf Ebrahimi // data.length, otherwise returns 1 + the position of last '>' without '?'
51*a62be085SSadaf Ebrahimi // before it. Usually the packet wrapper end is "<?xpacket end="w"?>.
GetXmpContentEnd(const string & data)52*a62be085SSadaf Ebrahimi size_t GetXmpContentEnd(const string& data) {
53*a62be085SSadaf Ebrahimi   if (data.empty()) {
54*a62be085SSadaf Ebrahimi     return 0;
55*a62be085SSadaf Ebrahimi   }
56*a62be085SSadaf Ebrahimi   for (size_t i = data.size() - 1; i >= 1; --i) {
57*a62be085SSadaf Ebrahimi     if (data[i] == '>') {
58*a62be085SSadaf Ebrahimi       if (data[i - 1] != '?') {
59*a62be085SSadaf Ebrahimi         return i + 1;
60*a62be085SSadaf Ebrahimi       }
61*a62be085SSadaf Ebrahimi     }
62*a62be085SSadaf Ebrahimi   }
63*a62be085SSadaf Ebrahimi   // It should not reach here for a valid XMP meta.
64*a62be085SSadaf Ebrahimi   LOG(WARNING) << "Failed to find the end of the XMP meta content.";
65*a62be085SSadaf Ebrahimi   return data.size();
66*a62be085SSadaf Ebrahimi }
67*a62be085SSadaf Ebrahimi 
68*a62be085SSadaf Ebrahimi // True if 's' starts with substring 'x'.
StartsWith(const string & s,const string & x)69*a62be085SSadaf Ebrahimi bool StartsWith(const string& s, const string& x) {
70*a62be085SSadaf Ebrahimi   return s.size() >= x.size() && !s.compare(0, x.size(), x);
71*a62be085SSadaf Ebrahimi }
72*a62be085SSadaf Ebrahimi // True if 's' ends with substring 'x'.
EndsWith(const string & s,const string & x)73*a62be085SSadaf Ebrahimi bool EndsWith(const string& s, const string& x) {
74*a62be085SSadaf Ebrahimi   return s.size() >= x.size() && !s.compare(s.size() - x.size(), x.size(), x);
75*a62be085SSadaf Ebrahimi }
76*a62be085SSadaf Ebrahimi 
77*a62be085SSadaf Ebrahimi // Parses the first valid XMP section. Any other valid XMP section will be
78*a62be085SSadaf Ebrahimi // ignored.
ParseFirstValidXMPSection(const std::vector<Section> & sections,XmpData * xmp)79*a62be085SSadaf Ebrahimi bool ParseFirstValidXMPSection(const std::vector<Section>& sections,
80*a62be085SSadaf Ebrahimi                                XmpData* xmp) {
81*a62be085SSadaf Ebrahimi   for (const Section& section : sections) {
82*a62be085SSadaf Ebrahimi     if (StartsWith(section.data, XmpConst::Header())) {
83*a62be085SSadaf Ebrahimi       const size_t end = GetXmpContentEnd(section.data);
84*a62be085SSadaf Ebrahimi       // Increment header length by 1 for the null termination.
85*a62be085SSadaf Ebrahimi       const size_t header_length = strlen(XmpConst::Header()) + 1;
86*a62be085SSadaf Ebrahimi       // Check for integer underflow before subtracting.
87*a62be085SSadaf Ebrahimi       if (header_length >= end) {
88*a62be085SSadaf Ebrahimi         LOG(ERROR) << "Invalid content length: "
89*a62be085SSadaf Ebrahimi                    << static_cast<int>(end - header_length);
90*a62be085SSadaf Ebrahimi         return false;
91*a62be085SSadaf Ebrahimi       }
92*a62be085SSadaf Ebrahimi       const size_t content_length = end - header_length;
93*a62be085SSadaf Ebrahimi       // header_length is guaranteed to be <= data.size due to the if condition
94*a62be085SSadaf Ebrahimi       // above. If this contract changes we must add an additonal check.
95*a62be085SSadaf Ebrahimi       const char* content_start = &section.data[header_length];
96*a62be085SSadaf Ebrahimi       // xmlReadMemory requires an int. Before casting size_t to int we must
97*a62be085SSadaf Ebrahimi       // check for integer overflow.
98*a62be085SSadaf Ebrahimi       if (content_length > INT_MAX) {
99*a62be085SSadaf Ebrahimi         LOG(ERROR) << "First XMP section too large, size: " << content_length;
100*a62be085SSadaf Ebrahimi         return false;
101*a62be085SSadaf Ebrahimi       }
102*a62be085SSadaf Ebrahimi       *xmp->MutableStandardSection() = xmlReadMemory(
103*a62be085SSadaf Ebrahimi           content_start, static_cast<int>(content_length), nullptr, nullptr, 0);
104*a62be085SSadaf Ebrahimi       if (xmp->StandardSection() == nullptr) {
105*a62be085SSadaf Ebrahimi         LOG(WARNING) << "Failed to parse standard section.";
106*a62be085SSadaf Ebrahimi         return false;
107*a62be085SSadaf Ebrahimi       }
108*a62be085SSadaf Ebrahimi       return true;
109*a62be085SSadaf Ebrahimi     }
110*a62be085SSadaf Ebrahimi   }
111*a62be085SSadaf Ebrahimi   return false;
112*a62be085SSadaf Ebrahimi }
113*a62be085SSadaf Ebrahimi 
114*a62be085SSadaf Ebrahimi // Collects the extended XMP sections with the given name into a string. Other
115*a62be085SSadaf Ebrahimi // sections will be ignored.
GetExtendedXmpSections(const std::vector<Section> & sections,const string & section_name)116*a62be085SSadaf Ebrahimi string GetExtendedXmpSections(const std::vector<Section>& sections,
117*a62be085SSadaf Ebrahimi                               const string& section_name) {
118*a62be085SSadaf Ebrahimi   string extended_header = XmpConst::ExtensionHeader();
119*a62be085SSadaf Ebrahimi   extended_header += '\0' + section_name;
120*a62be085SSadaf Ebrahimi   // section_name is dynamically extracted from the xml file and can have an
121*a62be085SSadaf Ebrahimi   // arbitrary size. Check for integer overflow before addition.
122*a62be085SSadaf Ebrahimi   if (extended_header.size() > SIZE_MAX - XmpConst::ExtensionHeaderOffset()) {
123*a62be085SSadaf Ebrahimi     return "";
124*a62be085SSadaf Ebrahimi   }
125*a62be085SSadaf Ebrahimi   const size_t section_start_offset =
126*a62be085SSadaf Ebrahimi       extended_header.size() + XmpConst::ExtensionHeaderOffset();
127*a62be085SSadaf Ebrahimi 
128*a62be085SSadaf Ebrahimi   // Compute the size of the buffer to parse the extended sections.
129*a62be085SSadaf Ebrahimi   std::vector<const Section*> xmp_sections;
130*a62be085SSadaf Ebrahimi   std::vector<size_t> xmp_end_offsets;
131*a62be085SSadaf Ebrahimi   size_t buffer_size = 0;
132*a62be085SSadaf Ebrahimi   for (const Section& section : sections) {
133*a62be085SSadaf Ebrahimi     if (extended_header.empty() || StartsWith(section.data, extended_header)) {
134*a62be085SSadaf Ebrahimi       const size_t end_offset = section.data.size();
135*a62be085SSadaf Ebrahimi       const size_t section_size = end_offset - section_start_offset;
136*a62be085SSadaf Ebrahimi       if (end_offset < section_start_offset ||
137*a62be085SSadaf Ebrahimi           section_size > SIZE_MAX - buffer_size) {
138*a62be085SSadaf Ebrahimi         return "";
139*a62be085SSadaf Ebrahimi       }
140*a62be085SSadaf Ebrahimi       buffer_size += section_size;
141*a62be085SSadaf Ebrahimi       xmp_sections.push_back(&section);
142*a62be085SSadaf Ebrahimi       xmp_end_offsets.push_back(end_offset);
143*a62be085SSadaf Ebrahimi     }
144*a62be085SSadaf Ebrahimi   }
145*a62be085SSadaf Ebrahimi 
146*a62be085SSadaf Ebrahimi   // Copy all the relevant sections' data into a buffer.
147*a62be085SSadaf Ebrahimi   string buffer(buffer_size, '\0');
148*a62be085SSadaf Ebrahimi   if (buffer.size() != buffer_size) {
149*a62be085SSadaf Ebrahimi     return "";
150*a62be085SSadaf Ebrahimi   }
151*a62be085SSadaf Ebrahimi   size_t offset = 0;
152*a62be085SSadaf Ebrahimi   for (int i = 0; i < xmp_sections.size(); ++i) {
153*a62be085SSadaf Ebrahimi     const Section* section = xmp_sections[i];
154*a62be085SSadaf Ebrahimi     const size_t length = xmp_end_offsets[i] - section_start_offset;
155*a62be085SSadaf Ebrahimi     std::copy_n(&section->data[section_start_offset], length, &buffer[offset]);
156*a62be085SSadaf Ebrahimi     offset += length;
157*a62be085SSadaf Ebrahimi   }
158*a62be085SSadaf Ebrahimi   return buffer;
159*a62be085SSadaf Ebrahimi }
160*a62be085SSadaf Ebrahimi 
161*a62be085SSadaf Ebrahimi // Parses the extended XMP sections with the given name. All other sections
162*a62be085SSadaf Ebrahimi // will be ignored.
ParseExtendedXmpSections(const std::vector<Section> & sections,const string & section_name,XmpData * xmp_data)163*a62be085SSadaf Ebrahimi bool ParseExtendedXmpSections(const std::vector<Section>& sections,
164*a62be085SSadaf Ebrahimi                               const string& section_name, XmpData* xmp_data) {
165*a62be085SSadaf Ebrahimi   const string extended_sections =
166*a62be085SSadaf Ebrahimi       GetExtendedXmpSections(sections, section_name);
167*a62be085SSadaf Ebrahimi   // xmlReadMemory requires an int. Before casting size_t to int we must check
168*a62be085SSadaf Ebrahimi   // for integer overflow.
169*a62be085SSadaf Ebrahimi   if (extended_sections.size() > INT_MAX) {
170*a62be085SSadaf Ebrahimi     LOG(WARNING) << "Extended sections too large, size: "
171*a62be085SSadaf Ebrahimi                  << extended_sections.size();
172*a62be085SSadaf Ebrahimi     return false;
173*a62be085SSadaf Ebrahimi   }
174*a62be085SSadaf Ebrahimi   *xmp_data->MutableExtendedSection() = xmlReadMemory(
175*a62be085SSadaf Ebrahimi       extended_sections.data(), static_cast<int>(extended_sections.size()),
176*a62be085SSadaf Ebrahimi       nullptr, nullptr, XML_PARSE_HUGE);
177*a62be085SSadaf Ebrahimi   if (xmp_data->ExtendedSection() == nullptr) {
178*a62be085SSadaf Ebrahimi     LOG(WARNING) << "Failed to parse extended sections.";
179*a62be085SSadaf Ebrahimi     return false;
180*a62be085SSadaf Ebrahimi   }
181*a62be085SSadaf Ebrahimi   return true;
182*a62be085SSadaf Ebrahimi }
183*a62be085SSadaf Ebrahimi 
184*a62be085SSadaf Ebrahimi // Extracts a XmpData from a JPEG image stream.
ExtractXmpMeta(const bool skip_extended,std::istream * file,XmpData * xmp_data)185*a62be085SSadaf Ebrahimi bool ExtractXmpMeta(const bool skip_extended, std::istream* file,
186*a62be085SSadaf Ebrahimi                     XmpData* xmp_data) {
187*a62be085SSadaf Ebrahimi   // We cannot use CHECK because this is ported to AOSP.
188*a62be085SSadaf Ebrahimi   assert(xmp_data != nullptr);  // NOLINT
189*a62be085SSadaf Ebrahimi   xmp_data->Reset();
190*a62be085SSadaf Ebrahimi 
191*a62be085SSadaf Ebrahimi   ParseOptions parse_options;
192*a62be085SSadaf Ebrahimi   parse_options.read_meta_only = true;
193*a62be085SSadaf Ebrahimi   if (skip_extended) {
194*a62be085SSadaf Ebrahimi     parse_options.section_header = XmpConst::Header();
195*a62be085SSadaf Ebrahimi     parse_options.section_header_return_first = true;
196*a62be085SSadaf Ebrahimi   }
197*a62be085SSadaf Ebrahimi   const std::vector<Section> sections = Parse(parse_options, file);
198*a62be085SSadaf Ebrahimi   if (sections.empty()) {
199*a62be085SSadaf Ebrahimi     LOG(WARNING) << "No sections found.";
200*a62be085SSadaf Ebrahimi     return false;
201*a62be085SSadaf Ebrahimi   }
202*a62be085SSadaf Ebrahimi 
203*a62be085SSadaf Ebrahimi   if (!ParseFirstValidXMPSection(sections, xmp_data)) {
204*a62be085SSadaf Ebrahimi     LOG(WARNING) << "Could not parse first section.";
205*a62be085SSadaf Ebrahimi     return false;
206*a62be085SSadaf Ebrahimi   }
207*a62be085SSadaf Ebrahimi   if (skip_extended) {
208*a62be085SSadaf Ebrahimi     return true;
209*a62be085SSadaf Ebrahimi   }
210*a62be085SSadaf Ebrahimi   string extension_name;
211*a62be085SSadaf Ebrahimi   DeserializerImpl deserializer(
212*a62be085SSadaf Ebrahimi       GetFirstDescriptionElement(xmp_data->StandardSection()));
213*a62be085SSadaf Ebrahimi   if (!deserializer.ParseString(XmpConst::HasExtensionPrefix(),
214*a62be085SSadaf Ebrahimi                                 XmpConst::HasExtension(), &extension_name)) {
215*a62be085SSadaf Ebrahimi     // No extended sections present, so nothing to parse.
216*a62be085SSadaf Ebrahimi     return true;
217*a62be085SSadaf Ebrahimi   }
218*a62be085SSadaf Ebrahimi   if (!ParseExtendedXmpSections(sections, extension_name, xmp_data)) {
219*a62be085SSadaf Ebrahimi     LOG(WARNING) << "Extended sections present, but could not be parsed.";
220*a62be085SSadaf Ebrahimi     return false;
221*a62be085SSadaf Ebrahimi   }
222*a62be085SSadaf Ebrahimi   return true;
223*a62be085SSadaf Ebrahimi }
224*a62be085SSadaf Ebrahimi 
225*a62be085SSadaf Ebrahimi // Extracts the specified string attribute.
GetStringProperty(const xmlNodePtr node,const char * prefix,const char * property,string * value)226*a62be085SSadaf Ebrahimi bool GetStringProperty(const xmlNodePtr node, const char* prefix,
227*a62be085SSadaf Ebrahimi                        const char* property, string* value) {
228*a62be085SSadaf Ebrahimi   const xmlDocPtr doc = node->doc;
229*a62be085SSadaf Ebrahimi   for (const _xmlAttr* attribute = node->properties; attribute != nullptr;
230*a62be085SSadaf Ebrahimi        attribute = attribute->next) {
231*a62be085SSadaf Ebrahimi     if (attribute->ns &&
232*a62be085SSadaf Ebrahimi         strcmp(FromXmlChar(attribute->ns->prefix), prefix) == 0 &&
233*a62be085SSadaf Ebrahimi         strcmp(FromXmlChar(attribute->name), property) == 0) {
234*a62be085SSadaf Ebrahimi       xmlChar* attribute_string =
235*a62be085SSadaf Ebrahimi           xmlNodeListGetString(doc, attribute->children, 1);
236*a62be085SSadaf Ebrahimi       *value = FromXmlChar(attribute_string);
237*a62be085SSadaf Ebrahimi       xmlFree(attribute_string);
238*a62be085SSadaf Ebrahimi       return true;
239*a62be085SSadaf Ebrahimi     }
240*a62be085SSadaf Ebrahimi   }
241*a62be085SSadaf Ebrahimi   return false;
242*a62be085SSadaf Ebrahimi }
243*a62be085SSadaf Ebrahimi 
244*a62be085SSadaf Ebrahimi // Reads the contents of a node.
245*a62be085SSadaf Ebrahimi // E.g. <prefix:node_name>Contents Here</prefix:node_name>
ReadNodeContent(const xmlNodePtr node,const char * prefix,const char * node_name,string * value)246*a62be085SSadaf Ebrahimi bool ReadNodeContent(const xmlNodePtr node, const char* prefix,
247*a62be085SSadaf Ebrahimi                      const char* node_name, string* value) {
248*a62be085SSadaf Ebrahimi   auto* element = DepthFirstSearch(node, node_name);
249*a62be085SSadaf Ebrahimi   if (element == nullptr) {
250*a62be085SSadaf Ebrahimi     return false;
251*a62be085SSadaf Ebrahimi   }
252*a62be085SSadaf Ebrahimi   if (prefix != nullptr &&
253*a62be085SSadaf Ebrahimi       (element->ns == nullptr || element->ns->prefix == nullptr ||
254*a62be085SSadaf Ebrahimi        strcmp(FromXmlChar(element->ns->prefix), prefix) != 0)) {
255*a62be085SSadaf Ebrahimi     return false;
256*a62be085SSadaf Ebrahimi   }
257*a62be085SSadaf Ebrahimi   xmlChar* node_content = xmlNodeGetContent(element);
258*a62be085SSadaf Ebrahimi   *value = FromXmlChar(node_content);
259*a62be085SSadaf Ebrahimi   free(node_content);
260*a62be085SSadaf Ebrahimi   return true;
261*a62be085SSadaf Ebrahimi }
262*a62be085SSadaf Ebrahimi 
263*a62be085SSadaf Ebrahimi template <typename T>
ConvertStringPropertyToType(const string & string_property,T * value)264*a62be085SSadaf Ebrahimi bool ConvertStringPropertyToType(const string& string_property, T* value) {
265*a62be085SSadaf Ebrahimi   QCHECK(value) << "Cannot call this method on a generic type";
266*a62be085SSadaf Ebrahimi   return false;
267*a62be085SSadaf Ebrahimi }
268*a62be085SSadaf Ebrahimi 
269*a62be085SSadaf Ebrahimi template <>
ConvertStringPropertyToType(const string & string_property,bool * value)270*a62be085SSadaf Ebrahimi bool ConvertStringPropertyToType<bool>(const string& string_property,
271*a62be085SSadaf Ebrahimi                                        bool* value) {
272*a62be085SSadaf Ebrahimi   return BoolStringToBool(string_property, value);
273*a62be085SSadaf Ebrahimi }
274*a62be085SSadaf Ebrahimi 
275*a62be085SSadaf Ebrahimi template <>
ConvertStringPropertyToType(const string & string_property,double * value)276*a62be085SSadaf Ebrahimi bool ConvertStringPropertyToType<double>(const string& string_property,
277*a62be085SSadaf Ebrahimi                                          double* value) {
278*a62be085SSadaf Ebrahimi   *value = std::stod(string_property);
279*a62be085SSadaf Ebrahimi   return true;
280*a62be085SSadaf Ebrahimi }
281*a62be085SSadaf Ebrahimi 
282*a62be085SSadaf Ebrahimi template <>
ConvertStringPropertyToType(const string & string_property,int * value)283*a62be085SSadaf Ebrahimi bool ConvertStringPropertyToType<int>(const string& string_property,
284*a62be085SSadaf Ebrahimi                                       int* value) {
285*a62be085SSadaf Ebrahimi   *value = 0;
286*a62be085SSadaf Ebrahimi   for (int i = 0; i < string_property.size(); ++i) {
287*a62be085SSadaf Ebrahimi     if (!isdigit(string_property[i])) {
288*a62be085SSadaf Ebrahimi       return false;
289*a62be085SSadaf Ebrahimi     }
290*a62be085SSadaf Ebrahimi   }
291*a62be085SSadaf Ebrahimi 
292*a62be085SSadaf Ebrahimi   *value = std::atoi(string_property.c_str());  // NOLINT
293*a62be085SSadaf Ebrahimi   return true;
294*a62be085SSadaf Ebrahimi }
295*a62be085SSadaf Ebrahimi 
296*a62be085SSadaf Ebrahimi template <>
ConvertStringPropertyToType(const string & string_property,int64 * value)297*a62be085SSadaf Ebrahimi bool ConvertStringPropertyToType<int64>(const string& string_property,
298*a62be085SSadaf Ebrahimi                                         int64* value) {
299*a62be085SSadaf Ebrahimi   *value = std::stol(string_property);
300*a62be085SSadaf Ebrahimi   return true;
301*a62be085SSadaf Ebrahimi }
302*a62be085SSadaf Ebrahimi 
303*a62be085SSadaf Ebrahimi }  // namespace
304*a62be085SSadaf Ebrahimi 
ReadXmpHeader(const string & filename,const bool skip_extended,XmpData * xmp_data)305*a62be085SSadaf Ebrahimi bool ReadXmpHeader(const string& filename, const bool skip_extended,
306*a62be085SSadaf Ebrahimi                    XmpData* xmp_data) {
307*a62be085SSadaf Ebrahimi   string filename_lower = filename;
308*a62be085SSadaf Ebrahimi   std::transform(filename_lower.begin(), filename_lower.end(),
309*a62be085SSadaf Ebrahimi                  filename_lower.begin(), ::tolower);
310*a62be085SSadaf Ebrahimi   if (!EndsWith(filename_lower, kJpgExtension) &&
311*a62be085SSadaf Ebrahimi       !EndsWith(filename_lower, kJpegExtension)) {
312*a62be085SSadaf Ebrahimi     LOG(WARNING) << "XMP parse: only JPEG file is supported";
313*a62be085SSadaf Ebrahimi     return false;
314*a62be085SSadaf Ebrahimi   }
315*a62be085SSadaf Ebrahimi 
316*a62be085SSadaf Ebrahimi   std::ifstream file(filename.c_str(), std::ios::binary);
317*a62be085SSadaf Ebrahimi   if (!file.is_open()) {
318*a62be085SSadaf Ebrahimi     LOG(WARNING) << " Could not read file: " << filename;
319*a62be085SSadaf Ebrahimi     return false;
320*a62be085SSadaf Ebrahimi   }
321*a62be085SSadaf Ebrahimi   return ExtractXmpMeta(skip_extended, &file, xmp_data);
322*a62be085SSadaf Ebrahimi }
323*a62be085SSadaf Ebrahimi 
ReadXmpFromMemory(const string & jpeg_contents,const bool skip_extended,XmpData * xmp_data)324*a62be085SSadaf Ebrahimi bool ReadXmpFromMemory(const string& jpeg_contents, const bool skip_extended,
325*a62be085SSadaf Ebrahimi                        XmpData* xmp_data) {
326*a62be085SSadaf Ebrahimi   std::istringstream stream(jpeg_contents);
327*a62be085SSadaf Ebrahimi   return ExtractXmpMeta(skip_extended, &stream, xmp_data);
328*a62be085SSadaf Ebrahimi }
329*a62be085SSadaf Ebrahimi 
ReadXmpHeader(std::istream * input_stream,bool skip_extended,XmpData * xmp_data)330*a62be085SSadaf Ebrahimi bool ReadXmpHeader(std::istream* input_stream, bool skip_extended,
331*a62be085SSadaf Ebrahimi                    XmpData* xmp_data) {
332*a62be085SSadaf Ebrahimi   return ExtractXmpMeta(skip_extended, input_stream, xmp_data);
333*a62be085SSadaf Ebrahimi }
334*a62be085SSadaf Ebrahimi 
335*a62be085SSadaf Ebrahimi }  // namespace xmpmeta
336*a62be085SSadaf Ebrahimi }  // namespace dynamic_depth
337