| 1 | #include "cmdparser.hpp"
|
| 2 | #include <cstring>
|
| 3 |
|
| 4 | namespace fs = boost::filesystem;
|
| 5 |
|
| 6 | using namespace std;
|
| 7 |
|
| 8 |
|
| 9 | Configuration::Configuration() :
|
| 10 | input_file(),
|
| 11 | output_dir(),
|
| 12 | unbitstuff(false),
|
| 13 | unbitstuff_char('>'),
|
| 14 | unbitstuff_targets(">"),
|
| 15 | trailing_space(true)
|
| 16 | {}
|
| 17 |
|
| 18 |
|
| 19 | Cmdline_parser::Cmdline_parser() {}
|
| 20 |
|
| 21 |
|
| 22 | Configuration*
|
| 23 | Cmdline_parser::parse(const int argc, const char* argv[]) {
|
| 24 |
|
| 25 | static const struct poptOption options[] =
|
| 26 | {
|
| 27 | {"unbitstuff-char", 'c', POPT_ARG_STRING1U, NULL__null, 'c'},
|
| 28 | {"output-dir", 'd', POPT_ARG_STRING1U, NULL__null, 'd'},
|
| 29 | {"input-file", 'f', POPT_ARG_STRING1U, NULL__null, 'f'},
|
| 30 | {"help", 'h', POPT_ARG_NONE0U, NULL__null, 'h'},
|
| 31 | {"notrailing-space", 's', POPT_ARG_NONE0U, NULL__null, 's'},
|
| 32 | {"unbitstuff-targets", 't', POPT_ARG_STRING1U, NULL__null, 't'},
|
| 33 | {"unbitstuff", 'u', POPT_ARG_NONE0U, NULL__null, 'u'},
|
| 34 | {NULL__null, 0, 0, NULL__null, 0}
|
| 35 | };
|
| 36 |
|
| 37 |
|
| 38 | const char** alias_tb_argv = new const char*[6];
|
| 39 | alias_tb_argv[0] = "-u";
|
| 40 | alias_tb_argv[1] = "-s";
|
| 41 | alias_tb_argv[2] = "-c";
|
| 42 | alias_tb_argv[3] = " ";
|
| 43 | alias_tb_argv[4] = "-t";
|
| 44 | alias_tb_argv[5] = " >";
|
| 45 |
|
| 46 | static const struct poptAlias alias_tb =
|
| 47 | {"thunderbird", '\0', 6, alias_tb_argv};
|
| 48 |
|
| 49 | poptContext context = poptGetContext("mbox2eml", argc, argv, options, 0);
|
| 50 |
|
| 51 | int c = poptAddAlias(context, alias_tb, 0);
|
| Value stored to 'c' during its initialization is never read |
| 52 |
|
| 53 | Configuration* config = new Configuration();
|
| 54 |
|
| 55 |
|
| 56 | while((c = poptGetNextOpt(context)) > 0) {
|
| 57 | const char *optarg = poptGetOptArg(context);
|
| 58 | switch(c) {
|
| 59 | case 'c':
|
| 60 | if(strlen(optarg) != 1) {
|
| 61 | cerr << "-c: argument must be exactly ONE character!" << endl;
|
| 62 | exit(1);
|
| 63 | }
|
| 64 | config->unbitstuff_char = optarg[0];
|
| 65 | #ifdef CMDDEBUG
|
| 66 | cout << "option: " << (char)c << " argument: " << optarg << endl;
|
| 67 | #endif //CMDDEBUG
|
| 68 | break;
|
| 69 | case 'd':
|
| 70 | config->output_dir = check_dir(optarg);
|
| 71 | #ifdef CMDDEBUG
|
| 72 | cout << "option: " << (char)c << " argument: " << optarg << endl;
|
| 73 | #endif //CMDDEBUG
|
| 74 | break;
|
| 75 | case 'f':
|
| 76 | config->input_file = check_file(optarg);
|
| 77 | #ifdef CMDDEBUG
|
| 78 | cout << "option: " << (char)c << " argument: " << optarg << endl;
|
| 79 | #endif //CMDDEBUG
|
| 80 | break;
|
| 81 | case 'h':
|
| 82 | #ifdef CMDDEBUG
|
| 83 | cout << "option: " << (char)c << endl;
|
| 84 | #endif //CMDDEBUG
|
| 85 | cout << usage() << endl;
|
| 86 | exit(0);
|
| 87 | case 's':
|
| 88 | config->trailing_space = false;
|
| 89 | #ifdef CMDDEBUG
|
| 90 | cout << "option: " << (char)c << endl;
|
| 91 | #endif //CMDDEBUG
|
| 92 | break;
|
| 93 | case 't':
|
| 94 | config->unbitstuff_targets = optarg;
|
| 95 | #ifdef CMDDEBUG
|
| 96 | cout << "option: " << (char)c << " argument: " << optarg << endl;
|
| 97 | #endif //CMDDEBUG
|
| 98 | break;
|
| 99 | case 'u':
|
| 100 | config->unbitstuff = true;
|
| 101 | #ifdef CMDDEBUG
|
| 102 | cout << "option: " << (char)c << endl;
|
| 103 | #endif //CMDDEBUG
|
| 104 | break;
|
| 105 | default:
|
| 106 | cerr << "unknown option: " << (char)c << endl;
|
| 107 | exit(1);
|
| 108 | }
|
| 109 | }
|
| 110 |
|
| 111 |
|
| 112 | if(c < -1) {
|
| 113 | cerr << poptBadOption(context, 0) << ": " << poptStrerror(c) << endl;
|
| 114 | }
|
| 115 |
|
| 116 |
|
| 117 | if(poptPeekArg(context) != NULL__null) {
|
| 118 | cerr << "No arguments without option switch allowed:" << endl;
|
| 119 | const char *xtra_arg;
|
| 120 | while((xtra_arg = poptGetArg(context)) != NULL__null) {
|
| 121 | cerr << xtra_arg << endl;
|
| 122 | }
|
| 123 | }
|
| 124 |
|
| 125 | poptFreeContext(context);
|
| 126 |
|
| 127 |
|
| 128 | if(!check_necessary_options_set(config)) {
|
| 129 | cerr << usage(true);
|
| 130 | exit(1);
|
| 131 | }
|
| 132 |
|
| 133 | return config;
|
| 134 | }
|
| 135 |
|
| 136 |
|
| 137 | bool
|
| 138 | Cmdline_parser::check_necessary_options_set(Configuration* config) {
|
| 139 | bool result = true;
|
| 140 |
|
| 141 | if(config->input_file.empty()) {
|
| 142 | cerr << "You must specify an input-file!" << endl;
|
| 143 | result = false;
|
| 144 | }
|
| 145 |
|
| 146 | if(config->output_dir.empty()) {
|
| 147 | cerr << "You must specify an output-directory!" << endl;
|
| 148 | result = false;
|
| 149 | }
|
| 150 |
|
| 151 | return result;
|
| 152 | }
|
| 153 |
|
| 154 |
|
| 155 |
|
| 156 |
|
| 157 |
|
| 158 |
|
| 159 |
|
| 160 |
|
| 161 |
|
| 162 |
|
| 163 |
|
| 164 | fs::path
|
| 165 | Cmdline_parser::check_dir(const char* dir_str) {
|
| 166 |
|
| 167 | fs::path full_path;
|
| 168 | try {
|
| 169 | fs::path dir_path(dir_str);
|
| 170 | full_path = fs::system_complete(dir_path);
|
| 171 | }
|
| 172 | catch(...) {
|
| 173 | cerr << "Directory name is not valid: " << dir_str << endl;
|
| 174 | exit(1);
|
| 175 | }
|
| 176 |
|
| 177 | bool exists = false;
|
| 178 | try {
|
| 179 | exists = fs::exists(full_path);
|
| 180 | }
|
| 181 | catch(const fs::filesystem_error& ex) {
|
| 182 | cerr << "Could not check if dir exists: " << ex.what() << endl;
|
| 183 | exit(1);
|
| 184 | }
|
| 185 | if(!exists) {
|
| 186 | try {
|
| 187 | exists = fs::create_directory(full_path);
|
| 188 | }
|
| 189 | catch(...) {}
|
| 190 | if(!exists) {
|
| 191 | cerr << "Could not create directory: " << full_path << endl;
|
| 192 | exit(1);
|
| 193 | }
|
| 194 | }
|
| 195 |
|
| 196 | if(!fs::is_directory(full_path)) {
|
| 197 | cerr << "This is a file and not a directory: " << full_path << endl;
|
| 198 | exit(1);
|
| 199 | }
|
| 200 |
|
| 201 | return full_path;
|
| 202 | }
|
| 203 |
|
| 204 |
|
| 205 |
|
| 206 |
|
| 207 |
|
| 208 |
|
| 209 | fs::path
|
| 210 | Cmdline_parser::check_file(const char* file_str) {
|
| 211 |
|
| 212 | fs::path full_path;
|
| 213 | try {
|
| 214 | fs::path file_path(file_str);
|
| 215 | full_path = fs::system_complete(file_path);
|
| 216 | }
|
| 217 | catch(...) {
|
| 218 | cerr << "File name is not valid: " << file_str << endl;
|
| 219 | exit(1);
|
| 220 | }
|
| 221 |
|
| 222 | try {
|
| 223 | if (!fs::exists(full_path)) {
|
| 224 | cerr << "File does not exist: " << full_path << endl;
|
| 225 | exit(1);
|
| 226 | }
|
| 227 | }
|
| 228 | catch(const fs::filesystem_error& ex) {
|
| 229 | cerr << "Could not check if file exists: " << ex.what() << endl;
|
| 230 | exit(1);
|
| 231 | }
|
| 232 |
|
| 233 | if(fs::is_directory(full_path)) {
|
| 234 | cerr << "This is a directory and not a file: " << full_path << endl;
|
| 235 | exit(1);
|
| 236 | }
|
| 237 |
|
| 238 | return full_path;
|
| 239 | }
|
| 240 |
|
| 241 |
|
| 242 |
|
| 243 |
|
| 244 |
|
| 245 | string
|
| 246 | Cmdline_parser::usage(bool oneliner) {
|
| 247 | ostringstream os;
|
| 248 |
|
| 249 | os << "Usage: mbox2eml [options] -f MBOX-FILE -d DEST-DIR\n";
|
| 250 | if(oneliner) {
|
| 251 | return os.str();
|
| 252 | }
|
| 253 |
|
| 254 | os << "Obligatory Options:\n"
|
| 255 | << " --output-dir | -d :\n"
|
| 256 | << " This is the output directory, where the eml-files are being saved.\n"
|
| 257 | << " --input-file | -f :\n"
|
| 258 | << " This is the input file which shall be split into eml-files.\n"
|
| 259 | << " This file MUST be in mbox format as described here:\n"
|
| 260 | << " http://www.die.net/doc/linux/man/man5/mbox.5.html\n"
|
| 261 | << "Optional Options:\n"
|
| 262 | << " --help | -h :\n"
|
| 263 | << " Displays this text.\n"
|
| 264 | << "Advanced Options:\n"
|
| 265 | << "THESE OPTIONS ARE VERY DANGEROUS!!!\n"
|
| 266 | << "PLEASE READ THE README-FILE VERY CAREFULLY.\n"
|
| 267 | << " --unbitstuff | -u :\n"
|
| 268 | << " Turns on the 'unbitstuff' functionality.\n"
|
| 269 | << " --unbitstuff-char | -c :\n"
|
| 270 | << " This is the character, which will be removed when 'unbitstuff'ing.\n"
|
| 271 | << " Default: '>'\n"
|
| 272 | << " --notrailing-space | -s :\n"
|
| 273 | << " This option triggers if a trailing space after 'From' is necessary.\n"
|
| 274 | << " Default: trailing space is necessary\n"
|
| 275 | << " --unbitstuff-targets | -t :\n"
|
| 276 | << " These are the characters which may be between the 'unbitstuff-char'\n"
|
| 277 | << " and 'From'. If only these characters are found, the affected line is\n"
|
| 278 | << " 'unbitstuff'ed.\n"
|
| 279 | << " Default: \">\"\n"
|
| 280 | << " --thunderbird:\n"
|
| 281 | << " This is an alias for -u -s -c \" \" -t \" >\"\n"
|
| 282 | << " This option enables the 'unbitstuff'ing for mbox-files produced by\n"
|
| 283 | << " Mozilla Thunderbird.\n"
|
| 284 | << " Thundebird violates the common mbox practice to bitstuff every line\n"
|
| 285 | << " starting with '[>]*From ' with a single '>'. Instead of this,\n"
|
| 286 | << " Thunderbird bitstuffs every line starting with '[ >]*From' with a\n"
|
| 287 | << " whitespace.\n"
|
| 288 | << " Using this option may corrupt digitally signed (i.e. S/MIME or PGP/GPG)\n"
|
| 289 | << " mails that have been encrypted using Thunderbird or EnigMail as\n"
|
| 290 | << " Thunderbird is internally working with the 'false' bitstuffed\n"
|
| 291 | << " mbox-format. So, if you try to fix this wrong format, you will modify\n"
|
| 292 | << " the content of the mail but the signature will not reflect the\n"
|
| 293 | << " modifications. And so the signature will be invalid.\n";
|
| 294 |
|
| 295 | return os.str();
|
| 296 | }
|
| 297 |
|