2.06.2011

mogmail – main function

mogmail - メイン処理

・pop3.h

   1: // Copyright (c) 2011 Mog Project. All rights reserved.
   2:  
   3: #ifndef _MOG_MAIL_POP3_H_
   4: #define _MOG_MAIL_POP3_H_
   5: #include <string>
   6: #include <boost/scoped_ptr.hpp>
   7: #pragma warning(disable : 4244)
   8: #include <boost/date_time/posix_time/posix_time.hpp>
   9: #pragma warning(default : 4244)
  10:  
  11: // Forward declaration.
  12: namespace mog {
  13: namespace net { class Connecter; }
  14: namespace mail {
  15:  
  16: // Mail headers struct.
  17: struct MailHeader {
  18:   boost::posix_time::ptime date;  // Universal Time, Coordinated
  19:   std::string from_name;
  20:   std::string from_address;
  21:   std::string subject;
  22: };
  23:  
  24: // A class manages POP3 communication.
  25: class POP3Client {
  26:  public:
  27:   POP3Client(
  28:       std::string const& server, std::string const& port,
  29:       std::string const& username, std::string const& password);
  30:   ~POP3Client();
  31:   MailHeader GetHeader(int index);
  32:   int num_mails() const { return num_mails_; }
  33:  
  34:  private:
  35:   boost::scoped_ptr<net::Connecter> connecter_;
  36:   bool is_active_;
  37:   int num_mails_;
  38:  
  39:   void Disconnect();
  40: };
  41:  
  42: }  // namespace mail
  43: }  // namespace mog
  44: #endif  // _MOG_MAIL_POP3_H_

