1 // ***************************************************************** -*- C++ -*-
2 /*
3  * Copyright (C) 2004-2021 Exiv2 authors
4  * This program is part of the Exiv2 distribution.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 #include <exiv2/exiv2.hpp>
22 
23 // include local header files which are not part of libexiv2
24 #include "actions.hpp"
25 #include "convert.hpp"
26 #include "exiv2app.hpp"
27 #include "futils.hpp"
28 #include "getopt.hpp"
29 #include "i18n.h"  // NLS support.
30 #include "utils.hpp"
31 #include "xmp_exiv2.hpp"
32 
33 #include <iostream>
34 #include <fstream>
35 #include <iomanip>
36 #include <cstring>
37 #include <cassert>
38 #include <cctype>
39 
40 #if defined(EXV_HAVE_REGEX_H)
41 #include <regex.h>
42 #endif
43 
44 // *****************************************************************************
45 // local declarations
46 namespace {
47 
48     //! List of all command identifiers and corresponding strings
49     static const CmdIdAndString cmdIdAndString[] = {
50         { add, "add" },
51         { set, "set" },
52         { del, "del" },
53         { reg, "reg" },
54         { invalidCmdId, "invalidCmd" }          // End of list marker
55     };
56 
57     // Return a command Id for a command string
58     CmdId commandId(const std::string& cmdString);
59 
60     // Evaluate [-]HH[:MM[:SS]], returns true and sets time to the value
61     // in seconds if successful, else returns false.
62     bool parseTime(const std::string& ts, long& time);
63 
64     /*!
65       @brief Parse the oparg string into a bitmap of common targets.
66       @param optarg Option arguments
67       @param action Action being processed
68       @return A bitmap of common targets or -1 in case of a parse error
69      */
70     int parseCommonTargets(const std::string& optarg,
71                            const std::string& action);
72 
73     /*!
74       @brief Parse numbers separated by commas into container
75       @param previewNumbers Container for the numbers
76       @param optarg Option arguments
77       @param j Starting index into optarg
78       @return Number of characters processed
79      */
80     int parsePreviewNumbers(Params::PreviewNumbers& previewNumbers,
81                             const std::string& optarg,
82                             int j);
83 
84     /*!
85       @brief Parse metadata modification commands from multiple files
86       @param modifyCmds Reference to a structure to store the parsed commands
87       @param cmdFiles Container with the file names
88      */
89     bool parseCmdFiles(ModifyCmds& modifyCmds,
90                        const Params::CmdFiles& cmdFiles);
91 
92     /*!
93       @brief Parse metadata modification commands from a container of commands
94       @param modifyCmds Reference to a structure to store the parsed commands
95       @param cmdLines Container with the commands
96      */
97     bool parseCmdLines(ModifyCmds& modifyCmds,
98                        const Params::CmdLines& cmdLines);
99 
100     /*!
101       @brief Parse one line of the command file
102       @param modifyCmd Reference to a command structure to store the parsed
103              command
104       @param line Input line
105       @param num Line number (used for error output)
106      */
107     bool parseLine(ModifyCmd& modifyCmd,
108                    const std::string& line, int num);
109 
110     /*!
111       @brief Parses a string containing backslash-escapes
112       @param input Input string, assumed to be UTF-8
113      */
114     std::string parseEscapes(const std::string& input);
115 }
116 
117 // *****************************************************************************
118 // Main
main(int argc,char * const argv[])119 int main(int argc, char* const argv[])
120 {
121     Exiv2::XmpParser::initialize();
122     ::atexit(Exiv2::XmpParser::terminate);
123 #ifdef EXV_ENABLE_BMFF
124     Exiv2::enableBMFF();
125 #endif
126 
127 #ifdef EXV_ENABLE_NLS
128     setlocale(LC_ALL, "");
129     const std::string localeDir = EXV_LOCALEDIR[0] == '/' ? EXV_LOCALEDIR : (Exiv2::getProcessPath() + EXV_SEPARATOR_STR + EXV_LOCALEDIR);
130     bindtextdomain(EXV_PACKAGE_NAME, localeDir.c_str());
131     textdomain(EXV_PACKAGE_NAME);
132 #endif
133 
134     // Handle command line arguments
135     Params& params = Params::instance();
136     if (params.getopt(argc, argv)) {
137         params.usage();
138         return 1;
139     }
140     if (params.help_) {
141         params.help();
142         return 0;
143     }
144     if (params.version_) {
145         params.version(params.verbose_);
146         return 0;
147     }
148 
149     int rc = 0;
150 
151     try {
152         // Create the required action class
153         Action::TaskFactory& taskFactory = Action::TaskFactory::instance();
154         Action::Task::AutoPtr task = taskFactory.create(Action::TaskType(params.action_));
155         assert(task.get());
156 
157         // Process all files
158         int n = 1;
159         int s = static_cast<int>(params.files_.size());
160         int w = s > 9 ? s > 99 ? 3 : 2 : 1;
161         for (Params::Files::const_iterator i = params.files_.begin(); i != params.files_.end(); ++i) {
162             if (params.verbose_) {
163                 std::cout << _("File") << " " << std::setw(w) << std::right << n++ << "/" << s << ": " << *i
164                           << std::endl;
165             }
166             task->setBinary(params.binary_);
167             int ret = task->run(*i);
168             if (rc == 0)
169                 rc = ret;
170         }
171 
172         taskFactory.cleanup();
173         params.cleanup();
174         Exiv2::XmpParser::terminate();
175 
176     } catch (const std::exception& exc) {
177         std::cerr << "Uncaught exception: " << exc.what() << std::endl;
178         rc = 1;
179     }
180 
181     // Return a positive one byte code for better consistency across platforms
182     return static_cast<unsigned int>(rc) % 256;
183 } // main
184 
185 // *****************************************************************************
186 // class Params
187 Params* Params::instance_ = 0;
188 
189 const Params::YodAdjust Params::emptyYodAdjust_[] = {
190     { false, "-Y", 0 },
191     { false, "-O", 0 },
192     { false, "-D", 0 },
193 };
194 
instance()195 Params& Params::instance()
196 {
197     if (0 == instance_) {
198         instance_ = new Params;
199     }
200     return *instance_;
201 }
202 
~Params()203 Params::~Params() {
204 #if defined(EXV_HAVE_REGEX_H)
205     for (size_t i=0; i<instance().greps_.size(); ++i) {
206         regfree(&instance().greps_.at(i));
207     }
208 #endif
209 }
210 
cleanup()211 void Params::cleanup()
212 {
213     delete instance_;
214     instance_ = 0;
215 }
216 
version(bool verbose,std::ostream & os) const217 void Params::version(bool verbose,std::ostream& os) const
218 {
219     os << EXV_PACKAGE_STRING << std::endl;
220     if ( Params::instance().greps_.empty() && !verbose) {
221     os << "\n"
222        << _("This program is free software; you can redistribute it and/or\n"
223             "modify it under the terms of the GNU General Public License\n"
224             "as published by the Free Software Foundation; either version 2\n"
225             "of the License, or (at your option) any later version.\n")
226        << "\n"
227        << _("This program is distributed in the hope that it will be useful,\n"
228             "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
229             "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
230             "GNU General Public License for more details.\n")
231        << "\n"
232        << _("You should have received a copy of the GNU General Public\n"
233             "License along with this program; if not, write to the Free\n"
234             "Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n"
235             "Boston, MA 02110-1301 USA\n");
236     }
237 
238     if ( verbose ) Exiv2::dumpLibraryInfo(os,Params::instance().greps_);
239 }
240 
usage(std::ostream & os) const241 void Params::usage(std::ostream& os) const
242 {
243     os << _("Usage:") << " " << progname()
244        << " " << _("[ options ] [ action ] file ...\n\n")
245        << _("Manipulate the Exif metadata of images.\n");
246 }
247 
printTarget(const std::string & before,int target,bool bPrint,std::ostream & out)248 std::string Params::printTarget(const std::string &before, int target, bool bPrint, std::ostream& out)
249 {
250     std::string t;
251     if ( target & Params::ctExif       ) t+= 'e';
252     if ( target & Params::ctXmpSidecar ) t+= 'X';
253     if ( target & Params::ctXmpRaw     ) t+= target & Params::ctXmpSidecar ? 'X' : 'R' ;
254     if ( target & Params::ctIptc       ) t+= 'i';
255     if ( target & Params::ctIccProfile ) t+= 'C';
256     if ( target & Params::ctIptcRaw    ) t+= 'I';
257     if ( target & Params::ctXmp        ) t+= 'x';
258     if ( target & Params::ctComment    ) t+= 'c';
259     if ( target & Params::ctThumb      ) t+= 't';
260     if ( target & Params::ctPreview    ) t+= 'p';
261     if ( target & Params::ctStdInOut   ) t+= '-';
262 
263     if ( bPrint ) out << before << " :" << t << std::endl;
264     return t;
265 }
266 
help(std::ostream & os) const267 void Params::help(std::ostream& os) const
268 {
269     usage(os);
270     os << _("\nActions:\n")
271        << _("  ad | adjust   Adjust Exif timestamps by the given time. This action\n"
272             "                requires at least one of the -a, -Y, -O or -D options.\n")
273        << _("  pr | print    Print image metadata.\n")
274        << _("  rm | delete   Delete image metadata from the files.\n")
275        << _("  in | insert   Insert metadata from corresponding *.exv files.\n"
276             "                Use option -S to change the suffix of the input files.\n")
277        << _("  ex | extract  Extract metadata to *.exv, *.xmp and thumbnail image files.\n")
278        << _("  mv | rename   Rename files and/or set file timestamps according to the\n"
279             "                Exif create timestamp. The filename format can be set with\n"
280             "                -r format, timestamp options are controlled with -t and -T.\n")
281        << _("  mo | modify   Apply commands to modify (add, set, delete) the Exif and\n"
282             "                IPTC metadata of image files or set the JPEG comment.\n"
283             "                Requires option -c, -m or -M.\n")
284        << _("  fi | fixiso   Copy ISO setting from the Nikon Makernote to the regular\n"
285             "                Exif tag.\n")
286        << _("  fc | fixcom   Convert the UNICODE Exif user comment to UCS-2. Its current\n"
287             "                character encoding can be specified with the -n option.\n")
288        << _("\nOptions:\n")
289        << _("   -h      Display this help and exit.\n")
290        << _("   -V      Show the program version and exit.\n")
291        << _("   -v      Be verbose during the program run.\n")
292        << _("   -q      Silence warnings and error messages during the program run (quiet).\n")
293        << _("   -Q lvl  Set log-level to d(ebug), i(nfo), w(arning), e(rror) or m(ute).\n")
294        << _("   -b      Show large binary values.\n")
295        << _("   -u      Show unknown tags.\n")
296        << _("   -g key  Only output info for this key (grep).\n")
297        << _("   -K key  Only output info for this key (exact match).\n")
298        << _("   -n enc  Charset to use to decode UNICODE Exif user comments.\n")
299        << _("   -k      Preserve file timestamps (keep).\n")
300        << _("   -t      Also set the file timestamp in 'rename' action (overrides -k).\n")
301        << _("   -T      Only set the file timestamp in 'rename' action, do not rename\n"
302             "           the file (overrides -k).\n")
303        << _("   -f      Do not prompt before overwriting existing files (force).\n")
304        << _("   -F      Do not prompt before renaming files (Force).\n")
305        << _("   -a time Time adjustment in the format [-]HH[:MM[:SS]]. This option\n"
306             "           is only used with the 'adjust' action.\n")
307        << _("   -Y yrs  Year adjustment with the 'adjust' action.\n")
308        << _("   -O mon  Month adjustment with the 'adjust' action.\n")
309        << _("   -D day  Day adjustment with the 'adjust' action.\n")
310        << _("   -p mode Print mode for the 'print' action. Possible modes are:\n")
311        << _("             s : print a summary of the Exif metadata (the default)\n")
312        << _("             a : print Exif, IPTC and XMP metadata (shortcut for -Pkyct)\n")
313        << _("             e : print Exif metadata (shortcut for -PEkycv)\n")
314        << _("             t : interpreted (translated) Exif data (-PEkyct)\n")
315        << _("             v : plain Exif data values (-PExgnycv)\n")
316        << _("             h : hexdump of the Exif data (-PExgnycsh)\n")
317        << _("             i : IPTC data values (-PIkyct)\n")
318        << _("             x : XMP properties (-PXkyct)\n")
319        << _("             c : JPEG comment\n")
320        << _("             p : list available previews\n")
321        << _("             C : print ICC profile embedded in image\n")
322        << _("             R : recursive print structure of image\n")
323        << _("             S : print structure of image\n")
324        << _("             X : extract XMP from image\n")
325        << _("   -P flgs Print flags for fine control of tag lists ('print' action):\n")
326        << _("             E : include Exif tags in the list\n")
327        << _("             I : IPTC datasets\n")
328        << _("             X : XMP properties\n")
329        << _("             x : print a column with the tag number\n")
330        << _("             g : group name\n")
331        << _("             k : key\n")
332        << _("             l : tag label\n")
333        << _("             n : tag name\n")
334        << _("             y : type\n")
335        << _("             c : number of components (count)\n")
336        << _("             s : size in bytes\n")
337        << _("             v : plain data value\n")
338        << _("             t : interpreted (translated) data\n")
339        << _("             h : hexdump of the data\n")
340        << _("   -d tgt  Delete target(s) for the 'delete' action. Possible targets are:\n")
341        << _("             a : all supported metadata (the default)\n")
342        << _("             e : Exif section\n")
343        << _("             t : Exif thumbnail only\n")
344        << _("             i : IPTC data\n")
345        << _("             x : XMP packet\n")
346        << _("             c : JPEG comment\n")
347        << _("   -i tgt  Insert target(s) for the 'insert' action. Possible targets are\n"
348             "           the same as those for the -d option, plus a modifier:\n"
349             "             X : Insert metadata from an XMP sidecar file <file>.xmp\n"
350             "           Only JPEG thumbnails can be inserted, they need to be named\n"
351             "           <file>-thumb.jpg\n")
352        << _("   -e tgt  Extract target(s) for the 'extract' action. Possible targets\n"
353             "           are the same as those for the -d option, plus a target to extract\n"
354             "           preview images and a modifier to generate an XMP sidecar file:\n"
355             "             p[<n>[,<m> ...]] : Extract preview images.\n"
356             "             X : Extract metadata to an XMP sidecar file <file>.xmp\n")
357        << _("   -r fmt  Filename format for the 'rename' action. The format string\n"
358             "           follows strftime(3). The following keywords are supported:\n")
359        << _("             :basename:   - original filename without extension\n")
360        << _("             :dirname:    - name of the directory holding the original file\n")
361        << _("             :parentname: - name of parent directory\n")
362        << _("           Default filename format is ")
363        <<               format_ << ".\n"
364        << _("   -c txt  JPEG comment string to set in the image.\n")
365        << _("   -m file Command file for the modify action. The format for commands is\n"
366             "           set|add|del <key> [[<type>] <value>].\n")
367        << _("   -M cmd  Command line for the modify action. The format for the\n"
368             "           commands is the same as that of the lines of a command file.\n")
369        << _("   -l dir  Location (directory) for files to be inserted from or extracted to.\n")
370        << _("   -S .suf Use suffix .suf for source files for insert command.\n\n");
371 } // Params::help
372 
option(int opt,const std::string & optarg,int optopt)373 int Params::option(int opt, const std::string& optarg, int optopt)
374 {
375     int rc = 0;
376     switch (opt) {
377     case 'h': help_ = true; break;
378     case 'V': version_ = true; break;
379     case 'v': verbose_ = true; break;
380     case 'q': Exiv2::LogMsg::setLevel(Exiv2::LogMsg::mute); break;
381     case 'Q': rc = setLogLevel(optarg); break;
382     case 'k': preserve_ = true; break;
383     case 'b': binary_ = true; break;
384     case 'u': unknown_ = false; break;
385     case 'f': force_ = true; fileExistsPolicy_ = overwritePolicy; break;
386     case 'F': force_ = true; fileExistsPolicy_ = renamePolicy; break;
387     case 'g': rc = evalGrep(optarg); break;
388     case 'K': rc = evalKey(optarg); printMode_ = pmList; break;
389     case 'n': charset_ = optarg; break;
390     case 'r': rc = evalRename(opt, optarg); break;
391     case 't': rc = evalRename(opt, optarg); break;
392     case 'T': rc = evalRename(opt, optarg); break;
393     case 'a': rc = evalAdjust(optarg); break;
394     case 'Y': rc = evalYodAdjust(yodYear, optarg); break;
395     case 'O': rc = evalYodAdjust(yodMonth, optarg); break;
396     case 'D': rc = evalYodAdjust(yodDay, optarg); break;
397     case 'p': rc = evalPrint(optarg); break;
398     case 'P': rc = evalPrintFlags(optarg); break;
399     case 'd': rc = evalDelete(optarg); break;
400     case 'e': rc = evalExtract(optarg); break;
401     case 'C': rc = evalExtract(optarg); break;
402     case 'i': rc = evalInsert(optarg); break;
403     case 'c': rc = evalModify(opt, optarg); break;
404     case 'm': rc = evalModify(opt, optarg); break;
405     case 'M': rc = evalModify(opt, optarg); break;
406     case 'l': directory_ = optarg; break;
407     case 'S': suffix_ = optarg; break;
408     case ':':
409         std::cerr << progname() << ": " << _("Option") << " -" << static_cast<char>(optopt)
410                    << " " << _("requires an argument\n");
411         rc = 1;
412         break;
413     case '?':
414         std::cerr << progname() << ": " << _("Unrecognized option") << " -"
415                   << static_cast<char>(optopt) << "\n";
416         rc = 1;
417         break;
418     default:
419         std::cerr << progname()
420                   << ": " << _("getopt returned unexpected character code") << " "
421                   << std::hex << opt << "\n";
422         rc = 1;
423         break;
424     }
425     return rc;
426 } // Params::option
427 
setLogLevel(const std::string & optarg)428 int Params::setLogLevel(const std::string& optarg)
429 {
430     int rc = 0;
431     const char logLevel = tolower(optarg[0]);
432     switch (logLevel) {
433     case 'd': Exiv2::LogMsg::setLevel(Exiv2::LogMsg::debug); break;
434     case 'i': Exiv2::LogMsg::setLevel(Exiv2::LogMsg::info); break;
435     case 'w': Exiv2::LogMsg::setLevel(Exiv2::LogMsg::warn); break;
436     case 'e': Exiv2::LogMsg::setLevel(Exiv2::LogMsg::error); break;
437     case 'm': Exiv2::LogMsg::setLevel(Exiv2::LogMsg::mute); break;
438     default:
439         std::cerr << progname() << ": " << _("Option") << " -Q: "
440                   << _("Invalid argument") << " \"" << optarg << "\"\n";
441         rc = 1;
442         break;
443     }
444     return rc;
445 } // Params::setLogLevel
446 
447 // http://stackoverflow.com/questions/874134/find-if-string-ends-with-another-string-in-c
ends_with(std::string const & value,std::string const & ending,std::string & stub)448 static inline bool ends_with(std::string const & value, std::string const & ending,std::string& stub)
449 {
450     if (ending.size() > value.size()) return false;
451     bool bResult = std::equal(ending.rbegin(), ending.rend(), value.rbegin());
452     stub         = bResult ? value.substr(0,value.length() - ending.length()) : value;
453     return bResult ;
454 }
455 
evalGrep(const std::string & optarg)456 int Params::evalGrep( const std::string& optarg)
457 {
458     int result=0;
459     std::string pattern;
460     std::string ignoreCase("/i");
461     bool bIgnoreCase = ends_with(optarg,ignoreCase,pattern);
462 #if defined(EXV_HAVE_REGEX_H)
463     // try to compile a reg-exp from the input argument and store it in the vector
464     const size_t i = greps_.size();
465     greps_.resize(i + 1);
466     regex_t *pRegex = &greps_[i];
467     int errcode = regcomp( pRegex, pattern.c_str(), bIgnoreCase ? REG_NOSUB|REG_ICASE : REG_NOSUB);
468 
469     // there was an error compiling the regexp
470     if( errcode ) {
471         size_t length = regerror (errcode, pRegex, NULL, 0);
472         char *buffer = new char[ length];
473         regerror (errcode, pRegex, buffer, length);
474         std::cerr << progname()
475               << ": " << _("Option") << " -g: "
476               << _("Invalid regexp") << " \"" << optarg << "\": " << buffer << "\n";
477 
478         // free the memory and drop the regexp
479         delete[] buffer;
480         regfree( pRegex);
481         greps_.resize(i);
482         result=1;
483     }
484 #else
485     greps_.push_back(Exiv2_grep_key_t(pattern,bIgnoreCase));
486 #endif
487     return result;
488 } // Params::evalGrep
489 
evalKey(const std::string & optarg)490 int Params::evalKey( const std::string& optarg)
491 {
492     int result=0;
493     keys_.push_back(optarg);
494     return result;
495 } // Params::evalKey
496 
evalRename(int opt,const std::string & optarg)497 int Params::evalRename(int opt, const std::string& optarg)
498 {
499     int rc = 0;
500     switch (action_) {
501     case Action::none:
502         action_ = Action::rename;
503         switch (opt) {
504         case 'r':
505             format_ = optarg;
506             formatSet_ = true;
507             break;
508         case 't': timestamp_ = true; break;
509         case 'T': timestampOnly_ = true; break;
510         }
511         break;
512     case Action::rename:
513         if (opt == 'r' && (formatSet_ || timestampOnly_)) {
514             std::cerr << progname()
515                       << ": " << _("Ignoring surplus option") << " -r \"" << optarg << "\"\n";
516         }
517         else {
518             format_ = optarg;
519             formatSet_ = true;
520         }
521         break;
522     default:
523         std::cerr << progname()
524                   << ": " << _("Option") << " -" << (char)opt
525                   << " " << _("is not compatible with a previous option\n");
526         rc = 1;
527         break;
528     }
529     return rc;
530 } // Params::evalRename
531 
evalAdjust(const std::string & optarg)532 int Params::evalAdjust(const std::string& optarg)
533 {
534     int rc = 0;
535     switch (action_) {
536     case Action::none:
537     case Action::adjust:
538         if (adjust_) {
539             std::cerr << progname()
540                       << ": " << _("Ignoring surplus option -a")  << " " << optarg << "\n";
541             break;
542         }
543         action_ = Action::adjust;
544         adjust_ = parseTime(optarg, adjustment_);
545         if (!adjust_) {
546             std::cerr << progname() << ": " << _("Error parsing -a option argument") << " `"
547                       << optarg << "'\n";
548             rc = 1;
549         }
550         break;
551     default:
552         std::cerr << progname()
553                   << ": " << _("Option -a is not compatible with a previous option\n");
554         rc = 1;
555         break;
556     }
557     return rc;
558 } // Params::evalAdjust
559 
evalYodAdjust(const Yod & yod,const std::string & optarg)560 int Params::evalYodAdjust(const Yod& yod, const std::string& optarg)
561 {
562     int rc = 0;
563     switch (action_) {
564     case Action::none: // fall-through
565     case Action::adjust:
566         if (yodAdjust_[yod].flag_) {
567             std::cerr << progname()
568                       << ": " << _("Ignoring surplus option") << " "
569                       << yodAdjust_[yod].option_ << " " << optarg << "\n";
570             break;
571         }
572         action_ = Action::adjust;
573         yodAdjust_[yod].flag_ = true;
574         if (!Util::strtol(optarg.c_str(), yodAdjust_[yod].adjustment_)) {
575             std::cerr << progname() << ": " << _("Error parsing") << " "
576                       << yodAdjust_[yod].option_ << " "
577                       << _("option argument") << " `" << optarg << "'\n";
578             rc = 1;
579         }
580         break;
581     default:
582         std::cerr << progname()
583                   << ": " << _("Option") << " "
584                   << yodAdjust_[yod].option_ << " "
585                   << _("is not compatible with a previous option\n");
586         rc = 1;
587         break;
588     }
589     return rc;
590 } // Params::evalYodAdjust
591 
evalPrint(const std::string & optarg)592 int Params::evalPrint(const std::string& optarg)
593 {
594     int rc = 0;
595     switch (action_) {
596         case Action::none:
597             switch (optarg[0]) {
598                 case 's':
599                     action_ = Action::print;
600                     printMode_ = pmSummary;
601                     break;
602                 case 'a':
603                     rc = evalPrintFlags("kyct");
604                     break;
605                 case 'e':
606                     rc = evalPrintFlags("Ekycv");
607                     break;
608                 case 't':
609                     rc = evalPrintFlags("Ekyct");
610                     break;
611                 case 'v':
612                     rc = evalPrintFlags("Exgnycv");
613                     break;
614                 case 'h':
615                     rc = evalPrintFlags("Exgnycsh");
616                     break;
617                 case 'i':
618                     rc = evalPrintFlags("Ikyct");
619                     break;
620                 case 'x':
621                     rc = evalPrintFlags("Xkyct");
622                     break;
623                 case 'c':
624                     action_ = Action::print;
625                     printMode_ = pmComment;
626                     break;
627                 case 'p':
628                     action_ = Action::print;
629                     printMode_ = pmPreview;
630                     break;
631                 case 'C':
632                     action_ = Action::print;
633                     printMode_ = pmIccProfile;
634                     break;
635                 case 'R':
636                 #ifdef NDEBUG
637                     std::cerr << progname() << ": " << _("Action not available in Release mode")
638                               << ": '" << optarg << "'\n";
639                     rc = 1;
640                 #else
641                     action_ = Action::print;
642                     printMode_ = pmRecursive;
643                 #endif
644                     break;
645                 case 'S':
646                     action_ = Action::print;
647                     printMode_ = pmStructure;
648                     break;
649                 case 'X':
650                     action_ = Action::print;
651                     printMode_ = pmXMP;
652                     break;
653                 default:
654                     std::cerr << progname() << ": " << _("Unrecognized print mode") << " `" << optarg << "'\n";
655                     rc = 1;
656                     break;
657             }
658             break;
659         case Action::print:
660             std::cerr << progname() << ": " << _("Ignoring surplus option -p") << optarg << "\n";
661             break;
662         default:
663             std::cerr << progname() << ": " << _("Option -p is not compatible with a previous option\n");
664             rc = 1;
665             break;
666     }
667     return rc;
668 }  // Params::evalPrint
669 
evalPrintFlags(const std::string & optarg)670 int Params::evalPrintFlags(const std::string& optarg)
671 {
672     int rc = 0;
673     switch (action_) {
674     case Action::none:
675         action_ = Action::print;
676         printMode_ = pmList;
677         for (std::size_t i = 0; i < optarg.length(); ++i) {
678             switch (optarg[i]) {
679             case 'E': printTags_  |= Exiv2::mdExif; break;
680             case 'I': printTags_  |= Exiv2::mdIptc; break;
681             case 'X': printTags_  |= Exiv2::mdXmp;  break;
682             case 'x': printItems_ |= prTag;   break;
683             case 'g': printItems_ |= prGroup; break;
684             case 'k': printItems_ |= prKey;   break;
685             case 'l': printItems_ |= prLabel; break;
686             case 'n': printItems_ |= prName;  break;
687             case 'y': printItems_ |= prType;  break;
688             case 'c': printItems_ |= prCount; break;
689             case 's': printItems_ |= prSize;  break;
690             case 'v': printItems_ |= prValue; break;
691             case 't': printItems_ |= prTrans; break;
692             case 'h': printItems_ |= prHex;   break;
693             case 'V': printItems_ |= prSet|prValue;break;
694             default:
695                 std::cerr << progname() << ": " << _("Unrecognized print item") << " `"
696                           << optarg[i] << "'\n";
697                 rc = 1;
698                 break;
699             }
700         }
701         break;
702     case Action::print:
703         std::cerr << progname() << ": "
704                   << _("Ignoring surplus option -P") << optarg << "\n";
705         break;
706     default:
707         std::cerr << progname() << ": "
708                   << _("Option -P is not compatible with a previous option\n");
709         rc = 1;
710         break;
711     }
712     return rc;
713 } // Params::evalPrintFlags
714 
evalDelete(const std::string & optarg)715 int Params::evalDelete(const std::string& optarg)
716 {
717     int rc = 0;
718     switch (action_) {
719     case Action::none:
720         action_ = Action::erase;
721         target_ = 0;
722         // fallthrough
723     case Action::erase:
724         rc = parseCommonTargets(optarg, "erase");
725         if (rc > 0) {
726             target_ |= rc;
727             rc = 0;
728         }
729         else {
730             rc = 1;
731         }
732         break;
733     default:
734         std::cerr << progname() << ": "
735                   << _("Option -d is not compatible with a previous option\n");
736         rc = 1;
737         break;
738     }
739     return rc;
740 } // Params::evalDelete
741 
evalExtract(const std::string & optarg)742 int Params::evalExtract(const std::string& optarg)
743 {
744     int rc = 0;
745     switch (action_) {
746     case Action::none:
747     case Action::modify:
748         action_ = Action::extract;
749         target_ = 0;
750         // fallthrough
751     case Action::extract:
752         rc = parseCommonTargets(optarg, "extract");
753         if (rc > 0) {
754             target_ |= rc;
755             rc = 0;
756         }
757         else {
758             rc = 1;
759         }
760         break;
761     default:
762         std::cerr << progname() << ": "
763                   << _("Option -e is not compatible with a previous option\n");
764         rc = 1;
765         break;
766     }
767     return rc;
768 } // Params::evalExtract
769 
evalInsert(const std::string & optarg)770 int Params::evalInsert(const std::string& optarg)
771 {
772     int rc = 0;
773     switch (action_) {
774     case Action::none:
775     case Action::modify:
776         action_ = Action::insert;
777         target_ = 0;
778         // fallthrough
779     case Action::insert:
780         rc = parseCommonTargets(optarg, "insert");
781         if (rc > 0) {
782             target_ |= rc;
783             rc = 0;
784         }
785         else {
786             rc = 1;
787         }
788         break;
789     default:
790         std::cerr << progname() << ": "
791                   << _("Option -i is not compatible with a previous option\n");
792         rc = 1;
793         break;
794     }
795     return rc;
796 } // Params::evalInsert
797 
evalModify(int opt,const std::string & optarg)798 int Params::evalModify(int opt, const std::string& optarg)
799 {
800     int rc = 0;
801     switch (action_) {
802     case Action::none:
803         action_ = Action::modify;
804         // fallthrough
805     case Action::modify:
806     case Action::extract:
807     case Action::insert:
808         if (opt == 'c') jpegComment_ = parseEscapes(optarg);
809         if (opt == 'm') cmdFiles_.push_back(optarg);  // parse the files later
810         if (opt == 'M') cmdLines_.push_back(optarg);  // parse the commands later
811         break;
812     default:
813         std::cerr << progname() << ": "
814                   << _("Option") << " -" << (char)opt << " "
815                   << _("is not compatible with a previous option\n");
816         rc = 1;
817         break;
818     }
819     return rc;
820 } // Params::evalModify
821 
nonoption(const std::string & argv)822 int Params::nonoption(const std::string& argv)
823 {
824     int rc = 0;
825     bool action = false;
826     if (first_) {
827         // The first non-option argument must be the action
828         first_ = false;
829         if (argv == "ad" || argv == "adjust") {
830             if (action_ != Action::none && action_ != Action::adjust) {
831                 std::cerr << progname() << ": "
832                           << _("Action adjust is not compatible with the given options\n");
833                 rc = 1;
834             }
835             action = true;
836             action_ = Action::adjust;
837         }
838         if (argv == "pr" || argv == "print") {
839             if (action_ != Action::none && action_ != Action::print) {
840                 std::cerr << progname() << ": "
841                           << _("Action print is not compatible with the given options\n");
842                 rc = 1;
843             }
844             action = true;
845             action_ = Action::print;
846         }
847         if (argv == "rm" || argv == "delete") {
848             if (action_ != Action::none && action_ != Action::erase) {
849                 std::cerr << progname() << ": "
850                           << _("Action delete is not compatible with the given options\n");
851                 rc = 1;
852             }
853             action = true;
854             action_ = Action::erase;
855         }
856         if (argv == "ex" || argv == "extract") {
857             if (   action_ != Action::none
858                 && action_ != Action::extract
859                 && action_ != Action::modify) {
860                 std::cerr << progname() << ": "
861                           << _("Action extract is not compatible with the given options\n");
862                 rc = 1;
863             }
864             action = true;
865             action_ = Action::extract;
866         }
867         if (argv == "in" || argv == "insert") {
868             if (   action_ != Action::none
869                 && action_ != Action::insert
870                 && action_ != Action::modify) {
871                 std::cerr << progname() << ": "
872                           << _("Action insert is not compatible with the given options\n");
873                 rc = 1;
874             }
875             action = true;
876             action_ = Action::insert;
877         }
878         if (argv == "mv" || argv == "rename") {
879             if (action_ != Action::none && action_ != Action::rename) {
880                 std::cerr << progname() << ": "
881                           << _("Action rename is not compatible with the given options\n");
882                 rc = 1;
883             }
884             action = true;
885             action_ = Action::rename;
886         }
887         if (argv == "mo" || argv == "modify") {
888             if (action_ != Action::none && action_ != Action::modify) {
889                 std::cerr << progname() << ": "
890                           << _("Action modify is not compatible with the given options\n");
891                 rc = 1;
892             }
893             action = true;
894             action_ = Action::modify;
895         }
896         if (argv == "fi" || argv == "fixiso") {
897             if (action_ != Action::none && action_ != Action::fixiso) {
898                 std::cerr << progname() << ": "
899                           << _("Action fixiso is not compatible with the given options\n");
900                 rc = 1;
901             }
902             action = true;
903             action_ = Action::fixiso;
904         }
905         if (argv == "fc" || argv == "fixcom" || argv == "fixcomment") {
906             if (action_ != Action::none && action_ != Action::fixcom) {
907                 std::cerr << progname() << ": "
908                           << _("Action fixcom is not compatible with the given options\n");
909                 rc = 1;
910             }
911             action = true;
912             action_ = Action::fixcom;
913         }
914         if (action_ == Action::none) {
915             // if everything else fails, assume print as the default action
916             action_ = Action::print;
917         }
918     }
919     if (!action) {
920         files_.push_back(argv);
921     }
922     return rc;
923 } // Params::nonoption
924 
readFileToBuf(FILE * f,Exiv2::DataBuf & buf)925 static int readFileToBuf(FILE* f,Exiv2::DataBuf& buf)
926 {
927     const int buff_size = 4*1028;
928     Exiv2::byte* bytes  = (Exiv2::byte*)::malloc(buff_size);
929     int       nBytes    = 0 ;
930     bool      more      = bytes != NULL;
931     while   ( more ) {
932         char buff[buff_size];
933         int  n     = (int) fread(buff,1,buff_size,f);
934         more       = n > 0 ;
935         if ( more ) {
936             bytes      = (Exiv2::byte*) realloc(bytes,nBytes+n);
937             memcpy(bytes+nBytes,buff,n);
938             nBytes    += n ;
939         }
940     }
941 
942     if ( nBytes ) {
943         buf.alloc(nBytes);
944         memcpy(buf.pData_,(const void*)bytes,nBytes);
945     }
946     if ( bytes != NULL ) ::free(bytes) ;
947     return nBytes;
948 }
949 
950 //#define DEBUG
getStdin(Exiv2::DataBuf & buf)951 void Params::getStdin(Exiv2::DataBuf& buf)
952 {
953     // copy stdin to stdinBuf
954     if ( stdinBuf.size_ == 0 ) {
955 #if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW__) || defined(_MSC_VER)
956         DWORD fdwMode;
957         _setmode(fileno(stdin), O_BINARY);
958         Sleep(300);
959         if ( !GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &fdwMode) ) { // failed: stdin has bytes!
960 #else
961         // http://stackoverflow.com/questions/34479795/make-c-not-wait-for-user-input/34479916#34479916
962         fd_set                readfds;
963         FD_ZERO             (&readfds);
964         FD_SET(STDIN_FILENO, &readfds);
965         struct timeval timeout =  {1,0}; // yes: set timeout seconds,microseconds
966 
967         // if we have something in the pipe, read it
968         if (select(1, &readfds, NULL, NULL, &timeout)) {
969 #endif
970 #ifdef DEBUG
971             std::cerr << "stdin has data" << std::endl;
972 #endif
973             readFileToBuf(stdin,stdinBuf);
974         }
975 #ifdef DEBUG
976         // this is only used to simulate reading from stdin when debugging
977         // to simulate exiv2 -pX foo.jpg                | exiv2 -iXX- bar.jpg
978         //             exiv2 -pX foo.jpg > ~/temp/stdin ; exiv2 -iXX- bar.jpg
979         if ( stdinBuf.size_ == 0 ) {
980             const char* path = "/Users/rmills/temp/stdin";
981             FILE* f = fopen(path,"rb");
982             if  ( f ) {
983                 readFileToBuf(f,stdinBuf);
984                 fclose(f);
985                 std::cerr << "read stdin from " << path << std::endl;
986             }
987         }
988 #endif
989 #ifdef DEBUG
990             std::cerr << "getStdin stdinBuf.size_ = " << stdinBuf.size_ << std::endl;
991 #endif
992     }
993 
994     // copy stdinBuf to buf
995     if ( stdinBuf.size_ ) {
996         buf.alloc(stdinBuf.size_);
997         memcpy(buf.pData_,stdinBuf.pData_,buf.size_);
998     }
999 #ifdef DEBUG
1000     std::cerr << "getStdin stdinBuf.size_ = " << stdinBuf.size_ << std::endl;
1001 #endif
1002 
1003 } // Params::getStdin()
1004 
1005 typedef std::map<std::string,std::string> long_t;
1006 
1007 int Params::getopt(int argc, char* const Argv[])
1008 {
1009     char** argv = new char* [argc+1];
1010     argv[argc] = NULL;
1011     long_t longs;
1012 
1013     longs["--adjust"   ] = "-a";
1014     longs["--binary"   ] = "-b";
1015     longs["--comment"  ] = "-c";
1016     longs["--delete"   ] = "-d";
1017     longs["--days"     ] = "-D";
1018     longs["--force"    ] = "-f";
1019     longs["--Force"    ] = "-F";
1020     longs["--grep"     ] = "-g";
1021     longs["--help"     ] = "-h";
1022     longs["--insert"   ] = "-i";
1023     longs["--keep"     ] = "-k";
1024     longs["--key"      ] = "-K";
1025     longs["--location" ] = "-l";
1026     longs["--modify"   ] = "-m";
1027     longs["--Modify"   ] = "-M";
1028     longs["--encode"   ] = "-n";
1029     longs["--months"   ] = "-O";
1030     longs["--print"    ] = "-p";
1031     longs["--Print"    ] = "-P";
1032     longs["--quiet"    ] = "-q";
1033     longs["--log"      ] = "-Q";
1034     longs["--rename"   ] = "-r";
1035     longs["--suffix"   ] = "-S";
1036     longs["--timestamp"] = "-t";
1037     longs["--Timestamp"] = "-T";
1038     longs["--unknown"  ] = "-u";
1039     longs["--verbose"  ] = "-v";
1040     longs["--Version"  ] = "-V";
1041     longs["--version"  ] = "-V";
1042     longs["--years"    ] = "-Y";
1043 
1044     for ( int i = 0 ; i < argc ; i++ ) {
1045         std::string* arg = new std::string(Argv[i]);
1046         if (longs.find(*arg) != longs.end() ) {
1047             argv[i] = ::strdup(longs[*arg].c_str());
1048         } else {
1049             argv[i] = ::strdup(Argv[i]);
1050         }
1051         delete arg;
1052     }
1053 
1054     int rc = Util::Getopt::getopt(argc, argv, optstring_);
1055     // Further consistency checks
1056     if (help_ || version_) {
1057         goto cleanup;
1058     }
1059     if (action_ == Action::none) {
1060         // This shouldn't happen since print is taken as default action
1061         std::cerr << progname() << ": " << _("An action must be specified\n");
1062         rc = 1;
1063     }
1064     if (   action_ == Action::adjust
1065         && !adjust_
1066         && !yodAdjust_[yodYear].flag_
1067         && !yodAdjust_[yodMonth].flag_
1068         && !yodAdjust_[yodDay].flag_) {
1069         std::cerr << progname() << ": "
1070                   << _("Adjust action requires at least one -a, -Y, -O or -D option\n");
1071         rc = 1;
1072     }
1073     if (   action_ == Action::modify
1074         && cmdFiles_.empty() && cmdLines_.empty() && jpegComment_.empty()) {
1075         std::cerr << progname() << ": "
1076                   << _("Modify action requires at least one -c, -m or -M option\n");
1077         rc = 1;
1078     }
1079     if (0 == files_.size()) {
1080         std::cerr << progname() << ": " << _("At least one file is required\n");
1081         rc = 1;
1082     }
1083     if (rc == 0 && !cmdFiles_.empty()) {
1084         // Parse command files
1085         if (!parseCmdFiles(modifyCmds_, cmdFiles_)) {
1086             std::cerr << progname() << ": " << _("Error parsing -m option arguments\n");
1087             rc = 1;
1088         }
1089     }
1090     if (rc == 0 && !cmdLines_.empty()) {
1091         // Parse command lines
1092         if (!parseCmdLines(modifyCmds_, cmdLines_)) {
1093             std::cerr << progname() << ": " << _("Error parsing -M option arguments\n");
1094             rc = 1;
1095         }
1096     }
1097     if (rc == 0 && (!cmdFiles_.empty() || !cmdLines_.empty())) {
1098         // We'll set them again, after reading the file
1099         Exiv2::XmpProperties::unregisterNs();
1100     }
1101     if (   !directory_.empty()
1102         && !(action_ == Action::insert || action_ == Action::extract)) {
1103         std::cerr << progname() << ": "
1104                   << _("-l option can only be used with extract or insert actions\n");
1105         rc = 1;
1106     }
1107     if (!suffix_.empty() && !(action_ == Action::insert)) {
1108         std::cerr << progname() << ": "
1109                   << _("-S option can only be used with insert action\n");
1110         rc = 1;
1111     }
1112     if (timestamp_ && !(action_ == Action::rename)) {
1113         std::cerr << progname() << ": "
1114                   << _("-t option can only be used with rename action\n");
1115         rc = 1;
1116     }
1117     if (timestampOnly_ && !(action_ == Action::rename)) {
1118         std::cerr << progname() << ": "
1119                   << _("-T option can only be used with rename action\n");
1120         rc = 1;
1121     }
1122 
1123  cleanup:
1124     // cleanup the argument vector
1125     for ( int i = 0 ; i < argc ; i++ ) ::free((void*)argv[i]);
1126     delete [] argv;
1127 
1128     return rc;
1129 } // Params::getopt
1130 
1131 // *****************************************************************************
1132 // local implementations
1133 namespace {
1134 
1135     bool parseTime(const std::string& ts, long& time)
1136     {
1137         std::string hstr, mstr, sstr;
1138         char *cts = new char[ts.length() + 1];
1139         strcpy(cts, ts.c_str());
1140         char *tmp = ::strtok(cts, ":");
1141         if (tmp) hstr = tmp;
1142         tmp = ::strtok(0, ":");
1143         if (tmp) mstr = tmp;
1144         tmp = ::strtok(0, ":");
1145         if (tmp) sstr = tmp;
1146         delete[] cts;
1147 
1148         int sign = 1;
1149         long hh(0), mm(0), ss(0);
1150         // [-]HH part
1151         if (!Util::strtol(hstr.c_str(), hh)) return false;
1152         if (hh < 0) {
1153             sign = -1;
1154             hh *= -1;
1155         }
1156         // check for the -0 special case
1157         if (hh == 0 && hstr.find('-') != std::string::npos) sign = -1;
1158         // MM part, if there is one
1159         if (mstr != "") {
1160             if (!Util::strtol(mstr.c_str(), mm)) return false;
1161             if (mm > 59) return false;
1162             if (mm < 0) return false;
1163         }
1164         // SS part, if there is one
1165         if (sstr != "") {
1166             if (!Util::strtol(sstr.c_str(), ss)) return false;
1167             if (ss > 59) return false;
1168             if (ss < 0) return false;
1169         }
1170 
1171         time = sign * (hh * 3600 + mm * 60 + ss);
1172         return true;
1173     } // parseTime
1174 
1175     void printUnrecognizedArgument(const char argc, const std::string& action)
1176     {
1177         std::cerr << Params::instance().progname() << ": " << _("Unrecognized ")
1178                   << action << " " << _("target") << " `"  << argc << "'\n";
1179     }
1180 
1181     int parseCommonTargets(const std::string& optarg, const std::string& action)
1182     {
1183         int rc = 0;
1184         int target = 0;
1185         int all = Params::ctExif | Params::ctIptc | Params::ctComment | Params::ctXmp;
1186         int extra = Params::ctXmpSidecar | Params::ctExif | Params::ctIptc | Params::ctXmp;
1187         for (size_t i = 0; rc == 0 && i < optarg.size(); ++i) {
1188             switch (optarg[i]) {
1189                 case 'e':
1190                     target |= Params::ctExif;
1191                     break;
1192                 case 'i':
1193                     target |= Params::ctIptc;
1194                     break;
1195                 case 'x':
1196                     target |= Params::ctXmp;
1197                     break;
1198                 case 'c':
1199                     target |= Params::ctComment;
1200                     break;
1201                 case 't':
1202                     target |= Params::ctThumb;
1203                     break;
1204                 case 'C':
1205                     target |= Params::ctIccProfile;
1206                     break;
1207                 case 'I':
1208                     target |= Params::ctIptcRaw;
1209                     break;
1210                 case '-':
1211                     target |= Params::ctStdInOut;
1212                     break;
1213                 case 'a':
1214                     target |= all;
1215                     break;
1216                 case 'X':
1217                     target |= extra;  // -eX
1218                     if (i > 0) {      // -eXX or -iXX
1219                         target |= Params::ctXmpRaw;
1220                         target &= ~extra;  // turn off those bits
1221                     }
1222                     break;
1223 
1224                 case 'p': {
1225                     if (strcmp(action.c_str(), "extract") == 0) {
1226                         i += (size_t)parsePreviewNumbers(Params::instance().previewNumbers_, optarg, (int)i + 1);
1227                         target |= Params::ctPreview;
1228                         break;
1229                     }
1230                     printUnrecognizedArgument(optarg[i], action);
1231                     rc = -1;
1232                     break;
1233                 }
1234                 default:
1235                     printUnrecognizedArgument(optarg[i], action);
1236                     rc = -1;
1237                     break;
1238             }
1239         }
1240         return rc ? rc : target;
1241     }
1242 
1243     int parsePreviewNumbers(Params::PreviewNumbers& previewNumbers,
1244                             const std::string& optarg,
1245                             int j)
1246     {
1247         size_t k = j;
1248         for (size_t i = j; i < optarg.size(); ++i) {
1249             std::ostringstream os;
1250             for (k = i; k < optarg.size() && isdigit(optarg[k]); ++k) {
1251                 os << optarg[k];
1252             }
1253             if (k > i) {
1254                 bool ok = false;
1255                 int num = Exiv2::stringTo<int>(os.str(), ok);
1256                 if (ok && num >= 0) {
1257                     previewNumbers.insert(num);
1258                 }
1259                 else {
1260                     std::cerr << Params::instance().progname() << ": "
1261                               << _("Invalid preview number") << ": " << num << "\n";
1262                 }
1263                 i = k;
1264             }
1265             if (!(k < optarg.size() && optarg[i] == ',')) break;
1266         }
1267         int ret = static_cast<int>(k - j);
1268         if (ret == 0) {
1269             previewNumbers.insert(0);
1270         }
1271 #ifdef DEBUG
1272         std::cout << "\nThe set now contains: ";
1273         for (Params::PreviewNumbers::const_iterator i = previewNumbers.begin();
1274              i != previewNumbers.end();
1275              ++i) {
1276             std::cout << *i << ", ";
1277         }
1278         std::cout << std::endl;
1279 #endif
1280         return (int) (k - j);
1281     } // parsePreviewNumbers
1282 
1283     bool parseCmdFiles(ModifyCmds& modifyCmds,
1284                        const Params::CmdFiles& cmdFiles)
1285     {
1286         Params::CmdFiles::const_iterator end = cmdFiles.end();
1287         Params::CmdFiles::const_iterator filename = cmdFiles.begin();
1288         for ( ; filename != end; ++filename) {
1289             try {
1290                 std::ifstream file(filename->c_str());
1291                 bool bStdin = filename->compare("-")== 0;
1292                 if (!file && !bStdin) {
1293                     std::cerr << *filename << ": "
1294                               << _("Failed to open command file for reading\n");
1295                     return false;
1296                 }
1297                 int num = 0;
1298                 std::string line;
1299                 while (bStdin?std::getline(std::cin, line):std::getline(file, line)) {
1300                     ModifyCmd modifyCmd;
1301                     if (parseLine(modifyCmd, line, ++num)) {
1302                         modifyCmds.push_back(modifyCmd);
1303                     }
1304                 }
1305             }
1306             catch (const Exiv2::AnyError& error) {
1307                 std::cerr << *filename << ", " << _("line") << " " << error << "\n";
1308                 return false;
1309             }
1310         }
1311         return true;
1312     } // parseCmdFile
1313 
1314     bool parseCmdLines(ModifyCmds& modifyCmds,
1315                        const Params::CmdLines& cmdLines)
1316     {
1317         try {
1318             int num = 0;
1319             Params::CmdLines::const_iterator end = cmdLines.end();
1320             Params::CmdLines::const_iterator line = cmdLines.begin();
1321             for ( ; line != end; ++line) {
1322                 ModifyCmd modifyCmd;
1323                 if (parseLine(modifyCmd, *line, ++num)) {
1324                     modifyCmds.push_back(modifyCmd);
1325                 }
1326             }
1327             return true;
1328         }
1329         catch (const Exiv2::AnyError& error) {
1330             std::cerr << _("-M option") << " " << error << "\n";
1331             return false;
1332         }
1333     } // parseCmdLines
1334 
1335 #if defined(_MSC_VER) || defined(__MINGW__)
1336     static std::string formatArg(const char* arg)
1337     {
1338         std::string result = "";
1339         char        b  = ' ' ;
1340         char        e  = '\\'; std::string E = std::string("\\");
1341         char        q  = '\''; std::string Q = std::string("'" );
1342         bool        qt = false;
1343         char* a    = (char*) arg;
1344         while  ( *a ) {
1345             if ( *a == b || *a == e || *a == q ) qt = true;
1346             if ( *a == q ) result += E;
1347             if ( *a == e ) result += E;
1348             result += std::string(a,1);
1349             a++ ;
1350         }
1351         if (qt) result = Q + result + Q;
1352 
1353         return result;
1354     }
1355 #endif
1356 
1357     bool parseLine(ModifyCmd& modifyCmd, const std::string& line, int num)
1358     {
1359         const std::string delim = " \t";
1360 
1361         // Skip empty lines and comments
1362         std::string::size_type cmdStart = line.find_first_not_of(delim);
1363         if (cmdStart == std::string::npos || line[cmdStart] == '#') return false;
1364 
1365         // Get command and key
1366         std::string::size_type cmdEnd = line.find_first_of(delim, cmdStart+1);
1367         std::string::size_type keyStart = line.find_first_not_of(delim, cmdEnd+1);
1368         std::string::size_type keyEnd = line.find_first_of(delim, keyStart+1);
1369         if (   cmdStart == std::string::npos
1370             || cmdEnd == std::string::npos
1371             || keyStart == std::string::npos) {
1372             std::string cmdLine ;
1373 #if defined(_MSC_VER) || defined(__MINGW__)
1374             for ( int i = 1 ; i < __argc ; i++ ) { cmdLine += std::string(" ") + formatArg(__argv[i]) ; }
1375 #endif
1376             throw Exiv2::Error(Exiv2::kerErrorMessage, Exiv2::toString(num)
1377                                + ": " + _("Invalid command line:") + cmdLine);
1378         }
1379 
1380         std::string cmd(line.substr(cmdStart, cmdEnd-cmdStart));
1381         CmdId cmdId = commandId(cmd);
1382         if (cmdId == invalidCmdId) {
1383             throw Exiv2::Error(Exiv2::kerErrorMessage, Exiv2::toString(num)
1384                                + ": " + _("Invalid command") + " `" + cmd + "'");
1385         }
1386 
1387         Exiv2::TypeId defaultType = Exiv2::invalidTypeId;
1388         std::string key(line.substr(keyStart, keyEnd-keyStart));
1389         MetadataId metadataId = invalidMetadataId;
1390         if (cmdId != reg) {
1391             try {
1392                 Exiv2::IptcKey iptcKey(key);
1393                 metadataId = iptc;
1394                 defaultType = Exiv2::IptcDataSets::dataSetType(iptcKey.tag(),
1395                                                                iptcKey.record());
1396             }
1397             catch (const Exiv2::AnyError&) {}
1398             if (metadataId == invalidMetadataId) {
1399                 try {
1400                     Exiv2::ExifKey exifKey(key);
1401                     metadataId = exif;
1402                     defaultType = exifKey.defaultTypeId();
1403                 }
1404                 catch (const Exiv2::AnyError&) {}
1405             }
1406             if (metadataId == invalidMetadataId) {
1407                 try {
1408                     Exiv2::XmpKey xmpKey(key);
1409                     metadataId = xmp;
1410                     defaultType = Exiv2::XmpProperties::propertyType(xmpKey);
1411                 }
1412                 catch (const Exiv2::AnyError&) {}
1413             }
1414             if (metadataId == invalidMetadataId) {
1415                 throw Exiv2::Error(Exiv2::kerErrorMessage, Exiv2::toString(num)
1416                                    + ": " + _("Invalid key") + " `" + key + "'");
1417             }
1418         }
1419         std::string value;
1420         Exiv2::TypeId type = defaultType;
1421         bool explicitType = false;
1422         if (cmdId != del) {
1423             // Get type and value
1424             std::string::size_type typeStart = std::string::npos;
1425             if (keyEnd != std::string::npos) typeStart = line.find_first_not_of(delim, keyEnd+1);
1426             std::string::size_type typeEnd = std::string::npos;
1427             if (typeStart != std::string::npos) typeEnd = line.find_first_of(delim, typeStart+1);
1428             std::string::size_type valStart = typeStart;
1429             std::string::size_type valEnd = std::string::npos;
1430             if (valStart != std::string::npos) valEnd = line.find_last_not_of(delim);
1431 
1432             if (   cmdId == reg
1433                 && (   keyEnd == std::string::npos
1434                     || valStart == std::string::npos)) {
1435                 throw Exiv2::Error(Exiv2::kerErrorMessage, Exiv2::toString(num)
1436                                    + ": " + _("Invalid command line") + " " );
1437             }
1438 
1439             if (   cmdId != reg
1440                 && typeStart != std::string::npos
1441                 && typeEnd != std::string::npos) {
1442                 std::string typeStr(line.substr(typeStart, typeEnd-typeStart));
1443                 Exiv2::TypeId tmpType = Exiv2::TypeInfo::typeId(typeStr);
1444                 if (tmpType != Exiv2::invalidTypeId) {
1445                     valStart = line.find_first_not_of(delim, typeEnd+1);
1446                     if (valStart == std::string::npos) {
1447                         throw Exiv2::Error(Exiv2::kerErrorMessage, Exiv2::toString(num)
1448                                            + ": " + _("Invalid command line") + " " );
1449                     }
1450                     type = tmpType;
1451                     explicitType = true;
1452                 }
1453             }
1454 
1455             if (valStart != std::string::npos) {
1456                 value = parseEscapes(line.substr(valStart, valEnd+1-valStart));
1457                 std::string::size_type last = value.length()-1;
1458                 if (   (value.at(0) == '"' && value.at(last) == '"')
1459                        || (value.at(0) == '\'' && value.at(last) == '\'')) {
1460                     value = value.substr(1, value.length()-2);
1461                 }
1462             }
1463         }
1464 
1465         modifyCmd.cmdId_ = cmdId;
1466         modifyCmd.key_ = key;
1467         modifyCmd.metadataId_ = metadataId;
1468         modifyCmd.typeId_ = type;
1469         modifyCmd.explicitType_ = explicitType;
1470         modifyCmd.value_ = value;
1471 
1472         if (cmdId == reg) {
1473             if (value.empty()) {
1474                 throw Exiv2::Error(Exiv2::kerErrorMessage,
1475                                    Exiv2::toString(num) + ": " + _("Empty value for key") +  + " `" + key + "'");
1476             }
1477 
1478             // Registration needs to be done immediately as the new namespaces are
1479             // looked up during parsing of subsequent lines (to validate XMP keys).
1480             Exiv2::XmpProperties::registerNs(modifyCmd.value_, modifyCmd.key_);
1481         }
1482 
1483         return true;
1484     } // parseLine
1485 
1486     CmdId commandId(const std::string& cmdString)
1487     {
1488         int i = 0;
1489         for (;   cmdIdAndString[i].cmdId_ != invalidCmdId
1490                  && cmdIdAndString[i].cmdString_ != cmdString; ++i) {}
1491         return cmdIdAndString[i].cmdId_;
1492     }
1493 
1494     std::string parseEscapes(const std::string& input)
1495     {
1496         std::string result = "";
1497         for (size_t i = 0; i < input.length(); ++i) {
1498             char ch = input[i];
1499             if (ch != '\\') {
1500                 result.push_back(ch);
1501                 continue;
1502             }
1503             size_t escapeStart = i;
1504             if (!(input.length() - 1 > i)) {
1505                 result.push_back(ch);
1506                 continue;
1507             }
1508             ++i;
1509             ch = input[i];
1510             switch (ch) {
1511             case '\\':                          // Escaping of backslash
1512                 result.push_back('\\');
1513                 break;
1514             case 'r':                           // Escaping of carriage return
1515                 result.push_back('\r');
1516                 break;
1517             case 'n':                           // Escaping of newline
1518                 result.push_back('\n');
1519                 break;
1520             case 't':                           // Escaping of tab
1521                 result.push_back('\t');
1522                 break;
1523             case 'u':                           // Escaping of unicode
1524                 if (input.length() >= 4 && input.length() - 4 > i) {
1525                     int acc = 0;
1526                     for (int j = 0; j < 4; ++j) {
1527                         ++i;
1528                         acc <<= 4;
1529                         if (input[i] >= '0' && input[i] <= '9') {
1530                             acc |= input[i] - '0';
1531                         }
1532                         else if (input[i] >= 'a' && input[i] <= 'f') {
1533                             acc |= input[i] - 'a' + 10;
1534                         }
1535                         else if (input[i] >= 'A' && input[i] <= 'F') {
1536                             acc |= input[i] - 'A' + 10;
1537                         }
1538                         else {
1539                             acc = -1;
1540                             break;
1541                         }
1542                     }
1543                     if (acc == -1) {
1544                         result.push_back('\\');
1545                         i = escapeStart;
1546                         break;
1547                     }
1548 
1549                     std::string ucs2toUtf8 = "";
1550                     ucs2toUtf8.push_back((char) ((acc & 0xff00) >> 8));
1551                     ucs2toUtf8.push_back((char) (acc & 0x00ff));
1552 
1553                     if (Exiv2::convertStringCharset (ucs2toUtf8, "UCS-2BE", "UTF-8")) {
1554                         result.append (ucs2toUtf8);
1555                     }
1556                 }
1557                 else {
1558                     result.push_back('\\');
1559                     result.push_back(ch);
1560                 }
1561                 break;
1562             default:
1563                 result.push_back('\\');
1564                 result.push_back(ch);
1565             }
1566         }
1567         return result;
1568     }
1569 
1570 }
1571 
1572