• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "xmpmeta/jpeg_io.h"
2 
3 #include <fstream>
4 #include <sstream>
5 
6 #include "android-base/logging.h"
7 
8 namespace dynamic_depth {
9 namespace xmpmeta {
10 namespace {
11 
12 // File markers.
13 // See: http://www.fileformat.info/format/jpeg/egff.htm or
14 // https://en.wikipedia.org/wiki/JPEG
15 const int kSoi = 0xd8;   // Start of image marker.
16 const int kApp1 = 0xe1;  // Start of EXIF section.
17 const int kSos = 0xda;   // Start of scan.
18 
19 // Number of bytes used to store a section's length in a JPEG file.
20 const int kSectionLengthByteSize = 2;
21 
22 // Returns the number of bytes available to be read. Sets the seek position
23 // to the place it was in before calling this function.
GetBytesAvailable(std::istream * input_stream)24 size_t GetBytesAvailable(std::istream* input_stream) {
25   const std::streamoff pos = input_stream->tellg();
26   if (pos == -1) {
27     return 0;
28   }
29 
30   input_stream->seekg(0, std::ios::end);
31   if (!input_stream->good()) {
32     return 0;
33   }
34 
35   const std::streamoff end = input_stream->tellg();
36   if (end == -1) {
37     return 0;
38   }
39   input_stream->seekg(pos);
40 
41   if (end <= pos) {
42     return 0;
43   }
44   return end - pos;
45 }
46 
47 // Returns the first byte in the stream cast to an integer.
ReadByteAsInt(std::istream * input_stream)48 int ReadByteAsInt(std::istream* input_stream) {
49   unsigned char byte;
50   input_stream->read(reinterpret_cast<char*>(&byte), 1);
51   if (!input_stream->good()) {
52     // Return an invalid value - no byte can be read as -1.
53     return -1;
54   }
55   return static_cast<int>(byte);
56 }
57 
58 // Reads the length of a section from 2 bytes.
Read2ByteLength(std::istream * input_stream,bool * error)59 size_t Read2ByteLength(std::istream* input_stream, bool* error) {
60   const int length_high = ReadByteAsInt(input_stream);
61   const int length_low = ReadByteAsInt(input_stream);
62   if (length_high == -1 || length_low == -1) {
63     *error = true;
64     return 0;
65   }
66   *error = false;
67   return length_high << 8 | length_low;
68 }
69 
HasPrefixString(const string & to_check,const string & prefix)70 bool HasPrefixString(const string& to_check, const string& prefix) {
71   if (to_check.size() < prefix.size()) {
72     return false;
73   }
74   return std::equal(prefix.begin(), prefix.end(), to_check.begin());
75 }
76 
77 }  // namespace
78 
Section(const string & buffer)79 Section::Section(const string& buffer) {
80   marker = kApp1;
81   is_image_section = false;
82   data = buffer;
83 }
84 
IsMarkerApp1()85 bool Section::IsMarkerApp1() { return marker == kApp1; }
86 
Parse(const ParseOptions & options,std::istream * input_stream)87 std::vector<Section> Parse(const ParseOptions& options,
88                            std::istream* input_stream) {
89   std::vector<Section> sections;
90   // Return early if this is not the start of a JPEG section.
91   if (ReadByteAsInt(input_stream) != 0xff ||
92       ReadByteAsInt(input_stream) != kSoi) {
93     LOG(WARNING) << "File's first two bytes does not match the sequence \xff"
94                  << kSoi;
95     return std::vector<Section>();
96   }
97 
98   int chr;  // Short for character.
99   while ((chr = ReadByteAsInt(input_stream)) != -1) {
100     if (chr != 0xff) {
101       LOG(WARNING) << "Read non-padding byte: " << chr;
102       return sections;
103     }
104     // Skip padding bytes.
105     while ((chr = ReadByteAsInt(input_stream)) == 0xff) {
106     }
107     if (chr == -1) {
108       LOG(WARNING) << "No more bytes in file available to be read.";
109       return sections;
110     }
111 
112     const int marker = chr;
113     if (marker == kSos) {
114       // kSos indicates the image data will follow and no metadata after that,
115       // so read all data at one time.
116       if (!options.read_meta_only) {
117         Section section;
118         section.marker = marker;
119         section.is_image_section = true;
120         const size_t bytes_available = GetBytesAvailable(input_stream);
121         section.data.resize(bytes_available);
122         input_stream->read(&section.data[0], bytes_available);
123         if (input_stream->good()) {
124           sections.push_back(section);
125         }
126       }
127       // All sections have been read.
128       return sections;
129     }
130 
131     bool error;
132     const size_t length = Read2ByteLength(input_stream, &error);
133     if (error || length < kSectionLengthByteSize) {
134       // No sections to read.
135       LOG(WARNING) << "No sections to read; section length is " << length;
136       return sections;
137     }
138 
139     const size_t bytes_left = GetBytesAvailable(input_stream);
140     if (length - kSectionLengthByteSize > bytes_left) {
141       LOG(WARNING) << "Invalid section length = " << length
142                    << " total bytes available = " << bytes_left;
143       return sections;
144     }
145 
146     if (!options.read_meta_only || marker == kApp1) {
147       Section section;
148       section.marker = marker;
149       section.is_image_section = false;
150       const size_t data_size = length - kSectionLengthByteSize;
151       section.data.resize(data_size);
152       if (section.data.size() != data_size) {
153         LOG(WARNING) << "Discrepancy in section data size "
154                      << section.data.size() << "and data size " << data_size;
155         return sections;
156       }
157       input_stream->read(&section.data[0], section.data.size());
158       if (input_stream->good() &&
159           (options.section_header.empty() ||
160            HasPrefixString(section.data, options.section_header))) {
161         sections.push_back(section);
162         // Return if we have specified to return the 1st section with
163         // the given name.
164         if (options.section_header_return_first) {
165           return sections;
166         }
167       }
168     } else {
169       // Skip this section since all EXIF/XMP meta will be in kApp1 section.
170       input_stream->ignore(length - kSectionLengthByteSize);
171     }
172   }
173   return sections;
174 }
175 
WriteSections(const std::vector<Section> & sections,std::ostream * output_stream)176 void WriteSections(const std::vector<Section>& sections,
177                    std::ostream* output_stream) {
178   output_stream->put(0xff);
179   output_stream->put(static_cast<unsigned char>(kSoi));
180   for (const Section& section : sections) {
181     output_stream->put(0xff);
182     output_stream->put(section.marker);
183     if (!section.is_image_section) {
184       const int section_length = static_cast<int>(section.data.length()) + 2;
185       // It's not the image data.
186       const int lh = section_length >> 8;
187       const int ll = section_length & 0xff;
188       output_stream->put(lh);
189       output_stream->put(ll);
190     }
191     output_stream->write(section.data.c_str(), section.data.length());
192   }
193 }
194 
195 }  // namespace xmpmeta
196 }  // namespace dynamic_depth
197