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(§ion.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(§ion.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