1 // ***************************************************************** -*- C++ -*-
2 /*
3  * Copyright (C) 2004-2017 Andreas Huggel <ahuggel@gmx.net>
4  *
5  * This program is part of the Exiv2 distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
20  */
21 /*
22   File:      epsimage.cpp
23   Version:   $Rev: 2455 $
24   Author(s): Michael Ulbrich (mul) <mul@rentapacs.de>
25              Volker Grabsch (vog) <vog@notjusthosting.com>
26   History:   7-Mar-2011, vog: created
27  */
28 // *****************************************************************************
29 #include "rcsid_int.hpp"
30 EXIV2_RCSID("@(#) $Id: epsimage.cpp $")
31 
32 // included header files
33 #include "config_exiv2.h"
34 
35 #include "epsimage.hpp"
36 #include "image.hpp"
37 #include "basicio.hpp"
38 #include "error.hpp"
39 #include "futils.hpp"
40 
41 // + standard includes
42 #include <algorithm>
43 #include <cassert>
44 #include <climits>
45 #include <cstring>
46 #include <iostream>
47 #include <sstream>
48 #include <string>
49 
50 // *****************************************************************************
51 namespace {
52 
53     using namespace Exiv2;
54     using Exiv2::byte;
55 
56     // signature of DOS EPS
57     const std::string dosEpsSignature = "\xC5\xD0\xD3\xC6";
58 
59     // first line of EPS
60     const std::string epsFirstLine[] = {
61         "%!PS-Adobe-3.0 EPSF-3.0",
62         "%!PS-Adobe-3.0 EPSF-3.0 ", // OpenOffice
63         "%!PS-Adobe-3.1 EPSF-3.0",  // Illustrator
64     };
65 
66     // blank EPS file
67     const std::string epsBlank = "%!PS-Adobe-3.0 EPSF-3.0\n"
68                                  "%%BoundingBox: 0 0 0 0\n";
69 
70     // list of all valid XMP headers
71     const std::string xmpHeaders[] = {
72 
73         // We do not enforce the trailing "?>" here, because the XMP specification
74         // permits additional attributes after begin="..." and id="...".
75 
76         // normal headers
77         "<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"",
78         "<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'",
79         "<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"",
80         "<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'",
81 
82         // deprecated headers (empty begin attribute, UTF-8 only)
83         "<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"",
84         "<?xpacket begin=\"\" id='W5M0MpCehiHzreSzNTczkc9d'",
85         "<?xpacket begin='' id=\"W5M0MpCehiHzreSzNTczkc9d\"",
86         "<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'",
87     };
88 
89     // list of all valid XMP trailers
90     struct XmpTrailer {
91         std::string trailer;
92         bool readOnly;
93     };
94 
95     const XmpTrailer xmpTrailers[] = {
96 
97         // We do not enforce the trailing "?>" here, because the XMP specification
98         // permits additional attributes after end="...".
99 
100         {"<?xpacket end=\"r\"", true},
101         {"<?xpacket end='r'",   true},
102         {"<?xpacket end=\"w\"", false},
103         {"<?xpacket end='w'",   false},
104     };
105 
106     // closing part of all valid XMP trailers
107     const std::string xmpTrailerEnd = "?>";
108 
109     //! Write data into temp file, taking care of errors
writeTemp(BasicIo & tempIo,const byte * data,size_t size)110     void writeTemp(BasicIo& tempIo, const byte* data, size_t size)
111     {
112         if (size == 0) return;
113         if (tempIo.write(data, static_cast<long>(size)) != static_cast<long>(size)) {
114             #ifndef SUPPRESS_WARNINGS
115             EXV_WARNING << "Failed to write to temporary file.\n";
116             #endif
117             throw Error(21);
118         }
119     }
120 
121     //! Write data into temp file, taking care of errors
writeTemp(BasicIo & tempIo,const std::string & data)122     void writeTemp(BasicIo& tempIo, const std::string &data)
123     {
124         writeTemp(tempIo, reinterpret_cast<const byte*>(data.data()), data.size());
125     }
126 
127     //! Get the current write position of temp file, taking care of errors
posTemp(BasicIo & tempIo)128     uint32_t posTemp(BasicIo& tempIo)
129     {
130         const long pos = tempIo.tell();
131         if (pos == -1) {
132             #ifndef SUPPRESS_WARNINGS
133             EXV_WARNING << "Internal error while determining current write position in temporary file.\n";
134             #endif
135             throw Error(21);
136         }
137         return static_cast<uint32_t>(pos);
138     }
139 
140     //! Check whether a string has a certain beginning
startsWith(const std::string & s,const std::string & start)141     bool startsWith(const std::string& s, const std::string& start)
142     {
143         return s.size() >= start.size() && memcmp(s.data(), start.data(), start.size()) == 0;
144     }
145 
146     //! Check whether a string contains only white space characters
onlyWhitespaces(const std::string & s)147     bool onlyWhitespaces(const std::string& s)
148     {
149         // According to the DSC 3.0 specification, 4.4 Parsing Rules,
150         // only spaces and tabs are considered to be white space characters.
151         return s.find_first_not_of(" \t") == std::string::npos;
152     }
153 
154     //! Read the next line of a buffer, allow for changing line ending style
readLine(std::string & line,const byte * data,size_t startPos,size_t size)155     size_t readLine(std::string& line, const byte* data, size_t startPos, size_t size)
156     {
157         line.clear();
158         size_t pos = startPos;
159         // step through line
160         while (pos < size && data[pos] != '\r' && data[pos] != '\n') {
161             line += data[pos];
162             pos++;
163         }
164         // skip line ending, if present
165         if (pos >= size) return pos;
166         pos++;
167         if (pos >= size) return pos;
168         if (data[pos - 1] == '\r' && data[pos] == '\n') pos++;
169         return pos;
170     }
171 
172     //! Read the previous line of a buffer, allow for changing line ending style
readPrevLine(std::string & line,const byte * data,size_t startPos,size_t size)173     size_t readPrevLine(std::string& line, const byte* data, size_t startPos, size_t size)
174     {
175         line.clear();
176         size_t pos = startPos;
177         if (pos > size) return pos;
178         // skip line ending of previous line, if present
179         if (pos <= 0) return pos;
180         if (data[pos - 1] == '\r' || data[pos - 1] == '\n') {
181             pos--;
182             if (pos <= 0) return pos;
183             if (data[pos - 1] == '\r' && data[pos] == '\n') {
184                 pos--;
185                 if (pos <= 0) return pos;
186             }
187         }
188         // step through previous line
189         while (pos >= 1 && data[pos - 1] != '\r' && data[pos - 1] != '\n') {
190             pos--;
191             line += data[pos];
192         }
193         std::reverse(line.begin(), line.end());
194         return pos;
195     }
196 
197     //! Find an XMP block
findXmp(size_t & xmpPos,size_t & xmpSize,const byte * data,size_t startPos,size_t size,bool write)198     void findXmp(size_t& xmpPos, size_t& xmpSize, const byte* data, size_t startPos, size_t size, bool write)
199     {
200         // search for valid XMP header
201         xmpSize = 0;
202         for (xmpPos = startPos; xmpPos < size; xmpPos++) {
203             if (data[xmpPos] != '\x00' && data[xmpPos] != '<') continue;
204             for (size_t i = 0; i < (sizeof xmpHeaders) / (sizeof *xmpHeaders); i++) {
205                 const std::string &header = xmpHeaders[i];
206                 if (xmpPos + header.size() > size) continue;
207                 if (memcmp(data + xmpPos, header.data(), header.size()) != 0) continue;
208                 #ifdef DEBUG
209                 EXV_DEBUG << "findXmp: Found XMP header at position: " << xmpPos << "\n";
210                 #endif
211 
212                 // search for valid XMP trailer
213                 for (size_t trailerPos = xmpPos + header.size(); trailerPos < size; trailerPos++) {
214                     if (data[xmpPos] != '\x00' && data[xmpPos] != '<') continue;
215                     for (size_t j = 0; j < (sizeof xmpTrailers) / (sizeof *xmpTrailers); j++) {
216                         const std::string &trailer = xmpTrailers[j].trailer;
217                         const bool readOnly = xmpTrailers[j].readOnly;
218 
219                         if (trailerPos + trailer.size() > size) continue;
220                         if (memcmp(data + trailerPos, trailer.data(), trailer.size()) != 0) continue;
221                         #ifdef DEBUG
222                         EXV_DEBUG << "findXmp: Found XMP trailer at position: " << trailerPos << "\n";
223                         #endif
224 
225                         if (readOnly) {
226                             #ifndef SUPPRESS_WARNINGS
227                             EXV_WARNING << "Unable to handle read-only XMP metadata yet. Please provide your "
228                                            "sample EPS file to the Exiv2 project: http://dev.exiv2.org/projects/exiv2\n";
229                             #endif
230                             throw Error(write ? 21 : 14);
231                         }
232 
233                         // search for end of XMP trailer
234                         for (size_t trailerEndPos = trailerPos + trailer.size(); trailerEndPos + xmpTrailerEnd.size() <= size; trailerEndPos++) {
235                             if (memcmp(data + trailerEndPos, xmpTrailerEnd.data(), xmpTrailerEnd.size()) == 0) {
236                                 xmpSize = (trailerEndPos + xmpTrailerEnd.size()) - xmpPos;
237                                 return;
238                             }
239                         }
240                         #ifndef SUPPRESS_WARNINGS
241                         EXV_WARNING << "Found XMP header but incomplete XMP trailer.\n";
242                         #endif
243                         throw Error(write ? 21 : 14);
244                     }
245                 }
246                 #ifndef SUPPRESS_WARNINGS
247                 EXV_WARNING << "Found XMP header but no XMP trailer.\n";
248                 #endif
249                 throw Error(write ? 21 : 14);
250             }
251         }
252     }
253 
254     //! Unified implementation of reading and writing EPS metadata
readWriteEpsMetadata(BasicIo & io,std::string & xmpPacket,NativePreviewList & nativePreviews,bool write)255     void readWriteEpsMetadata(BasicIo& io, std::string& xmpPacket, NativePreviewList& nativePreviews, bool write)
256     {
257         // open input file
258         if (io.open() != 0) {
259             throw Error(9, io.path(), strError());
260         }
261         IoCloser closer(io);
262 
263         // read from input file via memory map
264         const byte *data = io.mmap();
265 
266         // default positions and sizes
267         const size_t size = static_cast<size_t>(io.size());
268         size_t posEps = 0;
269         size_t posEndEps = size;
270         uint32_t posWmf = 0;
271         uint32_t sizeWmf = 0;
272         uint32_t posTiff = 0;
273         uint32_t sizeTiff = 0;
274 
275         // check for DOS EPS
276         const bool dosEps = (size >= dosEpsSignature.size() && memcmp(data, dosEpsSignature.data(), dosEpsSignature.size()) == 0);
277         if (dosEps) {
278             #ifdef DEBUG
279             EXV_DEBUG << "readWriteEpsMetadata: Found DOS EPS signature\n";
280             #endif
281             if (size < 30) {
282                 #ifndef SUPPRESS_WARNINGS
283                 EXV_WARNING << "Premature end of file after DOS EPS signature.\n";
284                 #endif
285                 throw Error(write ? 21 : 14);
286             }
287             posEps    = getULong(data +  4, littleEndian);
288             posEndEps = getULong(data +  8, littleEndian) + posEps;
289             posWmf    = getULong(data + 12, littleEndian);
290             sizeWmf   = getULong(data + 16, littleEndian);
291             posTiff   = getULong(data + 20, littleEndian);
292             sizeTiff  = getULong(data + 24, littleEndian);
293             const uint16_t checksum = getUShort(data + 28, littleEndian);
294             #ifdef DEBUG
295             EXV_DEBUG << "readWriteEpsMetadata: EPS section at position " << posEps << ", size " << (posEndEps - posEps) << "\n";
296             EXV_DEBUG << "readWriteEpsMetadata: WMF section at position " << posWmf << ", size " << sizeWmf << "\n";
297             EXV_DEBUG << "readWriteEpsMetadata: TIFF section at position " << posTiff << ", size " << sizeTiff << "\n";
298             #endif
299             if (checksum != 0xFFFF) {
300                 #ifdef DEBUG
301                 EXV_DEBUG << "readWriteEpsMetadata: DOS EPS checksum is not FFFF\n";
302                 #endif
303             }
304             if (!((posWmf == 0 && sizeWmf == 0) || (posTiff == 0 && sizeTiff == 0))) {
305                 #ifndef SUPPRESS_WARNINGS
306                 EXV_WARNING << "DOS EPS file has both WMF and TIFF section. Only one of those is allowed.\n";
307                 #endif
308                 if (write) throw Error(21);
309             }
310             if (sizeWmf == 0 && sizeTiff == 0) {
311                 #ifndef SUPPRESS_WARNINGS
312                 EXV_WARNING << "DOS EPS file has neither WMF nor TIFF section. Exactly one of those is required.\n";
313                 #endif
314                 if (write) throw Error(21);
315             }
316             if (posEps < 30 || posEndEps > size) {
317                 #ifndef SUPPRESS_WARNINGS
318                 EXV_WARNING << "DOS EPS file has invalid position (" << posEps << ") or size (" << (posEndEps - posEps) << ") for EPS section.\n";
319                 #endif
320                 throw Error(write ? 21 : 14);
321             }
322             if (sizeWmf != 0 && (posWmf < 30 || posWmf + sizeWmf > size)) {
323                 #ifndef SUPPRESS_WARNINGS
324                 EXV_WARNING << "DOS EPS file has invalid position (" << posWmf << ") or size (" << sizeWmf << ") for WMF section.\n";
325                 #endif
326                 if (write) throw Error(21);
327             }
328             if (sizeTiff != 0 && (posTiff < 30 || posTiff + sizeTiff > size)) {
329                 #ifndef SUPPRESS_WARNINGS
330                 EXV_WARNING << "DOS EPS file has invalid position (" << posTiff << ") or size (" << sizeTiff << ") for TIFF section.\n";
331                 #endif
332                 if (write) throw Error(21);
333             }
334         }
335 
336         // check first line
337         std::string firstLine;
338         const size_t posSecondLine = readLine(firstLine, data, posEps, posEndEps);
339         #ifdef DEBUG
340         EXV_DEBUG << "readWriteEpsMetadata: First line: " << firstLine << "\n";
341         #endif
342         bool matched = false;
343         for (size_t i = 0; !matched && i < (sizeof epsFirstLine) / (sizeof *epsFirstLine); i++) {
344             matched = (firstLine == epsFirstLine[i]);
345         }
346         if (!matched) {
347             throw Error(3, "EPS");
348         }
349 
350         // determine line ending style of the first line
351         if (posSecondLine >= posEndEps) {
352             #ifndef SUPPRESS_WARNINGS
353             EXV_WARNING << "Premature end of file after first line.\n";
354             #endif
355             throw Error(write ? 21 : 14);
356         }
357         const std::string lineEnding(reinterpret_cast<const char*>(data + posEps + firstLine.size()), posSecondLine - (posEps + firstLine.size()));
358         #ifdef DEBUG
359         if (lineEnding == "\n") {
360             EXV_DEBUG << "readWriteEpsMetadata: Line ending style: Unix (LF)\n";
361         } else if (lineEnding == "\r") {
362             EXV_DEBUG << "readWriteEpsMetadata: Line ending style: Mac (CR)\n";
363         } else if (lineEnding == "\r\n") {
364             EXV_DEBUG << "readWriteEpsMetadata: Line ending style: DOS (CR LF)\n";
365         } else {
366             EXV_DEBUG << "readWriteEpsMetadata: Line ending style: (unknown)\n";
367         }
368         #endif
369 
370         // scan comments
371         size_t posLanguageLevel = posEndEps;
372         size_t posContainsXmp = posEndEps;
373         size_t posPages = posEndEps;
374         size_t posExiv2Version = posEndEps;
375         size_t posExiv2Website = posEndEps;
376         size_t posEndComments = posEndEps;
377         size_t posAi7Thumbnail = posEndEps;
378         size_t posAi7ThumbnailEndData = posEndEps;
379         size_t posBeginPhotoshop = posEndEps;
380         size_t posEndPhotoshop = posEndEps;
381         size_t posPage = posEndEps;
382         size_t posBeginPageSetup = posEndEps;
383         size_t posEndPageSetup = posEndEps;
384         size_t posPageTrailer = posEndEps;
385         size_t posEof = posEndEps;
386         std::vector<std::pair<size_t, size_t> > removableEmbeddings;
387         unsigned int depth = 0;
388         const unsigned int maxDepth = UINT_MAX;
389         bool illustrator8 = false;
390         bool corelDraw = false;
391         bool implicitPage = false;
392         bool implicitPageSetup = false;
393         bool implicitPageTrailer = false;
394         bool inDefaultsPreviewPrologSetup = false;
395         bool inRemovableEmbedding = false;
396         std::string removableEmbeddingEndLine;
397         unsigned int removableEmbeddingsWithUnmarkedTrailer = 0;
398         for (size_t pos = posEps; pos < posEof;) {
399             const size_t startPos = pos;
400             std::string line;
401             pos = readLine(line, data, startPos, posEndEps);
402             #ifdef DEBUG
403             bool significantLine = true;
404             #endif
405             // nested documents
406             if (posPage == posEndEps && (startsWith(line, "%%IncludeDocument:") || startsWith(line, "%%BeginDocument:"))) {
407                 #ifndef SUPPRESS_WARNINGS
408                 EXV_WARNING << "Nested document at invalid position: " << startPos << "\n";
409                 #endif
410                 throw Error(write ? 21 : 14);
411             } else if (startsWith(line, "%%BeginDocument:")) {
412                 if (depth == maxDepth) {
413                     #ifndef SUPPRESS_WARNINGS
414                     EXV_WARNING << "Document too deeply nested at position: " << startPos << "\n";
415                     #endif
416                     throw Error(write ? 21 : 14);
417                 }
418                 depth++;
419             } else if (startsWith(line, "%%EndDocument")) {
420                 if (depth == 0) {
421                     #ifndef SUPPRESS_WARNINGS
422                     EXV_WARNING << "Unmatched EndDocument at position: " << startPos << "\n";
423                     #endif
424                     throw Error(write ? 21 : 14);
425                 }
426                 depth--;
427             } else {
428                 #ifdef DEBUG
429                 significantLine = false;
430                 #endif
431             }
432             #ifdef DEBUG
433             if (significantLine) {
434                 EXV_DEBUG << "readWriteEpsMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n";
435             }
436             significantLine = true;
437             #endif
438             if (depth != 0) continue;
439             // explicit "Begin" comments
440             if (startsWith(line, "%%BeginPreview:")) {
441                 inDefaultsPreviewPrologSetup = true;
442             } else if (line == "%%BeginDefaults") {
443                 inDefaultsPreviewPrologSetup = true;
444             } else if (line == "%%BeginProlog") {
445                 inDefaultsPreviewPrologSetup = true;
446             } else if (line == "%%BeginSetup") {
447                 inDefaultsPreviewPrologSetup = true;
448             } else if (posPage == posEndEps && startsWith(line, "%%Page:")) {
449                 posPage = startPos;
450             } else if (posPage != posEndEps && startsWith(line, "%%Page:")) {
451                 if (implicitPage) {
452                     #ifndef SUPPRESS_WARNINGS
453                     EXV_WARNING << "Page at position " << startPos << " conflicts with implicit page at position: " << posPage << "\n";
454                     #endif
455                     throw Error(write ? 21 : 14);
456                 }
457                 #ifndef SUPPRESS_WARNINGS
458                 EXV_WARNING << "Unable to handle multiple PostScript pages. Found second page at position: " << startPos << "\n";
459                 #endif
460                 throw Error(write ? 21 : 14);
461             } else if (line == "%%BeginPageSetup") {
462                 posBeginPageSetup = startPos;
463             } else if (!inRemovableEmbedding && line == "%Exiv2BeginXMP: Before %%EndPageSetup") {
464                 inRemovableEmbedding = true;
465                 removableEmbeddings.push_back(std::make_pair(startPos, startPos));
466                 removableEmbeddingEndLine = "%Exiv2EndXMP";
467             } else if (!inRemovableEmbedding && line == "%Exiv2BeginXMP: After %%PageTrailer") {
468                 inRemovableEmbedding = true;
469                 removableEmbeddings.push_back(std::make_pair(startPos, startPos));
470                 removableEmbeddingEndLine = "%Exiv2EndXMP";
471             } else if (!inRemovableEmbedding && line == "%ADOBeginClientInjection: PageSetup End \"AI11EPS\"") {
472                 inRemovableEmbedding = true;
473                 removableEmbeddings.push_back(std::make_pair(startPos, startPos));
474                 removableEmbeddingEndLine = "%ADOEndClientInjection: PageSetup End \"AI11EPS\"";
475             } else if (!inRemovableEmbedding && line == "%ADOBeginClientInjection: PageTrailer Start \"AI11EPS\"") {
476                 inRemovableEmbedding = true;
477                 removableEmbeddings.push_back(std::make_pair(startPos, startPos));
478                 removableEmbeddingEndLine = "%ADOEndClientInjection: PageTrailer Start \"AI11EPS\"";
479             } else if (!inRemovableEmbedding && line == "%begin_xml_code") {
480                 inRemovableEmbedding = true;
481                 removableEmbeddings.push_back(std::make_pair(startPos, startPos));
482                 removableEmbeddingEndLine = "%end_xml_code";
483                 removableEmbeddingsWithUnmarkedTrailer++;
484             } else {
485                 #ifdef DEBUG
486                 significantLine = false;
487                 #endif
488             }
489             #ifdef DEBUG
490             if (significantLine) {
491                 EXV_DEBUG << "readWriteEpsMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n";
492             }
493             significantLine = true;
494             #endif
495             // implicit comments
496             if (line == "%%EOF" || line == "%begin_xml_code" || !(line.size() >= 2 && line[0] == '%' && '\x21' <= line[1] && line[1] <= '\x7e')) {
497                 if (posEndComments == posEndEps) {
498                     posEndComments = startPos;
499                     #ifdef DEBUG
500                     EXV_DEBUG << "readWriteEpsMetadata: Found implicit EndComments at position: " << startPos << "\n";
501                     #endif
502                 }
503             }
504             if (posPage == posEndEps && posEndComments != posEndEps && !inDefaultsPreviewPrologSetup && !inRemovableEmbedding && !onlyWhitespaces(line)) {
505                 posPage = startPos;
506                 implicitPage = true;
507                 #ifdef DEBUG
508                 EXV_DEBUG << "readWriteEpsMetadata: Found implicit Page at position: " << startPos << "\n";
509                 #endif
510             }
511             if (posBeginPageSetup == posEndEps && (implicitPage || (posPage != posEndEps && !inRemovableEmbedding && line.size() >= 1 && line[0] != '%'))) {
512                 posBeginPageSetup = startPos;
513                 implicitPageSetup = true;
514                 #ifdef DEBUG
515                 EXV_DEBUG << "readWriteEpsMetadata: Found implicit BeginPageSetup at position: " << startPos << "\n";
516                 #endif
517             }
518             if (posEndPageSetup == posEndEps && implicitPageSetup && !inRemovableEmbedding && line.size() >= 1 && line[0] != '%') {
519                 posEndPageSetup = startPos;
520                 #ifdef DEBUG
521                 EXV_DEBUG << "readWriteEpsMetadata: Found implicit EndPageSetup at position: " << startPos << "\n";
522                 #endif
523             }
524             if (line.size() >= 1 && line[0] != '%') continue; // performance optimization
525             if (line == "%%EOF" || line == "%%Trailer" || line == "%%PageTrailer") {
526                 if (posBeginPageSetup == posEndEps) {
527                     posBeginPageSetup = startPos;
528                     implicitPageSetup = true;
529                     #ifdef DEBUG
530                     EXV_DEBUG << "readWriteEpsMetadata: Found implicit BeginPageSetup at position: " << startPos << "\n";
531                     #endif
532                 }
533                 if (posEndPageSetup == posEndEps) {
534                     posEndPageSetup = startPos;
535                     implicitPageSetup = true;
536                     #ifdef DEBUG
537                     EXV_DEBUG << "readWriteEpsMetadata: Found implicit EndPageSetup at position: " << startPos << "\n";
538                     #endif
539                 }
540             }
541             if (line == "%%EOF" || line == "%%Trailer") {
542                 if (posPageTrailer == posEndEps) {
543                     posPageTrailer = startPos;
544                     implicitPageTrailer = true;
545                     #ifdef DEBUG
546                     EXV_DEBUG << "readWriteEpsMetadata: Found implicit PageTrailer at position: " << startPos << "\n";
547                     #endif
548                 }
549             }
550             // remaining explicit comments
551             if (posEndComments == posEndEps && posLanguageLevel == posEndEps && startsWith(line, "%%LanguageLevel:")) {
552                 posLanguageLevel = startPos;
553             } else if (posEndComments == posEndEps && posContainsXmp == posEndEps && startsWith(line, "%ADO_ContainsXMP:")) {
554                 posContainsXmp = startPos;
555             } else if (posEndComments == posEndEps && posPages == posEndEps && startsWith(line, "%%Pages:")) {
556                 posPages = startPos;
557             } else if (posEndComments == posEndEps && posExiv2Version == posEndEps && startsWith(line, "%Exiv2Version:")) {
558                 posExiv2Version = startPos;
559             } else if (posEndComments == posEndEps && posExiv2Website == posEndEps && startsWith(line, "%Exiv2Website:")) {
560                 posExiv2Website = startPos;
561             } else if (posEndComments == posEndEps && startsWith(line, "%%Creator: Adobe Illustrator") && firstLine == "%!PS-Adobe-3.0 EPSF-3.0") {
562                 illustrator8 = true;
563             } else if (posEndComments == posEndEps && startsWith(line, "%AI7_Thumbnail:")) {
564                 posAi7Thumbnail = startPos;
565             } else if (posEndComments == posEndEps && posAi7Thumbnail != posEndEps && posAi7ThumbnailEndData == posEndEps && line == "%%EndData") {
566                 posAi7ThumbnailEndData = startPos;
567             } else if (posEndComments == posEndEps && line == "%%EndComments") {
568                 posEndComments = startPos;
569             } else if (inDefaultsPreviewPrologSetup && startsWith(line, "%%BeginResource: procset wCorel")) {
570                 corelDraw = true;
571             } else if (line == "%%EndPreview") {
572                 inDefaultsPreviewPrologSetup = false;
573             } else if (line == "%%EndDefaults") {
574                 inDefaultsPreviewPrologSetup = false;
575             } else if (line == "%%EndProlog") {
576                 inDefaultsPreviewPrologSetup = false;
577             } else if (line == "%%EndSetup") {
578                 inDefaultsPreviewPrologSetup = false;
579             } else if (posEndPageSetup == posEndEps && line == "%%EndPageSetup") {
580                 posEndPageSetup = startPos;
581             } else if (posPageTrailer == posEndEps && line == "%%PageTrailer") {
582                 posPageTrailer = startPos;
583             } else if (posBeginPhotoshop == posEndEps && startsWith(line, "%BeginPhotoshop:")) {
584                 posBeginPhotoshop = pos;
585             } else if (posBeginPhotoshop != posEndEps && posEndPhotoshop == posEndEps && line == "%EndPhotoshop") {
586                 posEndPhotoshop = startPos;
587             } else if (inRemovableEmbedding && line == removableEmbeddingEndLine) {
588                 inRemovableEmbedding = false;
589                 removableEmbeddings.back().second = pos;
590             } else if (line == "%%EOF") {
591                 posEof = startPos;
592             } else {
593                 #ifdef DEBUG
594                 significantLine = false;
595                 #endif
596             }
597             #ifdef DEBUG
598             if (significantLine) {
599                 EXV_DEBUG << "readWriteEpsMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n";
600             }
601             #endif
602         }
603 
604         // check for unfinished nested documents
605         if (depth != 0) {
606             #ifndef SUPPRESS_WARNINGS
607             EXV_WARNING << "Unmatched BeginDocument (" << depth << "x)\n";
608             #endif
609             throw Error(write ? 21 : 14);
610         }
611 
612         // look for the unmarked trailers of some removable XMP embeddings
613         size_t posXmpTrailerEnd = posEof;
614         for (size_t i = 0; i < removableEmbeddingsWithUnmarkedTrailer; i++) {
615             std::string line1;
616             const size_t posLine1 = readPrevLine(line1, data, posXmpTrailerEnd, posEndEps);
617             std::string line2;
618             const size_t posLine2 = readPrevLine(line2, data, posLine1, posEndEps);
619             size_t posXmpTrailer;
620             if (line1 == "[/EMC pdfmark") { // Exiftool style
621                 posXmpTrailer = posLine1;
622             } else if (line1 == "[/NamespacePop pdfmark" &&
623                        line2 == "[{nextImage} 1 dict begin /Metadata {photoshop_metadata_stream} def currentdict end /PUT pdfmark") { // Photoshop style
624                 posXmpTrailer = posLine2;
625             } else {
626                 #ifndef SUPPRESS_WARNINGS
627                 EXV_WARNING << "Unable to find XMP embedding trailer ending at position: " << posXmpTrailerEnd << "\n";
628                 #endif
629                 if (write) throw Error(21);
630                 break;
631             }
632             removableEmbeddings.push_back(std::make_pair(posXmpTrailer, posXmpTrailerEnd));
633             #ifdef DEBUG
634             EXV_DEBUG << "readWriteEpsMetadata: Recognized unmarked trailer of removable XMP embedding at "
635                          "[" << removableEmbeddings.back().first << "," << removableEmbeddings.back().second << ")"
636                          "\n";
637             #endif
638             posXmpTrailerEnd = posXmpTrailer;
639         }
640 
641         // interpret comment "%ADO_ContainsXMP:"
642         std::string line;
643         readLine(line, data, posContainsXmp, posEndEps);
644         bool containsXmp;
645         if (line == "%ADO_ContainsXMP: MainFirst" || line == "%ADO_ContainsXMP:MainFirst") {
646             containsXmp = true;
647         } else if (line == "" || line == "%ADO_ContainsXMP: NoMain" || line == "%ADO_ContainsXMP:NoMain") {
648             containsXmp = false;
649         } else {
650             #ifndef SUPPRESS_WARNINGS
651             EXV_WARNING << "Invalid line \"" << line << "\" at position: " << posContainsXmp << "\n";
652             #endif
653             throw Error(write ? 21 : 14);
654         }
655 
656         const bool deleteXmp = (write && xmpPacket.size() == 0);
657         bool fixBeginXmlPacket = false;
658         bool useFlexibleEmbedding = false;
659         size_t xmpPos = posEndEps;
660         size_t xmpSize = 0;
661         if (containsXmp) {
662             // search for XMP metadata
663             findXmp(xmpPos, xmpSize, data, posEps, posEndEps, write);
664             if (xmpPos == posEndEps) {
665                 #ifndef SUPPRESS_WARNINGS
666                 EXV_WARNING << "Unable to find XMP metadata as announced at position: " << posContainsXmp << "\n";
667                 #endif
668             }
669             // check embedding of XMP metadata
670             const size_t posLineAfterXmp = readLine(line, data, xmpPos + xmpSize, posEndEps);
671             if (line != "") {
672                 #ifndef SUPPRESS_WARNINGS
673                 EXV_WARNING << "Unexpected " << line.size() << " bytes of data after XMP at position: " << (xmpPos + xmpSize) << "\n";
674                 #endif
675             } else if (!deleteXmp) {
676                 readLine(line, data, posLineAfterXmp, posEndEps);
677                 if (line == "% &&end XMP packet marker&&" || line == "%  &&end XMP packet marker&&") {
678                     useFlexibleEmbedding = true;
679                 }
680             }
681         }
682         if (useFlexibleEmbedding) {
683             #ifdef DEBUG
684             EXV_DEBUG << "readWriteEpsMetadata: Using flexible XMP embedding\n";
685             #endif
686             const size_t posBeginXmlPacket = readPrevLine(line, data, xmpPos, posEndEps);
687             if (startsWith(line, "%begin_xml_packet:")) {
688                 #ifdef DEBUG
689                 EXV_DEBUG << "readWriteEpsMetadata: XMP embedding contains %begin_xml_packet\n";
690                 #endif
691                 if (write) {
692                     fixBeginXmlPacket = true;
693                     xmpSize += (xmpPos - posBeginXmlPacket);
694                     xmpPos = posBeginXmlPacket;
695                 }
696             } else if (posBeginPhotoshop != posEndEps) {
697                 #ifndef SUPPRESS_WARNINGS
698                 EXV_WARNING << "Missing %begin_xml_packet in Photoshop EPS at position: " << xmpPos << "\n";
699                 #endif
700                 if (write) throw Error(21);
701             }
702         }
703         if (!useFlexibleEmbedding) {
704             // check if there are irremovable XMP metadata blocks before EndPageSetup
705             size_t posOtherXmp = containsXmp ? xmpPos : posEps;
706             size_t sizeOtherXmp = 0;
707             for (;;) {
708                 findXmp(posOtherXmp, sizeOtherXmp, data, posOtherXmp + sizeOtherXmp, posEndPageSetup, write);
709                 if (posOtherXmp >= posEndPageSetup) break;
710                 bool isRemovableEmbedding = false;
711                 for (std::vector<std::pair<size_t, size_t> >::const_iterator e = removableEmbeddings.begin(); e != removableEmbeddings.end(); e++) {
712                     if (e->first <= posOtherXmp && posOtherXmp < e->second) {
713                         isRemovableEmbedding = true;
714                         break;
715                     }
716                 }
717                 if (!isRemovableEmbedding) {
718                     #ifndef SUPPRESS_WARNINGS
719                     EXV_WARNING << "XMP metadata block is not removable at position: " << posOtherXmp << "\n";
720                     #endif
721                     if (write) throw Error(21);
722                     break;
723                 }
724             }
725         }
726 
727         if (!write) {
728             // copy XMP metadata
729             xmpPacket.assign(reinterpret_cast<const char*>(data + xmpPos), xmpSize);
730 
731             // native previews
732             nativePreviews.clear();
733             if (posAi7ThumbnailEndData != posEndEps) {
734                 NativePreview nativePreview;
735                 std::string dummy;
736                 std::string lineAi7Thumbnail;
737                 const size_t posBeginData = readLine(lineAi7Thumbnail, data, posAi7Thumbnail, posEndEps);
738                 std::istringstream lineStreamAi7Thumbnail(lineAi7Thumbnail);
739                 lineStreamAi7Thumbnail >> dummy;
740                 lineStreamAi7Thumbnail >> nativePreview.width_;
741                 lineStreamAi7Thumbnail >> nativePreview.height_;
742                 std::string depth;
743                 lineStreamAi7Thumbnail >> depth;
744                 std::string lineBeginData;
745                 const size_t posAfterBeginData = readLine(lineBeginData, data, posBeginData, posEndEps);
746                 std::istringstream lineStreamBeginData(lineBeginData);
747                 std::string beginData;
748                 lineStreamBeginData >> beginData;
749                 lineStreamBeginData >> dummy;
750                 std::string type;
751                 lineStreamBeginData >> type;
752                 nativePreview.position_ = static_cast<long>(posAfterBeginData);
753                 nativePreview.size_ = static_cast<uint32_t>(posAi7ThumbnailEndData - posAfterBeginData);
754                 nativePreview.filter_ = "hex-ai7thumbnail-pnm";
755                 nativePreview.mimeType_ = "image/x-portable-anymap";
756                 if (depth != "8") {
757                     #ifndef SUPPRESS_WARNINGS
758                     EXV_WARNING << "Unable to handle Illustrator thumbnail depth: " << depth << "\n";
759                     #endif
760                 } else if (beginData != "%%BeginData:") {
761                     #ifndef SUPPRESS_WARNINGS
762                     EXV_WARNING << "Unable to handle Illustrator thumbnail data section: " << lineBeginData << "\n";
763                     #endif
764                 } else if (type != "Hex") {
765                     #ifndef SUPPRESS_WARNINGS
766                     EXV_WARNING << "Unable to handle Illustrator thumbnail data type: " << type << "\n";
767                     #endif
768                 } else {
769                     nativePreviews.push_back(nativePreview);
770                 }
771             }
772             if (posEndPhotoshop != posEndEps) {
773                 NativePreview nativePreview;
774                 nativePreview.position_ = static_cast<long>(posBeginPhotoshop);
775                 nativePreview.size_ = static_cast<uint32_t>(posEndPhotoshop - posBeginPhotoshop);
776                 nativePreview.width_ = 0;
777                 nativePreview.height_ = 0;
778                 nativePreview.filter_ = "hex-irb";
779                 nativePreview.mimeType_ = "image/jpeg";
780                 nativePreviews.push_back(nativePreview);
781             }
782             if (sizeWmf != 0) {
783                 NativePreview nativePreview;
784                 nativePreview.position_ = static_cast<long>(posWmf);
785                 nativePreview.size_ = sizeWmf;
786                 nativePreview.width_ = 0;
787                 nativePreview.height_ = 0;
788                 nativePreview.filter_ = "";
789                 nativePreview.mimeType_ = "image/x-wmf";
790                 nativePreviews.push_back(nativePreview);
791             }
792             if (sizeTiff != 0) {
793                 NativePreview nativePreview;
794                 nativePreview.position_ = static_cast<long>(posTiff);
795                 nativePreview.size_ = sizeTiff;
796                 nativePreview.width_ = 0;
797                 nativePreview.height_ = 0;
798                 nativePreview.filter_ = "";
799                 nativePreview.mimeType_ = "image/tiff";
800                 nativePreviews.push_back(nativePreview);
801             }
802         } else {
803             // check for Adobe Illustrator 8.0 or older
804             if (illustrator8) {
805                 #ifndef SUPPRESS_WARNINGS
806                 EXV_WARNING << "Unable to write to EPS files created by Adobe Illustrator 8.0 or older.\n";
807                 #endif
808                 throw Error(21);
809             }
810 
811             // create temporary output file
812             BasicIo::AutoPtr tempIo(new MemIo);
813             assert (tempIo.get() != 0);
814             if (!tempIo->isopen()) {
815                 #ifndef SUPPRESS_WARNINGS
816                 EXV_WARNING << "Unable to create temporary file for writing.\n";
817                 #endif
818                 throw Error(21);
819             }
820             #ifdef DEBUG
821             EXV_DEBUG << "readWriteEpsMetadata: Created temporary file " << tempIo->path() << "\n";
822             #endif
823 
824             // sort all positions
825             std::vector<size_t> positions;
826             positions.push_back(posLanguageLevel);
827             positions.push_back(posContainsXmp);
828             positions.push_back(posPages);
829             positions.push_back(posExiv2Version);
830             positions.push_back(posExiv2Website);
831             positions.push_back(posEndComments);
832             positions.push_back(posPage);
833             positions.push_back(posBeginPageSetup);
834             positions.push_back(posEndPageSetup);
835             positions.push_back(posPageTrailer);
836             positions.push_back(posEof);
837             positions.push_back(posEndEps);
838             if (useFlexibleEmbedding) {
839                 positions.push_back(xmpPos);
840             }
841             for (std::vector<std::pair<size_t, size_t> >::const_iterator e = removableEmbeddings.begin(); e != removableEmbeddings.end(); e++) {
842                 positions.push_back(e->first);
843             }
844             std::sort(positions.begin(), positions.end());
845 
846             // assemble result EPS document
847             if (dosEps) {
848                 // DOS EPS header will be written afterwards
849                 writeTemp(*tempIo, std::string(30, '\x00'));
850             }
851             const std::string containsXmpLine = deleteXmp ? "%ADO_ContainsXMP: NoMain" : "%ADO_ContainsXMP: MainFirst";
852             const uint32_t posEpsNew = posTemp(*tempIo);
853             size_t prevPos = posEps;
854             size_t prevSkipPos = prevPos;
855             for (std::vector<size_t>::const_iterator i = positions.begin(); i != positions.end(); i++) {
856                 const size_t pos = *i;
857                 if (pos == prevPos) continue;
858                 #ifdef DEBUG
859                 EXV_DEBUG << "readWriteEpsMetadata: Writing at " << pos << "\n";
860                 #endif
861                 if (pos < prevSkipPos) {
862                     #ifndef SUPPRESS_WARNINGS
863                     EXV_WARNING << "Internal error while assembling the result EPS document: "
864                                    "Unable to continue at position " << pos << " after skipping to position " << prevSkipPos << "\n";
865                     #endif
866                     throw Error(21);
867                 }
868                 writeTemp(*tempIo, data + prevSkipPos, pos - prevSkipPos);
869                 const size_t posLineEnd = readLine(line, data, pos, posEndEps);
870                 size_t skipPos = pos;
871                 // add last line ending if necessary
872                 if (pos == posEndEps && pos >= 1 && data[pos - 1] != '\r' && data[pos - 1] != '\n') {
873                     writeTemp(*tempIo, lineEnding);
874                     #ifdef DEBUG
875                     EXV_DEBUG << "readWriteEpsMetadata: Added missing line ending of last line\n";
876                     #endif
877                 }
878                 // update and complement DSC comments
879                 if (pos == posLanguageLevel && posLanguageLevel != posEndEps && !deleteXmp && !useFlexibleEmbedding) {
880                     if (line == "%%LanguageLevel:1" || line == "%%LanguageLevel: 1") {
881                         writeTemp(*tempIo, "%%LanguageLevel: 2" + lineEnding);
882                         skipPos = posLineEnd;
883                         #ifdef DEBUG
884                         EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n";
885                         #endif
886                     }
887                 }
888                 if (pos == posContainsXmp && posContainsXmp != posEndEps) {
889                     if (line != containsXmpLine) {
890                         writeTemp(*tempIo, containsXmpLine + lineEnding);
891                         skipPos = posLineEnd;
892                         #ifdef DEBUG
893                         EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n";
894                         #endif
895                     }
896                 }
897                 if (pos == posExiv2Version && posExiv2Version != posEndEps) {
898                     writeTemp(*tempIo, "%Exiv2Version: " + versionNumberHexString() + lineEnding);
899                     skipPos = posLineEnd;
900                     #ifdef DEBUG
901                     EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n";
902                     #endif
903                 }
904                 if (pos == posExiv2Website && posExiv2Website != posEndEps) {
905                     writeTemp(*tempIo, "%Exiv2Website: http://www.exiv2.org/" + lineEnding);
906                     skipPos = posLineEnd;
907                     #ifdef DEBUG
908                     EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n";
909                     #endif
910                 }
911                 if (pos == posEndComments) {
912                     if (posLanguageLevel == posEndEps && !deleteXmp && !useFlexibleEmbedding) {
913                         writeTemp(*tempIo, "%%LanguageLevel: 2" + lineEnding);
914                     }
915                     if (posContainsXmp == posEndEps) {
916                         writeTemp(*tempIo, containsXmpLine + lineEnding);
917                     }
918                     if (posPages == posEndEps) {
919                         writeTemp(*tempIo, "%%Pages: 1" + lineEnding);
920                     }
921                     if (posExiv2Version == posEndEps) {
922                         writeTemp(*tempIo, "%Exiv2Version: " + versionNumberHexString() + lineEnding);
923                     }
924                     if (posExiv2Website == posEndEps) {
925                         writeTemp(*tempIo, "%Exiv2Website: http://www.exiv2.org/" + lineEnding);
926                     }
927                     readLine(line, data, posEndComments, posEndEps);
928                     if (line != "%%EndComments") {
929                         writeTemp(*tempIo, "%%EndComments" + lineEnding);
930                     }
931                 }
932                 if (pos == posPage) {
933                     if (!startsWith(line, "%%Page:")) {
934                         writeTemp(*tempIo, "%%Page: 1 1" + lineEnding);
935                         writeTemp(*tempIo, "%%EndPageComments" + lineEnding);
936                     }
937                 }
938                 if (pos == posBeginPageSetup) {
939                     if (line != "%%BeginPageSetup") {
940                         writeTemp(*tempIo, "%%BeginPageSetup" + lineEnding);
941                     }
942                 }
943                 if (useFlexibleEmbedding) {
944                     // insert XMP metadata into existing flexible embedding
945                     if (pos == xmpPos) {
946                         if (fixBeginXmlPacket) {
947                             writeTemp(*tempIo, "%begin_xml_packet: " + toString(xmpPacket.size()) + lineEnding);
948                         }
949                         writeTemp(*tempIo, xmpPacket);
950                         skipPos += xmpSize;
951                         #ifdef DEBUG
952                         EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n";
953                         #endif
954                     }
955                 }
956                 if (!useFlexibleEmbedding) {
957                     // remove preceding embedding(s)
958                     for (std::vector<std::pair<size_t, size_t> >::const_iterator e = removableEmbeddings.begin(); e != removableEmbeddings.end(); e++) {
959                         if (pos == e->first) {
960                             skipPos = e->second;
961                             #ifdef DEBUG
962                             EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n";
963                             #endif
964                             break;
965                         }
966                     }
967                     // insert XMP metadata with new flexible embedding, if necessary
968                     if (pos == posEndPageSetup && !deleteXmp) {
969                         writeTemp(*tempIo, "%Exiv2BeginXMP: Before %%EndPageSetup" + lineEnding);
970                         if (corelDraw) {
971                             writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by CorelDRAW." + lineEnding);
972                             writeTemp(*tempIo, "@rs" + lineEnding);
973                         }
974                         if (posBeginPhotoshop != posEndEps) {
975                             writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by Photoshop." + lineEnding);
976                             writeTemp(*tempIo, "%begin_xml_code" + lineEnding);
977                         }
978                         writeTemp(*tempIo, "/currentdistillerparams where" + lineEnding);
979                         writeTemp(*tempIo, "{pop currentdistillerparams /CoreDistVersion get 5000 lt} {true} ifelse" + lineEnding);
980                         writeTemp(*tempIo, "{userdict /Exiv2_pdfmark /cleartomark load put" + lineEnding);
981                         writeTemp(*tempIo, "    userdict /Exiv2_metafile_pdfmark {flushfile cleartomark} bind put}" + lineEnding);
982                         writeTemp(*tempIo, "{userdict /Exiv2_pdfmark /pdfmark load put" + lineEnding);
983                         writeTemp(*tempIo, "    userdict /Exiv2_metafile_pdfmark {/PUT pdfmark} bind put} ifelse" + lineEnding);
984                         writeTemp(*tempIo, "[/NamespacePush Exiv2_pdfmark" + lineEnding);
985                         writeTemp(*tempIo, "[/_objdef {Exiv2_metadata_stream} /type /stream /OBJ Exiv2_pdfmark" + lineEnding);
986                         writeTemp(*tempIo, "[{Exiv2_metadata_stream} 2 dict begin" + lineEnding);
987                         writeTemp(*tempIo, "    /Type /Metadata def /Subtype /XML def currentdict end /PUT Exiv2_pdfmark" + lineEnding);
988                         writeTemp(*tempIo, "[{Exiv2_metadata_stream}" + lineEnding);
989                         writeTemp(*tempIo, "    currentfile 0 (% &&end XMP packet marker&&)" + lineEnding);
990                         writeTemp(*tempIo, "    /SubFileDecode filter Exiv2_metafile_pdfmark" + lineEnding);
991                         if (posBeginPhotoshop != posEndEps) {
992                             writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by Photoshop. "
993                                                "Parameter must be exact size of XMP metadata." + lineEnding);
994                             writeTemp(*tempIo, "%begin_xml_packet: " + toString(xmpPacket.size()) + lineEnding);
995                         }
996                         writeTemp(*tempIo, xmpPacket);
997                         writeTemp(*tempIo, lineEnding);
998                         writeTemp(*tempIo, "% &&end XMP packet marker&&" + lineEnding);
999                         writeTemp(*tempIo, "[/Document 1 dict begin" + lineEnding);
1000                         writeTemp(*tempIo, "    /Metadata {Exiv2_metadata_stream} def currentdict end /BDC Exiv2_pdfmark" + lineEnding);
1001                         if (posBeginPhotoshop != posEndEps) {
1002                             writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by Photoshop." + lineEnding);
1003                             writeTemp(*tempIo, "%end_xml_code" + lineEnding);
1004                         }
1005                         if (corelDraw) {
1006                             writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by CorelDRAW." + lineEnding);
1007                             writeTemp(*tempIo, "@sv" + lineEnding);
1008                         }
1009                         writeTemp(*tempIo, "%Exiv2EndXMP" + lineEnding);
1010                     }
1011                 }
1012                 if (pos == posEndPageSetup) {
1013                     if (line != "%%EndPageSetup") {
1014                         writeTemp(*tempIo, "%%EndPageSetup" + lineEnding);
1015                     }
1016                 }
1017                 if (!useFlexibleEmbedding) {
1018                     if (pos == posPageTrailer && !deleteXmp) {
1019                         if (!implicitPageTrailer) {
1020                             skipPos = posLineEnd;
1021                             #ifdef DEBUG
1022                             EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n";
1023                             #endif
1024                         }
1025                         writeTemp(*tempIo, "%%PageTrailer" + lineEnding);
1026                         writeTemp(*tempIo, "%Exiv2BeginXMP: After %%PageTrailer" + lineEnding);
1027                         writeTemp(*tempIo, "[/EMC Exiv2_pdfmark" + lineEnding);
1028                         writeTemp(*tempIo, "[/NamespacePop Exiv2_pdfmark" + lineEnding);
1029                         writeTemp(*tempIo, "%Exiv2EndXMP" + lineEnding);
1030                     }
1031                 }
1032                 // add EOF comment if necessary
1033                 if (pos == posEndEps && posEof == posEndEps) {
1034                     writeTemp(*tempIo, "%%EOF" + lineEnding);
1035                 }
1036                 prevPos = pos;
1037                 prevSkipPos = skipPos;
1038             }
1039             const uint32_t posEndEpsNew = posTemp(*tempIo);
1040             #ifdef DEBUG
1041             EXV_DEBUG << "readWriteEpsMetadata: New EPS size: " << (posEndEpsNew - posEpsNew) << "\n";
1042             #endif
1043             if (dosEps) {
1044                 // write WMF and/or TIFF section if present
1045                 writeTemp(*tempIo, data + posWmf, sizeWmf);
1046                 writeTemp(*tempIo, data + posTiff, sizeTiff);
1047                 #ifdef DEBUG
1048                 EXV_DEBUG << "readWriteEpsMetadata: New DOS EPS total size: " << posTemp(*tempIo) << "\n";
1049                 #endif
1050                 // write DOS EPS header
1051                 if (tempIo->seek(0, BasicIo::beg) != 0) {
1052                     #ifndef SUPPRESS_WARNINGS
1053                     EXV_WARNING << "Internal error while seeking in temporary file.\n";
1054                     #endif
1055                     throw Error(21);
1056                 }
1057                 byte dosEpsHeader[30];
1058                 dosEpsSignature.copy(reinterpret_cast<char*>(dosEpsHeader), dosEpsSignature.size());
1059                 ul2Data(dosEpsHeader +  4, posEpsNew,                                  littleEndian);
1060                 ul2Data(dosEpsHeader +  8, posEndEpsNew - posEpsNew,                   littleEndian);
1061                 ul2Data(dosEpsHeader + 12, sizeWmf == 0 ? 0 : posEndEpsNew,            littleEndian);
1062                 ul2Data(dosEpsHeader + 16, sizeWmf,                                    littleEndian);
1063                 ul2Data(dosEpsHeader + 20, sizeTiff == 0 ? 0 : posEndEpsNew + sizeWmf, littleEndian);
1064                 ul2Data(dosEpsHeader + 24, sizeTiff,                                   littleEndian);
1065                 us2Data(dosEpsHeader + 28, 0xFFFF,                                     littleEndian);
1066                 writeTemp(*tempIo, dosEpsHeader, sizeof(dosEpsHeader));
1067             }
1068 
1069             // copy temporary file to real output file
1070             io.close();
1071             io.transfer(*tempIo);
1072         }
1073     }
1074 
1075 } // namespace
1076 
1077 // *****************************************************************************
1078 // class member definitions
1079 namespace Exiv2
1080 {
1081 
EpsImage(BasicIo::AutoPtr io,bool create)1082     EpsImage::EpsImage(BasicIo::AutoPtr io, bool create)
1083             : Image(ImageType::eps, mdXmp, io)
1084     {
1085         //LogMsg::setLevel(LogMsg::debug);
1086         if (create) {
1087             if (io_->open() == 0) {
1088                 #ifdef DEBUG
1089                 EXV_DEBUG << "Exiv2::EpsImage:: Creating blank EPS image\n";
1090                 #endif
1091                 IoCloser closer(*io_);
1092                 if (io_->write(reinterpret_cast<const byte*>(epsBlank.data()), static_cast<long>(epsBlank.size())) != static_cast<long>(epsBlank.size())) {
1093                     #ifndef SUPPRESS_WARNINGS
1094                     EXV_WARNING << "Failed to write blank EPS image.\n";
1095                     #endif
1096                     throw Error(21);
1097                 }
1098             }
1099         }
1100     }
1101 
mimeType() const1102     std::string EpsImage::mimeType() const
1103     {
1104         return "application/postscript";
1105     }
1106 
setComment(const std::string &)1107     void EpsImage::setComment(const std::string& /*comment*/)
1108     {
1109         throw Error(32, "Image comment", "EPS");
1110     }
1111 
readMetadata()1112     void EpsImage::readMetadata()
1113     {
1114         #ifdef DEBUG
1115         EXV_DEBUG << "Exiv2::EpsImage::readMetadata: Reading EPS file " << io_->path() << "\n";
1116         #endif
1117 
1118         // read metadata
1119         readWriteEpsMetadata(*io_, xmpPacket_, nativePreviews_, /* write = */ false);
1120 
1121         // decode XMP metadata
1122         if (xmpPacket_.size() > 0 && XmpParser::decode(xmpData_, xmpPacket_) > 1) {
1123             #ifndef SUPPRESS_WARNINGS
1124             EXV_WARNING << "Failed to decode XMP metadata.\n";
1125             #endif
1126             throw Error(14);
1127         }
1128 
1129         #ifdef DEBUG
1130         EXV_DEBUG << "Exiv2::EpsImage::readMetadata: Finished reading EPS file " << io_->path() << "\n";
1131         #endif
1132     }
1133 
writeMetadata()1134     void EpsImage::writeMetadata()
1135     {
1136         #ifdef DEBUG
1137         EXV_DEBUG << "Exiv2::EpsImage::writeMetadata: Writing EPS file " << io_->path() << "\n";
1138         #endif
1139 
1140         // encode XMP metadata if necessary
1141         if (!writeXmpFromPacket() && XmpParser::encode(xmpPacket_, xmpData_) > 1) {
1142             #ifndef SUPPRESS_WARNINGS
1143             EXV_WARNING << "Failed to encode XMP metadata.\n";
1144             #endif
1145             throw Error(21);
1146         }
1147 
1148         // write metadata
1149         readWriteEpsMetadata(*io_, xmpPacket_, nativePreviews_, /* write = */ true);
1150 
1151         #ifdef DEBUG
1152         EXV_DEBUG << "Exiv2::EpsImage::writeMetadata: Finished writing EPS file " << io_->path() << "\n";
1153         #endif
1154     }
1155 
1156     // *************************************************************************
1157     // free functions
newEpsInstance(BasicIo::AutoPtr io,bool create)1158     Image::AutoPtr newEpsInstance(BasicIo::AutoPtr io, bool create)
1159     {
1160         Image::AutoPtr image(new EpsImage(io, create));
1161         if (!image->good()) {
1162             image.reset();
1163         }
1164         return image;
1165     }
1166 
isEpsType(BasicIo & iIo,bool advance)1167     bool isEpsType(BasicIo& iIo, bool advance)
1168     {
1169         // read as many bytes as needed for the longest (DOS) EPS signature
1170         long bufSize = static_cast<long>(dosEpsSignature.size());
1171         for (size_t i = 0; i < (sizeof epsFirstLine) / (sizeof *epsFirstLine); i++) {
1172             if (bufSize < static_cast<long>(epsFirstLine[i].size())) {
1173                 bufSize = static_cast<long>(epsFirstLine[i].size());
1174             }
1175         }
1176         DataBuf buf = iIo.read(bufSize);
1177         if (iIo.error() || buf.size_ != bufSize) {
1178             return false;
1179         }
1180         // check for all possible (DOS) EPS signatures
1181         bool matched = (memcmp(buf.pData_, dosEpsSignature.data(), dosEpsSignature.size()) == 0);
1182         for (size_t i = 0; !matched && i < (sizeof epsFirstLine) / (sizeof *epsFirstLine); i++) {
1183             matched = (memcmp(buf.pData_, epsFirstLine[i].data(), epsFirstLine[i].size()) == 0);
1184         }
1185         // seek back if possible and requested
1186         if (!advance || !matched) {
1187             iIo.seek(-buf.size_, BasicIo::cur);
1188         }
1189         return matched;
1190     }
1191 
1192 } // namespace Exiv2
1193