・pop3.cc

   1: // Copyright (c) 2011 Mog Project. All rights reserved.
   2:  
   3: #include "pop3.h"
   4:  
   5: #include <boost/xpressive/xpressive.hpp>
   6: #include <boost/foreach.hpp>
   7: #include <boost/system/system_error.hpp>
   8: #include <boost/algorithm/string.hpp>
   9:  
  10: #include "base64.h"
  11: #include "character_converter.h"
  12: #include "connecter.h"
  13:  
  14: namespace xp = boost::xpressive;
  15:  
  16: namespace mog {
  17: namespace mail {
  18:  
  19: // Decoding MIME.
  20: std::string DecodeString(std::string str) {
  21:   xp::sregex const regex = "=?"
  22:       >> (xp::s1=+((xp::set='=', '+', '-', '/')|xp::alpha|xp::digit)) >> '?'
  23:       >> (xp::s2=+((xp::set='=', '+', '-', '/')|xp::alpha|xp::digit)) >> '?'
  24:       >> (xp::s3=+((xp::set='=', '+', '-', '/')|xp::alpha|xp::digit)) >> "?=";
  25:   xp::smatch match;
  26:   while (xp::regex_search(str, match, regex)) {
  27:     // Supportecd only ISO-2022-JP and Base64
  28:     if (!boost::iequals(std::string("ISO-2022-JP"), match[1].str())) { throw std::runtime_error("Character-set is supported only ISO-2022-JP."); }
  29:     if (!boost::iequals(std::string("B"), match[2].str())) { throw std::runtime_error("Encoding method is supported only BASE64."); }
  30:     str = match.prefix() +
  31:         mog::util::CharacterConverter::JIStoSJIS(mog::util::Base64::Decode(match[3])) +
  32:         match.suffix();
  33:   }
  34:   return str;
  35: }
  36:  
  37: ////////////////////////////////////////////////////////////////////////////////
  38: // POP3Client
  39: POP3Client::POP3Client(
  40:     std::string const& server, std::string const& port,
  41:     std::string const& username, std::string const& password)
  42:     : is_active_(false), num_mails_(0) {
  43:  
  44:   std::string line;
  45:   xp::sregex const reg_ok = xp::bos >> "+OK " >> (xp::s1=*~xp::_s) >> *xp::_;
  46:   xp::smatch match;
  47:  
  48:   try {
  49:     // Create session.
  50:     connecter_.reset(new ::mog::net::Connecter(server, port));
  51:     connecter_->ReadLine(&line);
  52:     if (!regex_match(line, reg_ok)) { throw std::runtime_error("Failed to create session."); }
  53:  
  54:     connecter_->WriteLine("USER " + username);
  55:     connecter_->ReadLine(&line);
  56:     if (!regex_match(line, reg_ok)) { throw std::runtime_error("Failed to user authentication."); }
  57:  
  58:     connecter_->WriteLine("PASS " + password);
  59:     connecter_->ReadLine(&line);
  60:     if (!regex_match(line, reg_ok)) { throw std::runtime_error("Failed to password authentication."); }
  61:  
  62:     connecter_->WriteLine("STAT");
  63:     connecter_->ReadLine(&line);
  64:     if (!regex_match(line, match, reg_ok)) { throw std::runtime_error("Failed to get status."); }
  65:     num_mails_ = boost::lexical_cast<unsigned int>(match[1]);
  66:   } catch(boost::system::system_error const& e) {
  67:     Disconnect();
  68:     throw e;
  69:   }
  70:  
  71:   is_active_ = true;
  72: }
  73:  
  74: POP3Client::~POP3Client() throw() { Disconnect(); }
  75:  
  76: MailHeader POP3Client::GetHeader(int index) {
  77:   MailHeader mh;
  78:  
  79:   try {
  80:     bool in_data = false;
  81:     std::string sect_title, sect_data;
  82:     connecter_->WriteLine("TOP " + boost::lexical_cast< std::string >(index) + " 0");
  83:  
  84:     for (;;) {
  85:       std::string line;
  86:       xp::smatch match;
  87:  
  88:       connecter_->ReadLine(&line);
  89:  
  90:       if (in_data) {
  91:         if (!line.empty() && std::isspace(line[0])) {
  92:           sect_data.append(line.substr(1));
  93:           continue;  // Data in multi-lines.
  94:         }
  95:         if ("Date" == sect_title) {
  96:           // s1:day(xx), s2:month(English), s3:year(xxxx), s4:hour(xx), s5:minute(xx), s6:second(xx)
  97:           // s7:sign of time zone(+-), s8:time zone hour(xx), s9:time zone minute(xx)
  98:           // Ex. Thu, 13 Jan 2011 17:16:37 +0900
  99:           if (xp::regex_match(sect_data, match, xp::sregex(
 100:               +xp::alpha >> ", " >> (xp::s1=+xp::_d) >> ' ' >> (xp::s2 = +xp::alpha) >> ' ' >> (xp::s3 = +xp::_d) >> ' ' >>
 101:               (xp::s4 = +xp::_d) >> ':' >> (xp::s5 = +xp::_d) >> ':' >> (xp::s6 = +xp::_d) >> ' ' >>
 102:               (xp::s7 = (xp::set = '+', '-')) >> (xp::s8 = xp::repeat<2>(xp::_d)) >> (xp::s9 = xp::repeat<2>(xp::_d)) >> *xp::_))) {
 103:             mh.date = boost::posix_time::time_from_string(
 104:                 match[3] + '-' + match[2] + '-' + match[1] + ' ' +
 105:                 match[4] + ':' + match[5] + ':' + match[6]);
 106:             // Adjust time zone to UTC.
 107:             mh.date -= boost::posix_time::hours(boost::lexical_cast<int>(match[7] + match[8]))
 108:               + boost::posix_time::minutes(boost::lexical_cast<int>(match[7] + match[9]));
 109:           }
 110:         } else if ("Subject" == sect_title) {
 111:           mh.subject = DecodeString(sect_data);
 112:         } else if ("From" == sect_title) {
 113:           if (xp::regex_match(sect_data, match, xp::sregex(
 114:             !xp::as_xpr('"') >> (xp::s1 = *~(xp::set='<', '"')) >> !xp::as_xpr('"') >> *xp::_s
 115:             >> !xp::as_xpr("<">> (xp::s2 = *~xp::as_xpr('>')) >> '>')))) {
 116:             mh.from_name = boost::trim_copy(DecodeString(match[1]));
 117:             mh.from_address = match[2];
 118:           } else {
 119:             mh.from_name = DecodeString(sect_data);
 120:           }
 121:         }
 122:         in_data = false;
 123:       }
 124:  
 125:       if (xp::regex_match(line, match, xp::sregex(xp::bos >> (xp::s1=+~xp::_s) >> ": " >> (xp::s2=*xp::_)))) {
 126:         sect_title = match[1];
 127:         sect_data  = match[2];
 128:         in_data = true;
 129:       } else if ( "." == line ) { break; }
 130:     }
 131:   }
 132:   catch(boost::system::system_error const& e) {
 133:     Disconnect();
 134:     throw e;
 135:   }
 136:   return mh;
 137: }
 138:  
 139: void POP3Client::Disconnect() throw() {
 140:   if (!is_active_) { return; }
 141:  
 142:   std::string line;
 143:   connecter_->WriteLine("QUIT");
 144:   connecter_->ReadLine(&line);
 145:  
 146:   connecter_.reset();
 147:   is_active_ = false;
 148: }
 149:  
 150: }  // namespace mail
 151: }  // namespace mog

