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