・main.cc

   1: // Copyright (c) 2011 Mog Project. All rights reserved.
   2:  
   3: #pragma comment(lib, "cryptlib.lib")
   4:  
   5: #include <fstream>
   6: #include <vector>
   7:  
   8: #include <boost/scoped_ptr.hpp>
   9: #include <boost/foreach.hpp>
  10: #pragma warning(disable: 4512)
  11: #include <boost/program_options.hpp>
  12: #pragma warning(default: 4512)
  13: #include <boost/format.hpp>
  14: #include <boost/filesystem.hpp>
  15: #include <boost/date_time/local_time_adjustor.hpp>
  16: #include <boost/date_time/c_local_time_adjustor.hpp>
  17: #include <boost/xpressive/xpressive.hpp>
  18: #include <boost/mpl/if.hpp>
  19:  
  20: #pragma warning(push)
  21: #pragma warning(disable: 4083 100 127 189 244 505 512 615)
  22: #include <cryptopp/aes.h>
  23: #include <cryptopp/dh.h>
  24: #include <cryptopp/modes.h>
  25: #include <cryptopp/osrng.h>
  26: #pragma warning(pop)
  27:  
  28: #include "pop3.h"
  29: #include "base64.h"
  30:  
  31: using boost::posix_time::ptime;
  32: using CryptoPP::AES;
  33:  
  34: namespace po = boost::program_options;
  35: namespace xp = boost::xpressive;
  36:  
  37: namespace {
  38:  
  39: enum ExitCode { kOK = 0, kErrorSyntax, kErrorSetup, kErrorFetch, kErrorWriteResult, kErrorFatal };
  40: enum ExecutionMode { kModeFetch, kModeUsage, kModeSetup };
  41: struct ProgramOption {
  42:   ProgramOption() : mode(kModeFetch) {}
  43:   ExecutionMode mode;
  44:   int           out_count;
  45:   std::string   out_format;
  46:   std::string   ini_file_path;
  47: };
  48: struct ConnectOption {
  49:   ConnectOption()
  50:       :server_port("110") {}
  51:   std::string server_address;
  52:   std::string server_port;
  53:   std::string username;
  54:   std::string password;
  55: };
  56:  
  57: // Copy string with trailing null characters.
  58: void CopyWholeString(std::string const& src, std::string * dst) {
  59:   std::string::const_iterator it_src = src.begin();
  60:   std::string::iterator it_dst = dst->begin();
  61:   while (it_src != src.end() && it_dst != dst->end()) { *it_dst++ = *it_src++; }
  62: }
  63:  
  64: // AES encrypt and decrypt.
  65: template <bool IS_ENCRYPT>
  66: std::string FilterString(std::string const& in_value, std::string const& iv) {
  67:   std::string const kEncryptKey = "mogmog";
  68:   std::string encrypt_key(AES::DEFAULT_KEYLENGTH, 0);
  69:   std::string initialization_vector(AES::BLOCKSIZE, 0);
  70:   CopyWholeString(kEncryptKey, &encrypt_key);
  71:   CopyWholeString(iv, &initialization_vector);
  72:  
  73:   boost::mpl::if_<boost::mpl::bool_<IS_ENCRYPT>,
  74:       CryptoPP::CTR_Mode<AES>::Encryption,
  75:       CryptoPP::CTR_Mode<AES>::Decryption>::type processor;
  76:   processor.SetKeyWithIV(reinterpret_cast<byte const*>(
  77:       encrypt_key.c_str()), encrypt_key.size(), reinterpret_cast<byte const*>(initialization_vector.c_str()));
  78:  
  79:   std::string out_value;
  80:   CryptoPP::StreamTransformationFilter stf(processor, new CryptoPP::StringSink(out_value));
  81:   stf.Put(reinterpret_cast<byte const*>(in_value.c_str()), in_value.size());
  82:   stf.MessageEnd();
  83:   return out_value;
  84: }
  85:  
  86: // Parse command line.
  87: void ParseCommandLine(int argc, char * argv[], ProgramOption * option) {
  88:   po::options_description opt("Usage");
  89:   opt.add_options()
  90:       ("help", "Print this message.")
  91:       ("count", po::value<int>()->default_value(5), "Number of headers to get.")
  92:       ("format", po::value<std::string>()->default_value("[%m/%d %H:%M] %N <%A>%n %J"),
  93:           "Output format.\n"
  94:           "%y Last two digits of the year number\n"
  95:           "%Y Year number\n"
  96:           "%m Month number\n"
  97:           "%d Day number in the month\n"
  98:           "%H Hour number (24 hour system)\n"
  99:           "%M Minute number\n"
 100:           "%S Second number\n"
 101:           "%J Mail subject\n"
 102:           "%N Mail from-name\n"
 103:           "%A Mail from-address\n"
 104:           "%n New line\n"
 105:           "%% Character %")
 106:       ("ini-file", po::value<std::string>()->default_value("mogmail.ini"), "Path to the initial file.")
 107:       ("setup", "Remake the initial file.");
 108:   po::variables_map vm;
 109:   po::store(po::parse_command_line(argc, argv, opt), vm);
 110:   po::notify(vm);
 111:  
 112:   if (vm.count("help")) {
 113:     std::cout << opt << std::endl;
 114:     option->mode = kModeUsage;
 115:     return;
 116:   }
 117:   if (vm.count("setup")) { option->mode = kModeSetup; }
 118:   option->ini_file_path = vm["ini-file"].as<std::string>();
 119:   option->out_count = vm["count"].as<int>();
 120:   if (option->out_count <= 0) {
 121:     throw std::runtime_error("Count should be over 0.");
 122:   }
 123:   option->out_format = vm["format"].as<std::string>();
 124: }
 125:  
 126: // Read input from std::cin.
 127: bool GetLine(std::string const& prompt, std::string * line) {
 128:   std::cout << prompt;
 129:   std::getline(std::cin, *line);
 130:   return !line->empty();
 131: }
 132:  
 133: // Save configuration.
 134: void SaveConnectOption(std::string const& path, ConnectOption const& opt) {
 135:   std::ofstream ini_file(path);
 136:   if (!ini_file) { throw std::runtime_error("Failed to save ini-file."); }
 137:  
 138:   ini_file << "[POP3]\n";
 139:   ini_file << "SERVER=" << opt.server_address << "\n";
 140:   ini_file << "PORT=" << opt.server_port << "\n";
 141:   ini_file << "USERNAME=" << opt.username << "\n";
 142:   ini_file << "PASSWORD=" << mog::util::Base64::Encode(
 143:       FilterString<true>(opt.password, opt.username)) << "\n";
 144: }
 145:  
 146: // Load configuration.
 147: void LoadConnectOption(std::string const& path, ConnectOption *opt) {
 148:   std::ifstream ini_file(path);
 149:   if (!ini_file) {
 150:     throw std::runtime_error("Failed to open " + path +".\n" +
 151:                              "Execute '--setup' first.\n");
 152:   }
 153:  
 154:   std::string line;
 155:   std::string password_tmp;
 156:   xp::smatch match;
 157:   while (std::getline(ini_file, line)) {
 158:     if (xp::regex_match(line, match,
 159:         xp::sregex(xp::bos >> (xp::s1=+~(xp::set='=')) >> "=" >> (xp::s2=*xp::_)))) {
 160:       if ("SERVER" == match[1])   {
 161:         opt->server_address = match[2];
 162:       } else if ("PORT" == match[1]) {
 163:         opt->server_port = match[2];
 164:       } else if ("USERNAME" == match[1]) {
 165:         opt->username = match[2];
 166:       } else if ("PASSWORD" == match[1]) {
 167:         password_tmp = mog::util::Base64::Decode(match[2]);
 168:       }
 169:     }
 170:   }
 171:   opt->password = FilterString<false>(password_tmp, opt->username);
 172: }
 173:  
 174: // Interactive configuration setup.
 175: void SetupConfig(std::string const& path) {
 176:   ConnectOption conn_opt;
 177:   std::cout << boost::format("*** SETUP MODE ***") << std::endl;
 178:   std::string line;
 179:   for (;;) { if (GetLine("POP3 Server Address?: ", &conn_opt.server_address)) { break; } }
 180:   if (GetLine("POP3 Server Port?[110]: ", &line)) { conn_opt.server_port = line; }
 181:   for (;;) { if (GetLine("Username: ", &conn_opt.username)) { break; } }
 182:   for (;;) { if (GetLine("Password: ", &conn_opt.password)) { break; } }
 183:  
 184:   // Test connection.
 185:   std::cout << "Testing connection..." << std::endl;
 186:   boost::scoped_ptr<mog::mail::POP3Client> pop_client;
 187:   pop_client.reset(new mog::mail::POP3Client(
 188:       conn_opt.server_address, conn_opt.server_port, conn_opt.username, conn_opt.password));
 189:   pop_client.reset();
 190:   std::cout << "OK." << std::endl;
 191:  
 192:   // Save configuration.
 193:   SaveConnectOption(path, conn_opt);
 194:   std::cout << "Saved configuration to " << path << "." << std::endl;
 195: }
 196:  
 197: // Format result.
 198: std::string FormatResult(mog::mail::MailHeader const& mh, std::string const& format) {
 199:   std::string out_value;
 200:   tm local_time = boost::posix_time::to_tm(boost::date_time::c_local_adjustor<ptime>::utc_to_local(mh.date));
 201:  
 202:   bool flag_escaped = false;
 203:   for (std::string::const_iterator it = format.begin(); it != format.end(); ++it) {
 204:     if (flag_escaped) {
 205:       switch (*it) {
 206:         case 'y': out_value.append((boost::format("%02d") % (local_time.tm_year % 100)).str()); break;
 207:         case 'Y': out_value.append((boost::format("%04d") % (local_time.tm_year + 1900)).str()); break;
 208:         case 'm': out_value.append((boost::format("%02d") % (local_time.tm_mon + 1)).str()); break;
 209:         case 'd': out_value.append((boost::format("%02d") % local_time.tm_mday).str()); break;
 210:         case 'H': out_value.append((boost::format("%02d") % local_time.tm_hour).str()); break;
 211:         case 'M': out_value.append((boost::format("%02d") % local_time.tm_min).str()); break;
 212:         case 'S': out_value.append((boost::format("%02d") % local_time.tm_sec).str()); break;
 213:         case 'J': out_value.append(mh.subject); break;
 214:         case 'N': out_value.append(mh.from_name); break;
 215:         case 'A': out_value.append(mh.from_address); break;
 216:         case 'n': out_value.push_back('\n'); break;
 217:         case '%': out_value.push_back('%'); break;
 218:         default: throw std::runtime_error("Invalid output format: " + format);
 219:       }
 220:       flag_escaped = false;
 221:     } else if ('%' == *it) {
 222:       flag_escaped = true;
 223:     } else {
 224:       out_value.push_back(*it);
 225:     }
 226:   }
 227:   return out_value;
 228: }
 229:  
 230: }  // namespace
 231:  
 232: ////////////////////////////////////////////////////////////////////////////////
 233: // main
 234: int main(int argc, char * argv[]) {
 235:   ExitCode exit_code = kOK;
 236:   try {
 237:     ProgramOption option;
 238:  
 239:     // Parse command line options.
 240:     try {
 241:       ParseCommandLine(argc, argv, &option);
 242:     } catch(std::exception const& e) {
 243:       std::cout << "Error: " << e.what() << std::endl;
 244:       std::cout << "Check '--help' option.";
 245:       return kErrorSyntax;
 246:     }
 247:  
 248:     // Check mode.
 249:     switch (option.mode) {
 250:       case kModeUsage:
 251:         return kOK;
 252:       case kModeSetup:
 253:         // Save configuration file.
 254:         try {
 255:           SetupConfig(option.ini_file_path);
 256:         } catch(std::exception const& e) {
 257:           std::cout << "Error: " << e.what() << std::endl;
 258:           return kErrorSetup;
 259:         }
 260:         return kOK;
 261:       case kModeFetch:
 262:         break;
 263:       default:
 264:         assert(!"Unexpected mode.");
 265:     }
 266:  
 267:     int num_mails = 0;
 268:     std::vector<mog::mail::MailHeader> mail_header;
 269:     try {
 270:       // Load parameters.
 271:       ConnectOption conn_opt;
 272:       LoadConnectOption(option.ini_file_path, &conn_opt);
 273:  
 274:       // Connect to the POP3 server.
 275:       boost::scoped_ptr<mog::mail::POP3Client> pop_client;
 276:       pop_client.reset(new mog::mail::POP3Client(
 277:           conn_opt.server_address, conn_opt.server_port, conn_opt.username, conn_opt.password));
 278:       // Get mail count.
 279:       num_mails = pop_client->num_mails();
 280:       // Get headers.
 281:       for (int i = num_mails; i > num_mails - option.out_count && i > 0; --i) {
 282:         mail_header.push_back(pop_client->GetHeader(i));
 283:       }
 284:       // Disconnect from the server.
 285:       pop_client.reset();
 286:     } catch(std::exception const& e) {
 287:         std::cout << std::string("Error: ") + e.what();
 288:         exit_code = kErrorFetch;
 289:     }
 290:     // Format result.
 291:     try {
 292:       for (int i = 0; i < option.out_count; ++i) {
 293:         if (i < num_mails) {
 294:           std::cout << FormatResult(mail_header[i], option.out_format) << std::endl;
 295:         }
 296:       }
 297:     } catch(std::exception const& e) {
 298:         std::cout << std::string("Error: ") + e.what();
 299:         exit_code = kErrorWriteResult;
 300:     }
 301:   } catch(...) {
 302:     std::cout << "FATAL EXCEPTION!\n";
 303:     return kErrorFatal;
 304:   }
 305:   // system("pause");
 306:   return exit_code;
 307: }

0 件のコメント:

コメントを投稿