├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── README_zh.md ├── cmake └── mailio-config.cmake.in ├── doxygen.conf ├── doxygen.conf.in ├── examples ├── CMakeLists.txt ├── aleph0.png ├── imap_folders.cpp ├── imap_remove_msg.cpp ├── imaps_search.cpp ├── imaps_stat.cpp ├── infinity.png ├── message.cpp ├── pop3_remove_msg.cpp ├── pop3s_attachment.cpp ├── pop3s_fetch_one.cpp ├── smtp_utf8_qp_msg.cpp ├── smtps_attachment.cpp ├── smtps_multipart.cpp └── smtps_simple_msg.cpp ├── include ├── mailio │ ├── base64.hpp │ ├── binary.hpp │ ├── bit7.hpp │ ├── bit8.hpp │ ├── codec.hpp │ ├── dialog.hpp │ ├── imap.hpp │ ├── mailboxes.hpp │ ├── message.hpp │ ├── mime.hpp │ ├── percent.hpp │ ├── pop3.hpp │ ├── q_codec.hpp │ ├── quoted_printable.hpp │ └── smtp.hpp └── version.hpp.in ├── mailio.pc.in ├── src ├── base64.cpp ├── binary.cpp ├── bit7.cpp ├── bit8.cpp ├── codec.cpp ├── dialog.cpp ├── imap.cpp ├── mailboxes.cpp ├── message.cpp ├── mime.cpp ├── percent.cpp ├── pop3.cpp ├── q_codec.cpp ├── quoted_printable.cpp └── smtp.cpp └── test ├── CMakeLists.txt ├── aleph0.png ├── cv.txt └── test_message.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # vcpkg && cmake 2 | build/ 3 | vcpkg_installed/ 4 | 5 | # macOS 6 | .DS_Store 7 | */.DS_Store 8 | 9 | # visual studio 10 | out/ 11 | .vs/ 12 | 13 | # visual studio code 14 | .vscode/ 15 | 16 | # clion 17 | .idea/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.16.3) 2 | 3 | project(mailio VERSION 0.25.2) 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | 8 | if(NOT DEFINED LIB_SUFFIX) 9 | set(LIB_SUFFIX "" CACHE STRING "Suffix for installed library path. Ex. 64 for lib64") 10 | endif(NOT DEFINED LIB_SUFFIX) 11 | 12 | # Set bindir, if not use -DBIN_INSTALL_DIR 13 | if(NOT BIN_INSTALL_DIR) 14 | if(CMAKE_INSTALL_BINDIR) 15 | set(BIN_INSTALL_DIR ${CMAKE_INSTALL_BINDIR}) 16 | else(CMAKE_INSTALL_BINDIR) 17 | set(BIN_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/bin") 18 | endif(CMAKE_INSTALL_BINDIR) 19 | endif(NOT BIN_INSTALL_DIR) 20 | 21 | # Set libdir, if not use -DLIB_INSTALL_DIR 22 | if(NOT LIB_INSTALL_DIR) 23 | if(CMAKE_INSTALL_LIBDIR) 24 | set(LIB_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}) 25 | else(CMAKE_INSTALL_LIBDIR) 26 | set(LIB_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}") 27 | endif(CMAKE_INSTALL_LIBDIR) 28 | endif(NOT LIB_INSTALL_DIR) 29 | 30 | # Set includedir, if not use -DINCLUDE_INSTALL_DIR 31 | if(NOT INCLUDE_INSTALL_DIR) 32 | if(CMAKE_INSTALL_INCLUDEDIR) 33 | set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}) 34 | else(CMAKE_INSTALL_INCLUDEDIR) 35 | set(INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/include") 36 | endif(CMAKE_INSTALL_INCLUDEDIR) 37 | endif(NOT INCLUDE_INSTALL_DIR) 38 | 39 | # Set sharedir, if not use -DSHARE_INSTALL_DIR 40 | if(NOT SHARE_INSTALL_DIR) 41 | if(CMAKE_INSTALL_DATADIR) 42 | set(SHARE_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}") 43 | else(CMAKE_INSTALL_DATADIR) 44 | set(SHARE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/share") 45 | endif(CMAKE_INSTALL_DATADIR) 46 | endif(NOT SHARE_INSTALL_DIR) 47 | 48 | # options for mailio to control how the project is built. 49 | option(MAILIO_BUILD_DOCUMENTATION "Turn on to build doxygen based documentation." ON) 50 | option(MAILIO_BUILD_EXAMPLES "Turn on to build examples." ON) 51 | option(MAILIO_BUILD_TESTS "Turn on to build the tests." ON) 52 | option(MAILIO_DYN_LINK_TESTS "Turn on to dynamically link the tests." OFF) 53 | 54 | option(BUILD_SHARED_LIBS "Turn on to build mailio as a shared library. When off mailio is build as a static library." ON) 55 | 56 | # add a dependent option to build latex documentation or not. 57 | include(CMakeDependentOption) 58 | cmake_dependent_option(MAILIO_BUILD_LATEX_DOCUMENTATION "Build latex docs" ON "MAILIO_BUILD_DOCUMENTATION" ON) 59 | 60 | if(WIN32) 61 | set(Boost_USE_STATIC_LIBS ON) 62 | endif(WIN32) 63 | 64 | find_package(Boost REQUIRED COMPONENTS system date_time regex) 65 | find_package(OpenSSL) 66 | set(CMAKE_THREAD_PREFER_PTHREAD) 67 | # "Use of both the imported target as well as this switch is highly recommended for new code." 68 | set(THREADS_PREFER_PTHREAD_FLAG) 69 | find_package(Threads) 70 | 71 | set(project_sources 72 | ${PROJECT_SOURCE_DIR}/src/base64.cpp 73 | ${PROJECT_SOURCE_DIR}/src/binary.cpp 74 | ${PROJECT_SOURCE_DIR}/src/bit7.cpp 75 | ${PROJECT_SOURCE_DIR}/src/bit8.cpp 76 | ${PROJECT_SOURCE_DIR}/src/codec.cpp 77 | ${PROJECT_SOURCE_DIR}/src/dialog.cpp 78 | ${PROJECT_SOURCE_DIR}/src/imap.cpp 79 | ${PROJECT_SOURCE_DIR}/src/mailboxes.cpp 80 | ${PROJECT_SOURCE_DIR}/src/message.cpp 81 | ${PROJECT_SOURCE_DIR}/src/mime.cpp 82 | ${PROJECT_SOURCE_DIR}/src/percent.cpp 83 | ${PROJECT_SOURCE_DIR}/src/pop3.cpp 84 | ${PROJECT_SOURCE_DIR}/src/quoted_printable.cpp 85 | ${PROJECT_SOURCE_DIR}/src/q_codec.cpp 86 | ${PROJECT_SOURCE_DIR}/src/smtp.cpp 87 | ) 88 | 89 | set(project_headers 90 | ${PROJECT_SOURCE_DIR}/include/mailio/base64.hpp 91 | ${PROJECT_SOURCE_DIR}/include/mailio/binary.hpp 92 | ${PROJECT_SOURCE_DIR}/include/mailio/bit7.hpp 93 | ${PROJECT_SOURCE_DIR}/include/mailio/bit8.hpp 94 | ${PROJECT_SOURCE_DIR}/include/mailio/codec.hpp 95 | ${PROJECT_SOURCE_DIR}/include/mailio/dialog.hpp 96 | ${PROJECT_SOURCE_DIR}/include/mailio/imap.hpp 97 | ${PROJECT_SOURCE_DIR}/include/mailio/mailboxes.hpp 98 | ${PROJECT_SOURCE_DIR}/include/mailio/message.hpp 99 | ${PROJECT_SOURCE_DIR}/include/mailio/mime.hpp 100 | ${PROJECT_SOURCE_DIR}/include/mailio/percent.hpp 101 | ${PROJECT_SOURCE_DIR}/include/mailio/pop3.hpp 102 | ${PROJECT_SOURCE_DIR}/include/mailio/quoted_printable.hpp 103 | ${PROJECT_SOURCE_DIR}/include/mailio/q_codec.hpp 104 | ${PROJECT_SOURCE_DIR}/include/mailio/smtp.hpp 105 | ) 106 | 107 | # handle documentation 108 | if(${MAILIO_BUILD_DOCUMENTATION}) 109 | find_package(Doxygen COMPONENTS doxygen OPTIONAL_COMPONENTS dot) 110 | if(DOXYGEN_FOUND) 111 | set(DOXYGEN_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/docs/mailio) 112 | set(DOXYGEN_GENERATE_LATEX ${MAILIO_BUILD_LATEX_DOCUMENTATION}) 113 | set(DOXYGEN_OUTPUT_PATH ${DOCOUTDIR}) 114 | 115 | doxygen_add_docs(docs ${PROJECT_SOURCE_DIR}/include ALL WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 116 | else(DOXYGEN_FOUND) 117 | message(STATUS "doxygen was not found, documentation will not be built") 118 | endif(DOXYGEN_FOUND) 119 | endif(${MAILIO_BUILD_DOCUMENTATION}) 120 | 121 | add_library(${PROJECT_NAME} ${project_sources} ${project_headers}) 122 | add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 123 | 124 | # pkg-config support 125 | set(prefix "${CMAKE_INSTALL_PREFIX}") 126 | set(exec_prefix "\${prefix}" ) 127 | 128 | if(IS_ABSOLUTE "${LIB_INSTALL_DIR}") 129 | set(libdir "${LIB_INSTALL_DIR}") 130 | else(IS_ABSOLUTE "${LIB_INSTALL_DIR}") 131 | set(libdir "\${exec_prefix}/${LIB_INSTALL_DIR}") 132 | endif(IS_ABSOLUTE "${LIB_INSTALL_DIR}") 133 | 134 | if(IS_ABSOLUTE "${INCLUDE_INSTALL_DIR}") 135 | set(includedir "${INCLUDE_INSTALL_DIR}/${PROJECT_NAME}") 136 | else(IS_ABSOLUTE "${INCLUDE_INSTALL_DIR}") 137 | set(includedir "\${prefix}/${INCLUDE_INSTALL_DIR}/${PROJECT_NAME}") 138 | endif(IS_ABSOLUTE "${INCLUDE_INSTALL_DIR}") 139 | 140 | configure_file(mailio.pc.in ${CMAKE_BINARY_DIR}/mailio.pc IMMEDIATE @ONLY) 141 | install(FILES ${CMAKE_BINARY_DIR}/mailio.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) 142 | 143 | configure_file(${PROJECT_SOURCE_DIR}/include/version.hpp.in version.hpp) 144 | install(FILES ${CMAKE_BINARY_DIR}/version.hpp DESTINATION ${INCLUDE_INSTALL_DIR}/${PROJECT_NAME}) 145 | 146 | # generate the export header for exporting symbols 147 | # this is needed to generate a shared library. 148 | include(GenerateExportHeader) 149 | generate_export_header(${PROJECT_NAME} EXPORT_FILE_NAME export.hpp) 150 | target_include_directories(${PROJECT_NAME} 151 | PUBLIC 152 | "$" 153 | "$" 154 | ) 155 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/export.hpp DESTINATION "${INCLUDE_INSTALL_DIR}/${PROJECT_NAME}") 156 | 157 | if(Boost_FOUND) 158 | target_include_directories(${PROJECT_NAME} PUBLIC ${Boost_INCLUDE_DIRS}) 159 | target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES}) 160 | endif() 161 | 162 | if(OPENSSL_FOUND) 163 | target_include_directories(${PROJECT_NAME} PUBLIC ${OPENSSL_INCLUDE_DIR}) 164 | target_link_libraries(${PROJECT_NAME} ${OPENSSL_LIBRARIES}) 165 | endif() 166 | 167 | if(MINGW) 168 | target_link_libraries(${PROJECT_NAME} -lws2_32 ) 169 | endif(MINGW) 170 | 171 | if (MSVC) 172 | target_compile_options(${PROJECT_NAME} PRIVATE /W2 /WX) 173 | else() 174 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic) 175 | endif() 176 | 177 | install(DIRECTORY include/mailio DESTINATION ${INCLUDE_INSTALL_DIR}) 178 | 179 | include(CMakePackageConfigHelpers) 180 | include(GNUInstallDirs) 181 | 182 | # Target 183 | install(TARGETS ${PROJECT_NAME} 184 | EXPORT ${PROJECT_NAME}-export 185 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 186 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 187 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 188 | INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 189 | ) 190 | 191 | export( 192 | EXPORT ${PROJECT_NAME}-export 193 | NAMESPACE ${PROJECT_NAME}:: 194 | FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-targets.cmake 195 | ) 196 | 197 | install(EXPORT ${PROJECT_NAME}-export 198 | NAMESPACE ${PROJECT_NAME}:: 199 | FILE ${PROJECT_NAME}-targets.cmake 200 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}/ 201 | ) 202 | 203 | # Config and version 204 | configure_package_config_file( 205 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}-config.cmake.in 206 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake 207 | INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}/ 208 | ) 209 | 210 | write_basic_package_version_file( 211 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake 212 | VERSION ${PROJECT_VERSION} 213 | COMPATIBILITY AnyNewerVersion 214 | ) 215 | 216 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake 217 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake 218 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 219 | ) 220 | 221 | # optionally build examples 222 | if(${MAILIO_BUILD_EXAMPLES}) 223 | add_subdirectory(examples) 224 | file(GLOB PNGS "${PROJECT_SOURCE_DIR}/examples/*.png") 225 | install(FILES ${PNGS} DESTINATION "${SHARE_INSTALL_DIR}/${PROJECT_NAME}/examples/") 226 | endif(${MAILIO_BUILD_EXAMPLES}) 227 | 228 | if(${MAILIO_BUILD_TESTS}) 229 | enable_testing() 230 | add_subdirectory(test) 231 | file(GLOB PNGS "${PROJECT_SOURCE_DIR}/test/aleph0.png") 232 | install(FILES ${PNGS} DESTINATION "${SHARE_INSTALL_DIR}/${PROJECT_NAME}/test/") 233 | file(GLOB TXTS "${PROJECT_SOURCE_DIR}/test/cv.txt") 234 | install(FILES ${TXTS} DESTINATION "${SHARE_INSTALL_DIR}/${PROJECT_NAME}/test/") 235 | endif(${MAILIO_BUILD_TESTS}) 236 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Simplified BSD license 2 | 3 | Copyright (c) 2016, Tomislav Karastojković (http://www.alepho.com). All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # mailio # 3 | 4 | [![C++](https://img.shields.io/badge/C++-17-blue)](https://en.cppreference.com/w/cpp/17) 5 | [![License](https://img.shields.io/badge/License-MIT-blue)](LICENSE) 6 | [![Conan Center](https://img.shields.io/conan/v/mailio)](https://conan.io/center/recipes/mailio) 7 | [![Vcpkg](https://img.shields.io/vcpkg/v/mailio)](https://vcpkg.link/ports/mailio) 8 | 9 | [中文文档](README_zh.md) 10 | 11 | *mailio* is a cross platform C++ library for MIME format and SMTP, POP3 and IMAP protocols. It is based on the standard C++ 17 and Boost library. 12 | 13 | 14 | # Examples # 15 | 16 | To send a mail, one has to create `message` object and set it's attributes as author, recipient, subject and so on. Then, an SMTP connection 17 | is created by constructing `smtp` (or `smtps`) class. The message is sent over the connection: 18 | 19 | ```cpp 20 | message msg; 21 | msg.from(mail_address("mailio library", "mailio@gmail.com")); 22 | msg.add_recipient(mail_address("mailio library", "mailio@gmail.com")); 23 | msg.subject("smtps simple message"); 24 | msg.content("Hello, World!"); 25 | 26 | smtps conn("smtp.gmail.com", 587); 27 | conn.authenticate("mailio@gmail.com", "mailiopass", smtps::auth_method_t::START_TLS); 28 | conn.submit(msg); 29 | ``` 30 | 31 | To receive a mail, a `message` object is created to store the received message. Mail can be received over POP3 or IMAP, depending of mail server setup. 32 | If POP3 is used, then instance of `pop3` (or `pop3s`) class is created and message is fetched: 33 | 34 | ```cpp 35 | pop3s conn("pop.mail.yahoo.com", 995); 36 | conn.authenticate("mailio@yahoo.com", "mailiopass", pop3s::auth_method_t::LOGIN); 37 | message msg; 38 | conn.fetch(1, msg); 39 | ``` 40 | 41 | Receiving a message over IMAP is analogous. Since IMAP recognizes folders, then one has to be specified, like *inbox*: 42 | 43 | ```cpp 44 | imaps conn("imap.gmail.com", 993); 45 | conn.authenticate("mailio@gmail.com", "mailiopass", imap::auth_method_t::LOGIN); 46 | message msg; 47 | conn.fetch("inbox", 1, msg); 48 | ``` 49 | 50 | More advanced features are shown in `examples` directory, see below how to compile them. 51 | 52 | Note for Gmail users: it might be needed to [register](https://support.google.com/accounts/answer/6010255) *mailio* as a trusted application. Follow the 53 | [Gmail instructions](https://support.google.com/accounts/answer/3466521) to add it and use the generated password for all three protocols. 54 | 55 | Note for Zoho users: if 2FA is turned on, then instead of the primary password, the application password must be used. Follow 56 | [Zoho instructions](https://www.zoho.com/mail/help/adminconsole/two-factor-authentication.html#alink5) to add *mailio* as trusted application and use the 57 | generated password for all three protocols. 58 | 59 | 60 | # Setup # 61 | 62 | *mailio* library is supposed to work on all platforms supporting C++ 17 compiler, Boost 1.81 or newer and CMake build tool. The platforms tested so far are 63 | Linux, Windows, FreeBSD, MacOS, Cygwin, MinGW and the compilers are Gcc, Microsoft Visual C++ and Clang. 64 | 65 | There are several ways to build *mailio*: by cloning the [repo](https://github.com/karastojko/mailio.git) and using Cmake, by using Vcpkg or 66 | [xmake](https://xmake.io). 67 | 68 | 69 | ## CMake ## 70 | 71 | Ensure that OpenSSL, Boost and CMake are in the path. If they are not in the path, one could use CMake options `-DOPENSSL_ROOT_DIR`, `-DBOOST_ROOT` and 72 | `Boost_INCLUDE_DIRS` to set them. Boost must be built with the OpenSSL support. If it cannot be found in the path, set the path explicitly via `library-path` 73 | and `include` parameters of `b2` script (after `bootstrap` finishes). Both static and dynamic libraries should be built in the `build` directory. 74 | 75 | If one wants to specify a non-default installation directory, say `/opt/mailio`, then the option `-DCMAKE_INSTALL_PREFIX` should be used. If a user does not have 76 | privileges for the default directories, then it must specify one by using this CMake variable. 77 | 78 | Other available options are `BUILD_SHARED_LIBS` (whether a shared or static library shall be built, by default a shared lib is built), 79 | `MAILIO_BUILD_DOCUMENTATION` (if Doxygen documentation is generated, by default is on) and `MAILIO_BUILD_EXAMPLES` (if examples are built, by default is on). 80 | 81 | 82 | ### Linux, FreeBSD, MacOS, Cygwin ### 83 | 84 | From the terminal go into the directory where the library is downloaded to, and execute: 85 | ``` 86 | mkdir build 87 | cd ./build 88 | cmake .. 89 | make install 90 | ``` 91 | Installing the project ensures that the test project has also the auxiliary files copied, required by several tests. 92 | 93 | 94 | ### Microsoft Windows/Visual Studio ### 95 | 96 | From the command prompt go into the directory where the library is downloaded, and execute: 97 | ``` 98 | mkdir build 99 | cd .\build 100 | cmake .. 101 | ``` 102 | A solution file will be built, open it from Visual Studio and build the project. To install it, build the `INSTALL` project of the *mailio* solution, it copies 103 | also auxiliary files required by several tests. 104 | 105 | 106 | ### Microsoft Windows/MinGW ### 107 | 108 | Open the command prompt by using the `open_distro_window.bat` script, and execute: 109 | ``` 110 | mkdir build 111 | cd .\build 112 | cmake.exe .. -G "MinGW Makefiles" 113 | make install 114 | ``` 115 | 116 | 117 | ## Vcpkg ## 118 | 119 | Install [Vcpkg](https://github.com/microsoft/vcpkg) and run: 120 | ``` 121 | vcpkg install mailio 122 | ``` 123 | Tests are not available as an option in this case. Use the CMake way to build them. 124 | 125 | 126 | # Features # 127 | 128 | * Recursive formatter and parser of the MIME message. 129 | * MIME message recognizes the most common headers like subject, recipients, content type and so on. 130 | * Message body encodings that are supported: Seven bit, Eight bit, Binary, Base64 and Quoted Printable. 131 | * Header encodings that are supported: ASCII, UTF-8, Base64, Quoted Printable. 132 | * Header attributes encodings that are supported: ASCII, Base64, Quoted Printable, Percent. 133 | * String charset configurable for headers and their attributes. 134 | * All media types are recognized, including MIME message embedded within another message. 135 | * MIME message has configurable line length policy and strict mode for parsing. 136 | * SMTP implementation with message sending. Both plain and SSL (including START TLS) versions are available. 137 | * POP3 implementation with message receiving and removal, getting mailbox statistics. Both plain and SSL (including START TLS) versions are available. 138 | * IMAP implementation with message receiving, removal and search, getting mailbox statistics, managing folders. Both plain and SSL (including START TLS) 139 | versions are available. 140 | 141 | 142 | # Issues and improvements # 143 | 144 | The library is tested on valid mail servers, so probably there are negative test scenarios that are not covered by the code. In case you find one, please 145 | contact me. Here is a list of issues known so far and planned to be fixed in the future. 146 | 147 | * MIME header attributes not fully implemented. 148 | * IMAP supports only ASCII folder names. 149 | * IMAP lacks the idle support. 150 | * Editing parts of a message. 151 | * IMAP flags. 152 | * Asynchronous I/O. 153 | * More descriptive error messages. 154 | * External initialization of the SSL context. 155 | 156 | 157 | # Contributors # 158 | 159 | Thanks to all people who contribute to the project by improving the source code, report problems and propose new features. Here is a list from the Git history, 160 | in case I missed someone please let me know. 161 | 162 | * [Trevor Mellon](https://github.com/TrevorMellon): CMake build scripts. 163 | * [Kira Backes](mailto:kira.backes[at]nrwsoft.de): Fix for correct default message date. 164 | * [sledgehammer_999](mailto:hammered999[at]gmail.com): Replacement of Boost random function with the standard one. 165 | * [Paul Tsouchlos](mailto:developer.paul.123[at]gmail.com): Modernizing build scripts. 166 | * [Anton Zhvakin](mailto:a.zhvakin[at]galament.com): Replacement of deprecated Boost Asio entities. 167 | * [terminator356](mailto:termtech[at]rogers.com): IMAP searching, fetching messages by UIDs. 168 | * [Ilya Tsybulsky](mailto:ilya.tsybulsky[at]gmail.com): MIME parsing and formatting issues. UIDL command for POP3. 169 | * [Ayaz Salikhov](https://github.com/mathbunnyru): Conan packaging. 170 | * [Tim Lukas Harken](tlh[at]tlharken.de): Removing compilation warnings. 171 | * [Rainer Sabelka](mailto:saba[at]sabanet.at]): SMTP server response on accepting a mail. 172 | * [David Garcia](mailto:david.garcia[at]antiteum.com): Vcpkg port. 173 | * [ImJustStokedToBeHere](https://github.com/ImJustStokedToBeHere): Typo error in IMAP. 174 | * [lifof](mailto:youssef.beddad[at]gmail.com): Support for the MinGW compilation. 175 | * [Canyon E](https://github.com/canyone2015): IMAP folder delimiter static variable issue. 176 | * [ostermal](https://github.com/ostermal): Bug with the horizontal tab in MIME headers. 177 | * [MRsoymilk](mailto:313958485[at]qq.com): Bug in the sending attachment example. 178 | * [Don Yihtseu](https://github.com/tsurumi-yizhou): Add Chinese ReadMe. 179 | * [Leonhard Kipp](mailto:Leonhard.Kipp[at]ppro.com): Proper way to build the shared library. Message formatting options. Optional message subject. 180 | * [Orchistro](https://github.com/orchistro): Improving CMake build script. 181 | * [Abril Rincón Blanco](mailto:git[at]rinconblanco.es): Compilation fix for Clang earlier than the version 14. 182 | * [yjm6560](https://github.com/yjm6560): Various IMAP bugs. 183 | * [Hannah Sauermann](mailto:hannah.sauermann[at]seclous.com): Fix for the `stringstream` usage in older standard libraries. 184 | * [Matheus Gabriel Werny](mailto:matheusgwdl[at]protonmail.com): CMake fixes and improvements. 185 | * [stitch3210](https://github.com/stitch3210): Case insensitive headers. 186 | * [Luigi Masdea](luigimasdea0[at]gmail.com): Typo error. 187 | 188 | 189 | # References # 190 | 191 | * [RFC 822](http://www.rfc-editor.org/rfc/rfc822): Standard for the Format of ARPA Internet Text Messages. 192 | * [RFC 1939](http://www.rfc-editor.org/rfc/rfc1939): Post Office Protocol - Version 3 193 | * [RFC 2045](http://www.rfc-editor.org/rfc/rfc2045): Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies 194 | * [RFC 2046](http://www.rfc-editor.org/rfc/rfc2046): Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types 195 | * [RFC 2047](http://www.rfc-editor.org/rfc/rfc2047): MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text 196 | * [RFC 2177](http://www.rfc-editor.org/rfc/rfc2177): IMAP4 IDLE command 197 | * [RFC 2183](http://www.rfc-editor.org/rfc/rfc2183): Communicating Presentation Information in Internet Messages: The Content-Disposition Header Field 198 | * [RFC 2231](http://www.rfc-editor.org/rfc/rfc2231): MIME Parameter Value and Encoded Word Extensions: Sets, Languages, and Continuations 199 | * [RFC 2449](http://www.rfc-editor.org/rfc/rfc2449): Extension Mechanism 200 | * [RFC 3501](http://www.rfc-editor.org/rfc/rfc3501): Message Access Protocol - Version 4rev1 201 | * [RFC 5321](http://www.rfc-editor.org/rfc/rfc5321): Simple Mail Transfer Protocol 202 | * [RFC 5322](http://www.rfc-editor.org/rfc/rfc5322): Internet Message Format 203 | * [RFC 5987](http://www.rfc-editor.org/rfc/rfc5987): Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters 204 | * [RFC 6532](http://www.rfc-editor.org/rfc/rfc6532): Internationalized Email Headers 205 | 206 | 207 | # Contact # 208 | 209 | In case you find a bug, please drop me a mail to contact (at) alepho.com. Since this is my side project, I'll do my best to be responsive. 210 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | 2 | # mailio # 3 | 4 | *mailio* 是一个用于构建MIME格式和实现SMTP以及POP3协议的跨平台C++库,基于C++17标准,使用Boost。 5 | 6 | # 用例 # 7 | 8 | 为了发送一封邮件,你必须创建一个 `message` 对象,并且设置其各个属性,例如 `author`, `recipient`, `subject`等等。 9 | 10 | 旋即你需要构建一个`smtp` (或者`smtps`)类。`message`对象必需通过这个`smtp`连接进行发送。 11 | 12 | ```cpp 13 | message msg; 14 | msg.from(mail_address("mailio library", "mailio@gmail.com")); 15 | msg.add_recipient(mail_address("mailio library", "mailio@gmail.com")); 16 | msg.subject("图样的SMTP邮件"); 17 | msg.content("谈笑风生"); 18 | 19 | smtps conn("smtp.gmail.com", 587); 20 | conn.authenticate("mailio@gmail.com", "mailiopass", smtps::auth_method_t::START_TLS); 21 | conn.submit(msg); 22 | ``` 23 | 24 | 为了收取邮件,你需要创建一个空白的`message`对象来承载接收到的数据。邮件数据会通过POP3或者IMAP传递,这取决于邮件服务器。 25 | 如果要使用POP3,一个`pop3`(或者`pop3s`)实例需被创建。 26 | 27 | ```cpp 28 | pop3s conn("pop.mail.yahoo.com", 995); 29 | conn.authenticate("mailio@yahoo.com", "mailiopass", pop3s::auth_method_t::LOGIN); 30 | message msg; 31 | conn.fetch(1, msg); 32 | ``` 33 | 34 | IMAP也是相似的。然而因为IMAP辨识文件夹,所以你必须指定: 35 | ```cpp 36 | imaps conn("imap.gmail.com", 993); 37 | conn.authenticate("mailio@gmail.com", "mailiopass", imap::auth_method_t::LOGIN); 38 | message msg; 39 | conn.fetch("inbox", 1, msg); 40 | ``` 41 | 42 | 更多进阶姿势在`examples`文件夹里。下文为如何编译: 43 | 44 | 给Gmail用户的提示:你可能需要[注册](https://support.google.com/accounts/answer/6010255) *mailio* 作为一个可信应用。 关注 45 | [Gmail指示](https://support.google.com/accounts/answer/3466521) 来添加 *mailio* 并为协议生成密码。 46 | 47 | # 依赖 # 48 | 49 | *mailio*库理应在支持C++17、Boost库和CMake的所有平台上奏效。 50 | 51 | 对于Linux用户,如下配置是历经测试的: 52 | * Ubuntu 20.04.3 LTS. 53 | * Gcc 8.3.0. 54 | * Boost 1.66 with Regex, Date Time available. 55 | * POSIX Threads, OpenSSL and Crypto libraries available on the system. 56 | * CMake 3.16.3 57 | 58 | 对于FreeBSD用户,如下配置是历经测试的: 59 | * FreeBSD 13. 60 | * Clang 11.0.1. 61 | * Boost 1.72.0 (port). 62 | * CMake 3.21.3. 63 | 64 | 对于macOS用户,如下配置是历经测试的: 65 | * Apple LLVM 9.0.0. 66 | * Boost 1.66. 67 | * OpenSSL 1.0.2n available on the system. 68 | * CMake 3.16.3. 69 | 70 | 对于微软Windows用户,如下配置是历经测试的: 71 | * Windows 10. 72 | * Visual Studio 2019 Community Edition. 73 | * Boost 1.71.0. 74 | * OpenSSL 1.0.2t. 75 | * CMake 3.17.3. 76 | 77 | 对于Cygwin用户,如下配置是历经测试的: 78 | * Cygwin 3.2.0 on Windows 10. 79 | * Gcc 10.2. 80 | * Boost 1.66. 81 | * CMake 3.20. 82 | * LibSSL 1.0.2t and LibSSL 1.1.1f development packages. 83 | 84 | 对于MinGW用户,如下配置是历经测试的: 85 | * MinGW 17.1 on Windows 10 which comes with the bundled Gcc and Boost. 86 | * Gcc 9.2. 87 | * Boost 1.71.0. 88 | * OpenSSL 1.0.2t. 89 | * CMake 3.17.3. 90 | 91 | # 步骤 # 92 | 93 | 只需要两步即可构建:首先克隆[分支](https://github.com/karastojko/mailio.git),然后使用CMake或者Vcpkg 94 | 95 | ## CMake ## 96 | 97 | 确保OpenSSL, Boost, CMake都在PATH中。否则,需要设置CMake参数`-DOPENSSL_ROOT_DIR` 和 `-DBOOST_ROOT`。 98 | Boost必须在有OpenSSL下构建。如果在PATH中不能发现这些库,通过设置环境变量`library-path` 和 `include`来隐式设置。 99 | 你只需要在`bootstrap`后运行`b2`脚本。 100 | 101 | 动态和静态库都会构建于`build`文件夹。如果你需要把库安装到一个特定的路径(例如`/opt/mailio`),设置CMake参数`-DCMAKE_INSTALL_PREFIX`。 102 | 103 | 其他可行的参数有`BUILD_SHARED_LIBS`(默认开启,开启会构建动态链接库), `MAILIO_BUILD_DOCUMENTATION`(默认开启,会生成Doxygen文档,如果安装了的话), `MAILIO_BUILD_EXAMPLES`(默认开启,构建示例代码) 104 | 105 | ### Linux, FreeBSD, macOS, Cygwin ### 106 | 从终端直接进入项目路径构建,只需运行: 107 | ``` 108 | mkdir build 109 | cd ./build 110 | cmake .. 111 | make install 112 | ``` 113 | 114 | ### 微软 Windows/Visual Studio ### 115 | 从命令提示符进入构建,只需运行: 116 | ``` 117 | mkdir build 118 | cd .\build 119 | cmake .. 120 | ``` 121 | 会创造一个解决方案文件,在Visual Studio(或者MSBuild)中打开即可构建。 122 | 123 | ### 微软 Windows/MinGW ### 124 | 用`open_distro_window.bat`打开命令提示符,只需运行: 125 | ``` 126 | mkdir build 127 | cd .\build 128 | cmake.exe .. -G "MinGW Makefiles" 129 | make install 130 | ``` 131 | 132 | ## Vcpkg ## 133 | 用[Vcpkg](https://github.com/microsoft/vcpkg)安装, 只需要运行: 134 | ``` 135 | vcpkg install mailio 136 | ``` 137 | 138 | # 特性 # 139 | * 递归的MIME消息格式构建和解析 140 | * 识别最常用的MIME头,例如subject,recipients,content 141 | * 所有编码格式都支持,例如7bit,8bit,二进制,Base64和QP 142 | * Subject, attachment和name部分可以用ascii和utf8编码。 143 | * 所有的媒体格式都可以被识别,包括嵌入的MIME消息。 144 | * MIME消息有可配置的行长度策略和解析的严格模式 145 | * 包含无加密、SSL、TLS等版本的,用于发送消息的SMTP实现 146 | * 包含无加密、SSL、TLS等版本的,用于接受和删除邮件,获得邮箱统计数据的POP3实现 147 | * 包含无加密、SSL、TLS等版本的,用于接受和删除邮件,获得邮箱统计数据,管理文件夹的IMAP实现 148 | 149 | # 议题 # 150 | 并非所有邮件服务器都被覆盖测试了。如果你发现了什么不能用的库,那么请联系我们。这里是已知的,会在未来修复的问题: 151 | 152 | * 无ASCII附件名称为UTF-8 153 | 154 | # 贡献者 # 155 | * [Trevor Mellon](https://github.com/TrevorMellon): 提供了CMake构建脚本 156 | * [Kira Backes](mailto:kira.backes[at]nrwsoft.de): 修复了默认邮件日期 157 | * [sledgehammer_999](mailto:hammered999[at]gmail.com): 用Boost随机库替换了std随机库 158 | * [Paul Tsouchlos](mailto:developer.paul.123[at]gmail.com): 更新了构建脚本 159 | * [Anton Zhvakin](mailto:a.zhvakin[at]galament.com): 替换了被弃用的Boost Asio API 160 | * [terminator356](mailto:termtech[at]rogers.com): 用UID搜索获取IMAP消息 161 | * [Ilya Tsybulsky](mailto:ilya.tsybulsky[at]gmail.com): 解决了MIME解析和格式化问题,提供了POP3的UIDL命令 162 | * [Ayaz Salikhov](https://github.com/mathbunnyru): 提供了Conan包管理器 163 | * [Tim Lukas Harken](tlh[at]tlharken.de): 移除了编译警告 164 | * [Rainer Sabelka](mailto:saba[at]sabanet.at]): 提供了接受邮件时的SMTP回执 165 | * [David Garcia](mailto:david.garcia[at]antiteum.com): 提供了Vcpkg支持 166 | * [ImJustStokedToBeHere](https://github.com/ImJustStokedToBeHere): 解决了IMAP中的打字错误 167 | * [lifof](mailto:youssef.beddad[at]gmail.com): 提供了MinGW编译支持 168 | * [Canyon E](https://github.com/canyone2015): 解决了IMAP文件夹定界符的静态变量问题 169 | * [ostermal](https://github.com/ostermal): 修复了MIME头中的水平标签的bug 170 | * [MRsoymilk](mailto:313958485[at]qq.com): 修复了发送附件的bug 171 | * [Don Yihtseu](https://github.com/tsurumi-yizhou): 提供了中文文档 172 | * [Orchistro](https://github.com/orchistro): Improving CMake build script. 173 | * [Leonhard Kipp](mailto:Leonhard.Kipp[at]ppro.com): Proper way to build the shared library. Message formatting options. Optional message subject. 174 | * [Abril Rincón Blanco](mailto:git[at]rinconblanco.es): Compilation fix for Clang earlier than the version 14. 175 | * [yjm6560](https://github.com/yjm6560): Various IMAP bugs. 176 | * [Hannah Sauermann](mailto:hannah.sauermann[at]seclous.com): Fix for the `stringstream` usage in older standard libraries. 177 | * [Matheus Gabriel Werny](mailto:matheusgwdl[at]protonmail.com): CMake fixes and improvements. 178 | * [stitch3210](https://github.com/stitch3210): Case insensitive headers. 179 | * [Luigi Masdea](luigimasdea0[at]gmail.com): Typo error. 180 | 181 | 182 | # 联系方式 # 183 | 如果你发现了bug,请给我的邮箱:contact (at) alepho.com 发邮件。苟利mailio生死以,岂因祸福避趋之。 184 | -------------------------------------------------------------------------------- /cmake/mailio-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | find_dependency(Boost COMPONENTS date_time regex system) 5 | find_dependency(OpenSSL) 6 | find_dependency(Threads) 7 | 8 | include("${CMAKE_CURRENT_LIST_DIR}/mailio-targets.cmake") 9 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # "inline" function to add mailio sources easily. 2 | function(add_mailio_example SOURCE_FILE) 3 | get_filename_component(file_name ${SOURCE_FILE} NAME_WE) 4 | add_executable(${file_name} ${SOURCE_FILE}) 5 | target_link_libraries(${file_name} PUBLIC mailio ${CMAKE_THREAD_LIBS_INIT}) 6 | install(TARGETS ${file_name} DESTINATION "${SHARE_INSTALL_DIR}/${PROJECT_NAME}/examples") 7 | endfunction(add_mailio_example) 8 | 9 | # find all the example files. 10 | file(GLOB example_files ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) 11 | 12 | # loop through each file and add the example 13 | foreach(file_name ${example_files}) 14 | add_mailio_example(${file_name}) 15 | endforeach(file_name ${example_files}) 16 | -------------------------------------------------------------------------------- /examples/aleph0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karastojko/mailio/ebbd2af5a62a4c3b51167fded93951dfdd5a8c3e/examples/aleph0.png -------------------------------------------------------------------------------- /examples/imap_folders.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | imaps_folders.cpp 4 | ----------------- 5 | 6 | Connects to IMAP server and lists recursively all folders. 7 | 8 | 9 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 10 | 11 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 12 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 13 | 14 | */ 15 | 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | using mailio::imaps; 24 | using mailio::imap_error; 25 | using mailio::dialog_error; 26 | using std::for_each; 27 | using std::cout; 28 | using std::endl; 29 | using std::string; 30 | 31 | 32 | void print_folders(unsigned tabs, const imaps::mailbox_folder_t& folder) 33 | { 34 | string indent(tabs, '\t'); 35 | for (auto& f : folder.folders) 36 | { 37 | cout << indent << f.first << endl; 38 | if (!f.second.folders.empty()) 39 | print_folders(++tabs, f.second); 40 | } 41 | } 42 | 43 | 44 | int main() 45 | { 46 | try 47 | { 48 | imaps conn("imap.mailserver.com", 993); 49 | // modify username/password to use real credentials 50 | conn.authenticate("mailio@mailserver.com", "mailiopass", imaps::auth_method_t::LOGIN); 51 | imaps::mailbox_folder_t fld = conn.list_folders(""); 52 | print_folders(0, fld); 53 | } 54 | catch (imap_error& exc) 55 | { 56 | cout << exc.what() << endl; 57 | } 58 | catch (dialog_error& exc) 59 | { 60 | cout << exc.what() << endl; 61 | } 62 | 63 | return EXIT_SUCCESS; 64 | } 65 | -------------------------------------------------------------------------------- /examples/imap_remove_msg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | imap_remove_msg.cpp 4 | ------------------- 5 | 6 | Connects to IMAP server and removes a message in mailbox. 7 | 8 | 9 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 10 | 11 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 12 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 13 | 14 | */ 15 | 16 | 17 | #include 18 | #include 19 | 20 | 21 | using mailio::imap; 22 | using mailio::imap_error; 23 | using mailio::dialog_error; 24 | using std::cout; 25 | using std::endl; 26 | 27 | 28 | int main() 29 | { 30 | try 31 | { 32 | // use a server with plain (non-SSL) connectivity 33 | imap conn("imap.mailserver.com", 143); 34 | // modify to use real account 35 | conn.authenticate("mailio@mailserver.com", "mailiopass", imap::auth_method_t::LOGIN); 36 | // remove first message from mailbox 37 | conn.remove("inbox", 1); 38 | } 39 | catch (imap_error& exc) 40 | { 41 | cout << exc.what() << endl; 42 | } 43 | catch (dialog_error& exc) 44 | { 45 | cout << exc.what() << endl; 46 | } 47 | 48 | return EXIT_SUCCESS; 49 | } 50 | -------------------------------------------------------------------------------- /examples/imaps_search.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | imaps_search.cpp 4 | ---------------- 5 | 6 | Connects to IMAP server and searches for messages which satisfy the criteria. 7 | 8 | 9 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 10 | 11 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 12 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 13 | 14 | */ 15 | 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | 24 | using mailio::message; 25 | using mailio::codec; 26 | using mailio::imaps; 27 | using mailio::imap_error; 28 | using mailio::dialog_error; 29 | using std::cout; 30 | using std::endl; 31 | using std::for_each; 32 | using std::list; 33 | using std::string; 34 | 35 | 36 | int main() 37 | { 38 | try 39 | { 40 | imaps conn("imap-mail.outlook.com", 993); 41 | // modify username/password to use real credentials 42 | conn.authenticate("mailio@outlook.com", "mailiopass", imaps::auth_method_t::LOGIN); 43 | conn.select(list({"Inbox"})); 44 | list messages; 45 | list conds; 46 | conds.push_back(imaps::search_condition_t(imaps::search_condition_t::BEFORE_DATE, boost::gregorian::date(2018, 6, 22))); 47 | conds.push_back(imaps::search_condition_t(imaps::search_condition_t::SUBJECT, "mailio")); 48 | conn.search(conds, messages, true); 49 | for_each(messages.begin(), messages.end(), [](unsigned int msg_uid){ cout << msg_uid << endl; }); 50 | } 51 | catch (imap_error& exc) 52 | { 53 | cout << exc.what() << endl; 54 | } 55 | catch (dialog_error& exc) 56 | { 57 | cout << exc.what() << endl; 58 | } 59 | 60 | return EXIT_SUCCESS; 61 | } 62 | -------------------------------------------------------------------------------- /examples/imaps_stat.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | imaps_stat.cpp 4 | -------------- 5 | 6 | Connects to IMAP server and gets number of messages in mailbox. 7 | 8 | 9 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 10 | 11 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 12 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 13 | 14 | */ 15 | 16 | 17 | #include 18 | #include 19 | 20 | 21 | using mailio::imaps; 22 | using mailio::imap_error; 23 | using mailio::dialog_error; 24 | using std::cout; 25 | using std::endl; 26 | 27 | 28 | int main() 29 | { 30 | try 31 | { 32 | // connect to server 33 | imaps conn("imap.zoho.com", 993); 34 | // modify to use an existing zoho account 35 | conn.authenticate("mailio@zoho.com", "mailiopass", imaps::auth_method_t::LOGIN); 36 | // query inbox statistics 37 | imaps::mailbox_stat_t stat = conn.statistics("inbox"); 38 | cout << "Number of messages in mailbox: " << stat.messages_no << endl; 39 | } 40 | catch (imap_error& exc) 41 | { 42 | cout << exc.what() << endl; 43 | } 44 | catch (dialog_error& exc) 45 | { 46 | cout << exc.what() << endl; 47 | } 48 | 49 | return EXIT_SUCCESS; 50 | } 51 | -------------------------------------------------------------------------------- /examples/infinity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karastojko/mailio/ebbd2af5a62a4c3b51167fded93951dfdd5a8c3e/examples/infinity.png -------------------------------------------------------------------------------- /examples/message.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karastojko/mailio/ebbd2af5a62a4c3b51167fded93951dfdd5a8c3e/examples/message.cpp -------------------------------------------------------------------------------- /examples/pop3_remove_msg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | pop3_remove_msg.cpp 4 | ------------------- 5 | 6 | Connects to POP3 server and removes first message in mailbox. 7 | 8 | 9 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 10 | 11 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 12 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 13 | 14 | */ 15 | 16 | 17 | #include 18 | #include 19 | 20 | 21 | using mailio::pop3; 22 | using mailio::pop3_error; 23 | using mailio::dialog_error; 24 | using std::cout; 25 | using std::endl; 26 | 27 | 28 | int main() 29 | { 30 | try 31 | { 32 | // use a server with plain (non-SSL) connectivity 33 | pop3 conn("pop.mailserver.com", 110); 34 | // modify to use real account 35 | conn.authenticate("mailio@mailserver.com", "mailiopass", pop3::auth_method_t::LOGIN); 36 | // remove first message from mailbox 37 | conn.remove(1); 38 | } 39 | catch (pop3_error& exc) 40 | { 41 | cout << exc.what() << endl; 42 | } 43 | catch (dialog_error& exc) 44 | { 45 | cout << exc.what() << endl; 46 | } 47 | 48 | return EXIT_SUCCESS; 49 | } 50 | -------------------------------------------------------------------------------- /examples/pop3s_attachment.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | pop3s_attachment.cpp 4 | -------------------- 5 | 6 | Fetches attachments of a message on POP3 server. 7 | 8 | For this sample to be executed properly, use the message sent by `smtps_attachment.cpp`. 9 | 10 | 11 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 12 | 13 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 14 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 15 | 16 | */ 17 | 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | 26 | using mailio::message; 27 | using mailio::string_t; 28 | using mailio::codec; 29 | using mailio::pop3s; 30 | using mailio::pop3_error; 31 | using mailio::dialog_error; 32 | using std::cout; 33 | using std::endl; 34 | using std::string; 35 | using std::ofstream; 36 | 37 | 38 | int main() 39 | { 40 | try 41 | { 42 | // mail message to store the fetched one 43 | message msg; 44 | // set the line policy to mandatory, so longer lines could be parsed 45 | msg.line_policy(codec::line_len_policy_t::RECOMMENDED); 46 | 47 | // use a server with SSL connectivity 48 | pop3s conn("pop3.mailserver.com", 995); 49 | // modify to use real account 50 | conn.authenticate("mailio@mailserver.com", "mailiopass", pop3s::auth_method_t::LOGIN); 51 | // fetch the first message from mailbox 52 | conn.fetch(1, msg); 53 | 54 | ofstream ofs1("alepho.png", std::ios::binary); 55 | string_t att1; 56 | msg.attachment(1, ofs1, att1); 57 | ofstream ofs2("infiniti.png", std::ios::binary); 58 | string_t att2; 59 | msg.attachment(2, ofs2, att2); 60 | cout << "Received message with subject `" << msg.subject() << "` and attached files `" << 61 | att1 << "` and `" << att2 << "` saved as `alepho.png` and `infiniti.png`." << endl; 62 | } 63 | catch (pop3_error& exc) 64 | { 65 | cout << exc.what() << endl; 66 | } 67 | catch (dialog_error& exc) 68 | { 69 | cout << exc.what() << endl; 70 | } 71 | 72 | return EXIT_SUCCESS; 73 | } 74 | -------------------------------------------------------------------------------- /examples/pop3s_fetch_one.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | pop3s_fetch_one.cpp 4 | ------------------- 5 | 6 | Connects to POP3 server via SSL and fetches first message from mailbox. 7 | 8 | 9 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 10 | 11 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 12 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 13 | 14 | */ 15 | 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | using mailio::message; 23 | using mailio::codec; 24 | using mailio::pop3s; 25 | using mailio::pop3_error; 26 | using mailio::dialog_error; 27 | using std::cout; 28 | using std::endl; 29 | 30 | 31 | int main() 32 | { 33 | try 34 | { 35 | // mail message to store the fetched one 36 | message msg; 37 | // set the line policy to mandatory, so longer lines could be parsed 38 | msg.line_policy(codec::line_len_policy_t::RECOMMENDED); 39 | 40 | // connect to server 41 | pop3s conn("pop.mail.yahoo.com", 995); 42 | // modify to use existing yahoo account 43 | conn.authenticate("mailio@yahoo.com", "mailiopass", pop3s::auth_method_t::LOGIN); 44 | // fetch the first message from mailbox 45 | conn.fetch(1, msg); 46 | cout << msg.subject() << endl; 47 | } 48 | catch (pop3_error& exc) 49 | { 50 | cout << exc.what() << endl; 51 | } 52 | catch (dialog_error& exc) 53 | { 54 | cout << exc.what() << endl; 55 | } 56 | 57 | return EXIT_SUCCESS; 58 | } 59 | -------------------------------------------------------------------------------- /examples/smtp_utf8_qp_msg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | smtp_utf8_qp_msg.cpp 4 | -------------------- 5 | 6 | Connects to SMTP server and sends a message with UTF8 content and subject. 7 | 8 | 9 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 10 | 11 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 12 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 13 | 14 | */ 15 | 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | using mailio::codec; 24 | using mailio::string_t; 25 | using mailio::message; 26 | using mailio::mail_address; 27 | using mailio::mime; 28 | using mailio::smtp; 29 | using mailio::smtp_error; 30 | using mailio::dialog_error; 31 | using std::cout; 32 | using std::endl; 33 | 34 | 35 | int main() 36 | { 37 | try 38 | { 39 | // create mail message 40 | message msg; 41 | msg.from(mail_address(string_t("mailio library", "ASCII", codec::codec_t::BASE64), "mailio@mailserver.com"));// set the correct sender name and address 42 | msg.add_recipient(mail_address(string_t("mailio library", "ASCII", codec::codec_t::BASE64), "mailio@gmail.com"));// set the correct recipent name and address 43 | msg.add_recipient(mail_address(string_t("mailio library", "ASCII", codec::codec_t::BASE64), "mailio@outlook.com"));// add more recipients 44 | msg.add_cc_recipient(mail_address(string_t("mailio library", "ASCII", codec::codec_t::BASE64), "mailio@yahoo.com"));// add CC recipient 45 | msg.add_bcc_recipient(mail_address(string_t("mailio library", "ASCII", codec::codec_t::BASE64), "mailio@zoho.com")); 46 | 47 | msg.subject("smtp utf8 quoted printable message"); 48 | // create message in Cyrillic alphabet 49 | // set Transfer Encoding to Quoted Printable and set Content Type to UTF8 50 | msg.content_transfer_encoding(mime::content_transfer_encoding_t::QUOTED_PRINTABLE); 51 | msg.content_type(message::media_type_t::TEXT, "plain", "utf-8"); 52 | 53 | msg.content( 54 | u8"Ово је јако дугачка порука која има и празних линија и предугачких линија. Није јасно како ће се текст преломити\r\n" 55 | u8"па се надам да ће то овај текст показати.\r\n" 56 | u8"\r\n" 57 | u8"Треба видети како познати мејл клијенти ломе текст, па на\r\n" 58 | u8"основу тога дорадити форматирање мејла. А можда и нема потребе, јер libmailio није замишљен да се\r\n" 59 | u8"бави форматирањем текста.\r\n" 60 | u8"\r\n\r\n" 61 | u8"У сваком случају, после провере латинице треба урадити и проверу utf8 карактера одн. ћирилице\r\n" 62 | u8"и видети како се прелама текст када су карактери вишебајтни. Требало би да је небитно да ли је енкодинг\r\n" 63 | u8"base64 или quoted printable, јер се ascii карактери преламају у нове линије. Овај тест би требало да\r\n" 64 | u8"покаже има ли багова у логици форматирања,\r\n" 65 | u8"а исто то треба проверити са парсирањем.\r\n" 66 | u8"\r\n\r\n\r\n\r\n" 67 | u8"Овде је и провера за низ празних линија."); 68 | 69 | // use a server with plain (non-SSL) connectivity 70 | smtp conn("smtp.mailserver.com", 587); 71 | // modify username/password to use real credentials 72 | conn.authenticate("mailio@mailserver.com", "mailiopass", smtp::auth_method_t::LOGIN); 73 | conn.submit(msg); 74 | } 75 | catch (smtp_error& exc) 76 | { 77 | cout << exc.what() << endl; 78 | } 79 | catch (dialog_error& exc) 80 | { 81 | cout << exc.what() << endl; 82 | } 83 | 84 | return EXIT_SUCCESS; 85 | } 86 | -------------------------------------------------------------------------------- /examples/smtps_attachment.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | smtps_attachment.cpp 4 | -------------------- 5 | 6 | Connects to SMTP server via SSL and sends a message with attached files. 7 | 8 | 9 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 10 | 11 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 12 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 13 | 14 | */ 15 | 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | 24 | using mailio::message; 25 | using mailio::string_t; 26 | using mailio::mail_address; 27 | using mailio::smtps; 28 | using mailio::smtp_error; 29 | using mailio::dialog_error; 30 | using std::cout; 31 | using std::endl; 32 | using std::ifstream; 33 | using std::string; 34 | using std::tuple; 35 | using std::make_tuple; 36 | using std::list; 37 | 38 | 39 | int main() 40 | { 41 | try 42 | { 43 | // create mail message 44 | message msg; 45 | msg.from(mail_address("mailio library", "mailio@gmail.com"));// set the correct sender name and address 46 | msg.add_recipient(mail_address("mailio library", "mailio@gmail.com"));// set the correct recipent name and address 47 | msg.subject("smtps message with attachment"); 48 | msg.content("Here are Aleph0 and Infinity pictures."); 49 | 50 | ifstream ifs1("aleph0.png", std::ios::binary); 51 | ifstream ifs2("infinity.png", std::ios::binary); 52 | list> atts; 53 | atts.push_back(make_tuple(std::ref(ifs1), "aleph0.png", message::content_type_t(message::media_type_t::IMAGE, "png"))); 54 | atts.push_back(make_tuple(std::ref(ifs2), "infinity.png", message::content_type_t(message::media_type_t::IMAGE, "png"))); 55 | msg.attach(atts); 56 | 57 | // use a server with plain (non-SSL) connectivity 58 | smtps conn("smtp.mailserver.com", 465); 59 | // modify username/password to use real credentials 60 | conn.authenticate("mailio@mailserver.com", "mailiopass", smtps::auth_method_t::LOGIN); 61 | conn.submit(msg); 62 | } 63 | catch (smtp_error& exc) 64 | { 65 | cout << exc.what() << endl; 66 | } 67 | catch (dialog_error& exc) 68 | { 69 | cout << exc.what() << endl; 70 | } 71 | 72 | return EXIT_SUCCESS; 73 | } 74 | -------------------------------------------------------------------------------- /examples/smtps_multipart.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | smtps_multipart.cpp 4 | ------------------- 5 | 6 | Connects to SMTP server and sends a multipart message. 7 | 8 | 9 | Copyright (C) 2023, Tomislav Karastojkovic (http://www.alepho.com). 10 | 11 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 12 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 13 | 14 | */ 15 | 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | 24 | using mailio::message; 25 | using mailio::mail_address; 26 | using mailio::mime; 27 | using mailio::smtps; 28 | using mailio::smtp_error; 29 | using mailio::dialog_error; 30 | using std::cout; 31 | using std::endl; 32 | using std::ifstream; 33 | using std::ostringstream; 34 | 35 | 36 | int main() 37 | { 38 | try 39 | { 40 | // create mail message 41 | message msg; 42 | msg.from(mail_address("mailio library", "mailio@mailserver.com"));// set the correct sender name and address 43 | msg.add_recipient(mail_address("mailio library", "mailio@mailserver.com"));// set the correct recipent name and address 44 | msg.subject("smtps multipart message"); 45 | msg.boundary("012456789@mailio.dev"); 46 | msg.content_type(message::media_type_t::MULTIPART, "related"); 47 | 48 | mime title; 49 | title.content_type(message::media_type_t::TEXT, "html", "utf-8"); 50 | title.content_transfer_encoding(mime::content_transfer_encoding_t::BIT_8); 51 | title.content("

Здраво, Свете!

"); 52 | 53 | ifstream ifs("aleph0.png"); 54 | ostringstream ofs; 55 | ofs << ifs.rdbuf(); 56 | 57 | mime img; 58 | img.content_type(message::media_type_t::IMAGE, "png"); 59 | img.content_transfer_encoding(mime::content_transfer_encoding_t::BASE_64); 60 | img.content_disposition(mime::content_disposition_t::INLINE); 61 | img.content(ofs.str()); 62 | img.name("a0.png"); 63 | 64 | msg.add_part(title); 65 | msg.add_part(img); 66 | 67 | // connect to server 68 | smtps conn("smtp.mailserver.com", 587); 69 | // modify username/password to use real credentials 70 | conn.authenticate("mailio@mailserver.com", "mailiopass", smtps::auth_method_t::START_TLS); 71 | conn.submit(msg); 72 | } 73 | catch (smtp_error& exc) 74 | { 75 | cout << exc.what() << endl; 76 | } 77 | catch (dialog_error& exc) 78 | { 79 | cout << exc.what() << endl; 80 | } 81 | 82 | return EXIT_SUCCESS; 83 | } 84 | -------------------------------------------------------------------------------- /examples/smtps_simple_msg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | smtps_simple_msg.cpp 4 | -------------------- 5 | 6 | Connects to SMTP server via START_TLS and sends a simple message. 7 | 8 | 9 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 10 | 11 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 12 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 13 | 14 | */ 15 | 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | using mailio::message; 23 | using mailio::mail_address; 24 | using mailio::smtps; 25 | using mailio::smtp_error; 26 | using mailio::dialog_error; 27 | using std::cout; 28 | using std::endl; 29 | 30 | 31 | int main() 32 | { 33 | try 34 | { 35 | // create mail message 36 | message msg; 37 | msg.from(mail_address("mailio library", "mailio@gmail.com"));// set the correct sender name and address 38 | msg.add_recipient(mail_address("mailio library", "mailio@gmail.com"));// set the correct recipent name and address 39 | msg.subject("smtps simple message"); 40 | msg.content("Hello, World!"); 41 | 42 | // connect to server 43 | smtps conn("smtp.gmail.com", 587); 44 | // modify username/password to use real credentials 45 | conn.authenticate("mailio@gmail.com", "mailiopass", smtps::auth_method_t::START_TLS); 46 | conn.submit(msg); 47 | } 48 | catch (smtp_error& exc) 49 | { 50 | cout << exc.what() << endl; 51 | } 52 | catch (dialog_error& exc) 53 | { 54 | cout << exc.what() << endl; 55 | } 56 | 57 | return EXIT_SUCCESS; 58 | } 59 | -------------------------------------------------------------------------------- /include/mailio/base64.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | base64.hpp 4 | ---------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #ifdef _MSC_VER 17 | #pragma warning(push) 18 | #pragma warning(disable:4251) 19 | #endif 20 | 21 | #include 22 | #include 23 | #include "codec.hpp" 24 | #include "export.hpp" 25 | 26 | 27 | namespace mailio 28 | { 29 | 30 | 31 | /** 32 | Base64 codec. 33 | 34 | @todo Add static method `string encode(string)` to be used by `smtp`? 35 | @todo Does it need the first line policy? 36 | **/ 37 | class MAILIO_EXPORT base64 : public codec 38 | { 39 | public: 40 | 41 | /** 42 | Base64 character set. 43 | **/ 44 | static const std::string CHARSET; 45 | 46 | /** 47 | Setting the encoder and decoder line policies. 48 | 49 | Since Base64 encodes three characters into four, the split is made after each fourth character. It seems that email clients do not merge properly 50 | many lines of encoded text if the split is not grouped by four characters. For that reason, the constructor sets line policies to be divisible by 51 | the number four. 52 | 53 | @param line1_policy First line policy to set. 54 | @param lines_policy Other lines policy than the first one to set. 55 | **/ 56 | base64(std::string::size_type line1_policy, std::string::size_type lines_policy); 57 | 58 | base64(const base64&) = delete; 59 | 60 | base64(base64&&) = delete; 61 | 62 | /** 63 | Default destructor. 64 | **/ 65 | ~base64() = default; 66 | 67 | void operator=(const base64&) = delete; 68 | 69 | void operator=(base64&&) = delete; 70 | 71 | /** 72 | Encoding a string into vector of Base64 encoded strings by applying the line policy. 73 | 74 | @param text String to encode. 75 | @return Vector of Base64 encoded strings. 76 | **/ 77 | std::vector encode(const std::string& text) const; 78 | 79 | /** 80 | Decoding a vector of Base64 encoded strings to string by applying the line policy. 81 | 82 | @param text Vector of Base64 encoded strings. 83 | @return Decoded string. 84 | @throw codec_error Bad character. 85 | @todo Line policy not verified. 86 | **/ 87 | std::string decode(const std::vector& text) const; 88 | 89 | /** 90 | Decoding a Base64 string to a string. 91 | 92 | @param text Base64 encoded string. 93 | @return Encoded string. 94 | @throw * `decode(const std::vector&)`. 95 | **/ 96 | std::string decode(const std::string& text) const; 97 | 98 | private: 99 | 100 | /** 101 | Checking if the given character is in the base64 character set. 102 | 103 | @param ch Character to check. 104 | @return True if it is, false if not. 105 | **/ 106 | bool is_allowed(char ch) const; 107 | 108 | /** 109 | Number of six bit chunks. 110 | **/ 111 | static constexpr unsigned short SEXTETS_NO = 4; 112 | 113 | /** 114 | Number of eight bit characters. 115 | **/ 116 | static constexpr unsigned short OCTETS_NO = SEXTETS_NO - 1; 117 | }; 118 | 119 | 120 | } // namespace mailio 121 | 122 | 123 | #ifdef _MSC_VER 124 | #pragma warning(pop) 125 | #endif 126 | -------------------------------------------------------------------------------- /include/mailio/binary.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | binary.hpp 4 | ---------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #include 17 | #include 18 | #include "codec.hpp" 19 | #include "export.hpp" 20 | 21 | 22 | namespace mailio 23 | { 24 | 25 | 26 | /** 27 | Binary codec. 28 | **/ 29 | class MAILIO_EXPORT binary : public codec 30 | { 31 | public: 32 | 33 | /** 34 | Setting the encoder and decoder line policies. 35 | 36 | @param line1_policy First line policy to set. 37 | @param lines_policy Other lines policy than the first one to set. 38 | **/ 39 | binary(std::string::size_type line1_policy, std::string::size_type lines_policy); 40 | 41 | binary(const binary&) = delete; 42 | 43 | binary(binary&&) = delete; 44 | 45 | /** 46 | Default destructor. 47 | **/ 48 | ~binary() = default; 49 | 50 | void operator=(const binary&) = delete; 51 | 52 | void operator=(binary&&) = delete; 53 | 54 | /** 55 | Encoding a string into vector of binary encoded strings. 56 | 57 | @param text String to encode. 58 | @return Vector with binary encoded strings. 59 | **/ 60 | std::vector encode(const std::string& text) const; 61 | 62 | /** 63 | Decoding a vector of binary encoded strings. 64 | 65 | @param text Vector of binary encoded strings. 66 | @return Decoded string. 67 | @todo Line policy to be verified. 68 | **/ 69 | std::string decode(const std::vector& text) const; 70 | }; 71 | 72 | 73 | } // namespace mailio 74 | -------------------------------------------------------------------------------- /include/mailio/bit7.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | bit7.hpp 4 | -------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #include 17 | #include 18 | #include "codec.hpp" 19 | #include "export.hpp" 20 | 21 | 22 | namespace mailio 23 | { 24 | 25 | 26 | /** 27 | Seven bit codec. 28 | **/ 29 | class MAILIO_EXPORT bit7 : public codec 30 | { 31 | public: 32 | 33 | /** 34 | Setting the encoder and decoder line policies. 35 | 36 | @param line1_policy First line policy to set. 37 | @param lines_policy Other lines policy than the first one to set. 38 | **/ 39 | bit7(std::string::size_type line1_policy, std::string::size_type lines_policy); 40 | 41 | bit7(const bit7&) = delete; 42 | 43 | bit7(bit7&&) = delete; 44 | 45 | /** 46 | Default destructor. 47 | **/ 48 | ~bit7() = default; 49 | 50 | void operator=(const bit7&) = delete; 51 | 52 | void operator=(bit7&&) = delete; 53 | 54 | /** 55 | Encoding a string into vector of 7bit encoded strings by applying the line policy. 56 | 57 | @param text String to encode. 58 | @return Vector of seven bit encoded strings. 59 | @throw codec_error Bad character. 60 | **/ 61 | std::vector encode(const std::string& text) const; 62 | 63 | /** 64 | Decoding a vector of 7bit encoded strings to string by applying the line policy. 65 | 66 | @param text Vector of 7bit encoded strings. 67 | @return Decoded string. 68 | @throw codec_error Line policy overflow. 69 | @throw codec_error Bad character. 70 | **/ 71 | std::string decode(const std::vector& text) const; 72 | 73 | private: 74 | 75 | /** 76 | Checking if a character is in the 7bit character set. 77 | 78 | @param ch Character to check. 79 | @return True if it is, false if not. 80 | **/ 81 | bool is_allowed(char ch) const; 82 | }; 83 | 84 | 85 | } // namespace mailio 86 | -------------------------------------------------------------------------------- /include/mailio/bit8.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | bit8.hpp 4 | -------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | 17 | #include 18 | #include 19 | #include "codec.hpp" 20 | #include "export.hpp" 21 | 22 | 23 | namespace mailio 24 | { 25 | 26 | 27 | /** 28 | Eight bit codec. 29 | **/ 30 | class MAILIO_EXPORT bit8 : public codec 31 | { 32 | public: 33 | 34 | /** 35 | Setting the encoder and decoder line policies. 36 | 37 | @param line1_policy First line policy to set. 38 | @param lines_policy Other lines policy than the first one to set. 39 | **/ 40 | bit8(std::string::size_type line1_policy, std::string::size_type lines_policy); 41 | 42 | bit8(const bit8&) = delete; 43 | 44 | bit8(bit8&&) = delete; 45 | 46 | /** 47 | Default destructor. 48 | **/ 49 | ~bit8() = default; 50 | 51 | void operator=(const bit8&) = delete; 52 | 53 | void operator=(bit8&&) = delete; 54 | 55 | /** 56 | Encoding a string into vector of 8bit encoded strings by applying the line policy. 57 | 58 | @param text String to encode. 59 | @return Vector of eight bit encoded strings. 60 | @throw codec_error Bad character. 61 | **/ 62 | std::vector encode(const std::string& text) const; 63 | 64 | /** 65 | Decoding a vector of 8bit strings to string by applying the line policy. 66 | 67 | @param text Vector of eight bit encoded strings. 68 | @return Decoded string. 69 | @throw codec_error Line policy overflow. 70 | @throw codec_error Bad character. 71 | **/ 72 | std::string decode(const std::vector& text) const; 73 | 74 | private: 75 | 76 | /** 77 | Checking if a character is in the 8bit character set. 78 | 79 | @param ch Character to check. 80 | @return True if it is, false if not. 81 | **/ 82 | bool is_allowed(char ch) const; 83 | }; 84 | 85 | 86 | } // namespace mailio 87 | -------------------------------------------------------------------------------- /include/mailio/codec.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | codec.hpp 4 | --------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #ifdef _MSC_VER 17 | #pragma warning(push) 18 | #pragma warning(disable:4251) 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | #include "export.hpp" 25 | 26 | 27 | namespace mailio 28 | { 29 | 30 | 31 | /** 32 | Base class for codecs, contains various constants and miscellaneous functions for encoding/decoding purposes. 33 | 34 | @todo `encode()` and `decode()` as abstract methods? 35 | **/ 36 | class MAILIO_EXPORT codec 37 | { 38 | public: 39 | 40 | /** 41 | Calculating value of the given hex digit. 42 | **/ 43 | static int hex_digit_to_int(char digit); 44 | 45 | /** 46 | Checking if a character is eight bit. 47 | 48 | @param ch Character to check. 49 | @return True if eight bit, false if seven bit. 50 | **/ 51 | static bool is_8bit_char(char ch); 52 | 53 | /** 54 | Escaping the specified characters in the given string. 55 | 56 | @param text String where to escape certain characters. 57 | @param escaping_chars Characters to be escaped. 58 | @return The given string with the escaped characters. 59 | **/ 60 | static std::string escape_string(const std::string& text, const std::string& escaping_chars); 61 | 62 | /** 63 | Surrounding the given string with the given character. 64 | 65 | @param text String to surround. 66 | @param surround_char Character to be used for the surrounding. 67 | @return Surrounded string. 68 | **/ 69 | static std::string surround_string(const std::string& text, char surround_char = '"'); 70 | 71 | /** 72 | Checking if a string is eight bit encoded. 73 | 74 | @param txt String to check. 75 | @return True if it's eight bit, false if not. 76 | **/ 77 | static bool is_utf8_string(const std::string& txt); 78 | 79 | /** 80 | Nil character. 81 | **/ 82 | static const char NIL_CHAR = '\0'; 83 | 84 | /** 85 | Carriage return character. 86 | **/ 87 | static const char CR_CHAR = '\r'; 88 | 89 | /** 90 | Line feed character. 91 | **/ 92 | static const char LF_CHAR = '\n'; 93 | 94 | /** 95 | Plus character. 96 | **/ 97 | static const char PLUS_CHAR = '+'; 98 | 99 | /** 100 | Minus character. 101 | **/ 102 | static const char MINUS_CHAR = '-'; 103 | 104 | /** 105 | Percent character. 106 | **/ 107 | static const char PERCENT_HEX_FLAG = '%'; 108 | 109 | /** 110 | Slash character. 111 | **/ 112 | static const char SLASH_CHAR = '/'; 113 | 114 | /** 115 | Backslash character. 116 | **/ 117 | static const char BACKSLASH_CHAR = '\\'; 118 | 119 | /** 120 | Equal character. 121 | **/ 122 | static const char EQUAL_CHAR = '='; 123 | 124 | /** 125 | Equal character as string. 126 | **/ 127 | static const std::string EQUAL_STR; 128 | 129 | /** 130 | Space character. 131 | **/ 132 | static const char SPACE_CHAR = ' '; 133 | 134 | /** 135 | Space character as string. 136 | **/ 137 | static const std::string SPACE_STR; 138 | 139 | /** 140 | Exclamation mark character. 141 | **/ 142 | static const char EXCLAMATION_CHAR = '!'; 143 | 144 | /** 145 | Question mark character. 146 | **/ 147 | static const char QUESTION_MARK_CHAR = '?'; 148 | 149 | /** 150 | Dot character. 151 | **/ 152 | static const char DOT_CHAR = '.'; 153 | 154 | /** 155 | Dot character string. 156 | **/ 157 | static const std::string DOT_STR; 158 | 159 | /** 160 | Comma character. 161 | **/ 162 | static const char COMMA_CHAR = ','; 163 | 164 | /** 165 | Comma character as string. 166 | **/ 167 | static const std::string COMMA_STR; 168 | 169 | /** 170 | Colon character. 171 | **/ 172 | static const char COLON_CHAR = ':'; 173 | 174 | /** 175 | Colon character as string. 176 | **/ 177 | static const std::string COLON_STR; 178 | 179 | /** 180 | Semicolon character. 181 | **/ 182 | static const char SEMICOLON_CHAR = ';'; 183 | 184 | /** 185 | Semicolon character as string. 186 | **/ 187 | static const std::string SEMICOLON_STR; 188 | 189 | /** 190 | Zero number character. 191 | **/ 192 | static const char ZERO_CHAR = '0'; 193 | 194 | /** 195 | Nine number character. 196 | **/ 197 | static const char NINE_CHAR = '9'; 198 | 199 | /** 200 | Letter A character. 201 | **/ 202 | static const char A_CHAR = 'A'; 203 | 204 | /** 205 | Tilde character. 206 | **/ 207 | static const char TILDE_CHAR = '~'; 208 | 209 | /** 210 | Quote character. 211 | **/ 212 | static const char QUOTE_CHAR = '"'; 213 | 214 | /** 215 | Quote character as string. 216 | **/ 217 | static const std::string QUOTE_STR; 218 | 219 | /** 220 | Left parenthesis character. 221 | **/ 222 | static const char LEFT_PARENTHESIS_CHAR = '('; 223 | 224 | /** 225 | Right parenthesis character. 226 | **/ 227 | static const char RIGHT_PARENTHESIS_CHAR = ')'; 228 | 229 | /** 230 | Left bracket chartacter. 231 | **/ 232 | static const char LEFT_BRACKET_CHAR = '['; 233 | 234 | /** 235 | Right bracket chartacter. 236 | **/ 237 | static const char RIGHT_BRACKET_CHAR = ']'; 238 | 239 | /** 240 | Left brace character. 241 | **/ 242 | static const char LEFT_BRACE_CHAR = '{'; 243 | 244 | /** 245 | Right brace character. 246 | **/ 247 | static const char RIGHT_BRACE_CHAR = '}'; 248 | 249 | /** 250 | Monkey character. 251 | **/ 252 | static const char MONKEY_CHAR = '@'; 253 | 254 | /** 255 | Less than character. 256 | **/ 257 | static const char LESS_THAN_CHAR = '<'; 258 | 259 | /** 260 | Less than character as string. 261 | **/ 262 | static const std::string LESS_THAN_STR; 263 | 264 | /** 265 | Greater than character. 266 | **/ 267 | static const char GREATER_THAN_CHAR = '>'; 268 | 269 | /** 270 | Greater than character as string. 271 | **/ 272 | static const std::string GREATER_THAN_STR; 273 | 274 | /** 275 | Underscore character. 276 | **/ 277 | static const char UNDERSCORE_CHAR = '_'; 278 | 279 | /** 280 | Hexadecimal alphabet. 281 | **/ 282 | static const std::string HEX_DIGITS; 283 | 284 | /** 285 | Carriage return plus line feed string. 286 | **/ 287 | static const std::string END_OF_LINE; 288 | 289 | /** 290 | Dot character is the end of message for SMTP. 291 | **/ 292 | static const std::string END_OF_MESSAGE; 293 | 294 | /** 295 | ASCII charset label. 296 | **/ 297 | static const std::string CHARSET_ASCII; 298 | 299 | /** 300 | UTF-8 charset label. 301 | **/ 302 | static const std::string CHARSET_UTF8; 303 | 304 | /** 305 | Attribute indicator for the charset and language parameters. 306 | **/ 307 | static const char ATTRIBUTE_CHARSET_SEPARATOR{'\''}; 308 | 309 | /** 310 | Attribute indicator for the charset and language parameters as string. 311 | **/ 312 | static const std::string ATTRIBUTE_CHARSET_SEPARATOR_STR; 313 | 314 | /** 315 | Line length policy. 316 | **/ 317 | enum class line_len_policy_t : std::string::size_type {RECOMMENDED = 78, MANDATORY = 998, NONE = UINT_MAX, 318 | VERYLARGE [[deprecated]] = 16384}; 319 | 320 | /** 321 | Methods used for the MIME header encoding/decoding. 322 | **/ 323 | enum class codec_t {ASCII, BASE64, QUOTED_PRINTABLE, UTF8, PERCENT}; 324 | 325 | /** 326 | Setting the encoder and decoder line policies. 327 | 328 | @param line1_policy First line policy to set. 329 | @param lines_policy Other lines policy than the first one to set. 330 | **/ 331 | codec(std::string::size_type line1_policy, std::string::size_type lines_policy); 332 | 333 | codec(const codec&) = delete; 334 | 335 | codec(codec&&) = delete; 336 | 337 | /** 338 | Default destructor. 339 | **/ 340 | virtual ~codec() = default; 341 | 342 | void operator=(const codec&) = delete; 343 | 344 | void operator=(codec&&) = delete; 345 | 346 | /** 347 | Enabling/disabling the strict mode. 348 | 349 | @param mode True to enable strict mode, false to disable. 350 | **/ 351 | void strict_mode(bool mode); 352 | 353 | /** 354 | Returning the strict mode status. 355 | 356 | @return True if strict mode enabled, false if disabled. 357 | **/ 358 | bool strict_mode() const; 359 | 360 | protected: 361 | 362 | /** 363 | Policy applied for encoding of the first line. 364 | **/ 365 | std::string::size_type line1_policy_; 366 | 367 | /** 368 | Policy applied for encoding of the lines other than first one, and for decoding of all lines including the first one. 369 | **/ 370 | std::string::size_type lines_policy_; 371 | 372 | /** 373 | Strict mode for encoding/decoding. 374 | **/ 375 | bool strict_mode_; 376 | }; 377 | 378 | 379 | /** 380 | Error thrown by codecs. 381 | **/ 382 | class codec_error : public std::runtime_error 383 | { 384 | public: 385 | 386 | /** 387 | Calling parent constructor. 388 | 389 | @param msg Error message. 390 | **/ 391 | explicit codec_error(const std::string& msg) : std::runtime_error(msg) 392 | { 393 | } 394 | 395 | /** 396 | Calling parent constructor. 397 | 398 | @param msg Error message. 399 | **/ 400 | explicit codec_error(const char* msg) : std::runtime_error(msg) 401 | { 402 | } 403 | }; 404 | 405 | 406 | /** 407 | String which contains charset together with the representation. 408 | **/ 409 | template 410 | struct String 411 | { 412 | /** 413 | String content. 414 | **/ 415 | Buf buffer; 416 | 417 | 418 | /** 419 | String charset. 420 | **/ 421 | std::string charset; 422 | 423 | 424 | /** 425 | String codec. 426 | **/ 427 | codec::codec_t codec_type; 428 | 429 | /** 430 | Default constructor. 431 | **/ 432 | String() : buffer(), charset(codec::CHARSET_ASCII), codec_type(codec::codec_t::ASCII) 433 | { 434 | } 435 | 436 | 437 | /** 438 | Default copy constructor. 439 | **/ 440 | String(const String&) = default; 441 | 442 | 443 | /** 444 | Default move constructor. 445 | **/ 446 | String(String&&) = default; 447 | 448 | 449 | /** 450 | Initializing of the buffer and charset. 451 | 452 | @param buffer_s Content of the string. 453 | @param charset_s Charset of the string. 454 | @param codec_s Codec of the string. 455 | **/ 456 | String(const Buf& buffer_s, const std::string& charset_s = codec::CHARSET_ASCII, codec::codec_t codec_s = codec::codec_t::ASCII) : 457 | buffer(buffer_s), charset(boost::to_upper_copy(charset_s)), codec_type(codec_s) 458 | { 459 | } 460 | 461 | 462 | /** 463 | Initializing of the buffer with the string literal. 464 | 465 | @param str String literal. 466 | @param charset_s Charset of the string. 467 | @param codec_s Codec of the string. 468 | **/ 469 | String(const Char* str, const std::string& charset_s = codec::CHARSET_ASCII, codec::codec_t codec_s = codec::codec_t::ASCII) : 470 | String(Buf(str), charset_s, codec_s) 471 | { 472 | } 473 | 474 | 475 | /** 476 | Default copy assignment. 477 | **/ 478 | String& operator=(const String& other) = default; 479 | 480 | 481 | /** 482 | Default move assignment. 483 | **/ 484 | String& operator=(String&& other) = default; 485 | 486 | 487 | /** 488 | Conversion to the buffer type. 489 | **/ 490 | operator Buf() const 491 | { 492 | return buffer; 493 | } 494 | }; 495 | 496 | 497 | /** 498 | Output stream standard insert operator. 499 | 500 | @param os Output stream to insert into. 501 | @oaram str String to insert. 502 | @return The output stream itself. 503 | **/ 504 | template 505 | std::ostream& operator<<(std::ostream& os, const String& str) 506 | { 507 | return os << str.buffer; 508 | } 509 | 510 | 511 | using string_t = String; 512 | #if defined(__cpp_char8_t) 513 | using u8string_t = String; 514 | #endif 515 | 516 | 517 | // String operators. 518 | 519 | 520 | /** 521 | Deals only with the buffers. The left character set is taken, the right is ignored. 522 | 523 | @param lhs First string to add. 524 | @param rhs Second string to add. 525 | @result Concatenated given strings. 526 | **/ 527 | template 528 | String operator+(const String& lhs, const String& rhs) 529 | { 530 | String result; 531 | result.buffer = lhs.buffer + rhs.buffer; 532 | result.charset = lhs.charset; 533 | return result; 534 | } 535 | 536 | 537 | /** 538 | Deals only with the buffers. The left character set is taken, the right is ignored. 539 | 540 | @param lhs String to be added to. 541 | @param rhs String to add. 542 | @result Second string concatenated to the first one. 543 | **/ 544 | template 545 | String& operator+=(String& lhs, const String& rhs) 546 | { 547 | lhs.buffer += rhs.buffer; 548 | return lhs; 549 | } 550 | 551 | 552 | /** 553 | Checking whether the strings are equal by the content and charset. 554 | 555 | @param lhs First string to compare. 556 | @param rhs Second string to compare. 557 | @return True if they are equal, false if not. 558 | **/ 559 | template 560 | bool operator==(const String& lhs, const String& rhs) 561 | { 562 | return lhs.buffer == rhs.buffer && lhs.charset == rhs.charset && lhs.codec_type == rhs.codec_type; 563 | } 564 | 565 | 566 | /** 567 | Checking whether the strings are not equal by the content or charset. 568 | 569 | @param lhs First string to compare. 570 | @param rhs Second string to compare. 571 | @return True if they are not equal, false if they are. 572 | **/ 573 | template 574 | bool operator!=(const String& lhs, const String& rhs) 575 | { 576 | return !operator==(lhs, rhs); 577 | } 578 | 579 | 580 | /** 581 | Checking whether the first string is less than the second one. 582 | 583 | @param lhs First string to compare. 584 | @param rhs Second string to compare. 585 | @return True if the first one is less than the second one, false otherwise. 586 | **/ 587 | template 588 | bool operator<(const String& lhs, const String& rhs) 589 | { 590 | return lhs.buffer < rhs.buffer; 591 | } 592 | 593 | 594 | /** 595 | Checking whether the first string is greater than the second one. 596 | 597 | @param lhs First string to compare. 598 | @param rhs Second string to compare. 599 | @return True if the first one is greater than the second one, false otherwise. 600 | **/ 601 | template 602 | bool operator>(const String& lhs, const String& rhs) 603 | { 604 | return operator<(rhs, lhs); 605 | } 606 | 607 | 608 | /** 609 | Checking whether the first string is less or equal than the second one. 610 | 611 | @param lhs First string to compare. 612 | @param rhs Second string to compare. 613 | @return True if the first one is less or equal than the second one, false otherwise. 614 | **/ 615 | template 616 | bool operator<=(const String& lhs, const String& rhs) 617 | { 618 | return !operator>(rhs, lhs); 619 | } 620 | 621 | 622 | /** 623 | Checking whether the first string is greater or equal than the second one. 624 | 625 | @param lhs First string to compare. 626 | @param rhs Second string to compare. 627 | @return True if the first one is greater or equal than the second one, false otherwise. 628 | **/ 629 | template 630 | bool operator>=(const String& lhs, const String& rhs) 631 | { 632 | return !operator<(rhs, lhs); 633 | } 634 | 635 | 636 | // String and std::string. 637 | 638 | 639 | /** 640 | Deals only with the buffers. The left character set is taken. 641 | 642 | @param lhs First string to add. 643 | @param rhs Second string to add. 644 | @result Concatenated given strings. 645 | **/ 646 | template 647 | String operator+(const String& lhs, const std::string& rhs) 648 | { 649 | String result; 650 | result.buffer = lhs.buffer + rhs; 651 | result.charset = lhs.charset; 652 | return result; 653 | } 654 | 655 | 656 | /** 657 | Deals only with the buffers. The left character set is taken. 658 | 659 | @param lhs String to be added to. 660 | @param rhs String to add. 661 | @result Second string concatenated to the first one. 662 | **/ 663 | template 664 | String& operator+=(String& lhs, const std::string& rhs) 665 | { 666 | lhs.buffer += rhs; 667 | return lhs; 668 | } 669 | 670 | 671 | /** 672 | Checking whether the strings are equal by the content. 673 | 674 | @param lhs First string to compare. 675 | @param rhs Second string to compare. 676 | @return True if they are equal, false if not. 677 | **/ 678 | template 679 | bool operator==(const String& lhs, const std::string& rhs) 680 | { 681 | return lhs.buffer == rhs; 682 | } 683 | 684 | 685 | /** 686 | Checking whether the strings are not equal by the content. 687 | 688 | @param lhs First string to compare. 689 | @param rhs Second string to compare. 690 | @return True if they are not equal, false if they are. 691 | **/ 692 | template 693 | bool operator!=(const String& lhs, const std::string& rhs) 694 | { 695 | return !operator==(lhs, rhs); 696 | } 697 | 698 | 699 | /** 700 | Checking whether the first string is less than the second one. 701 | 702 | @param lhs First string to compare. 703 | @param rhs Second string to compare. 704 | @return True if the first one is less than the second one, false otherwise. 705 | **/ 706 | template 707 | bool operator<(const String& lhs, const std::string& rhs) 708 | { 709 | return lhs.buffer < rhs; 710 | } 711 | 712 | 713 | /** 714 | Checking whether the first string is greater than the second one. 715 | 716 | @param lhs First string to compare. 717 | @param rhs Second string to compare. 718 | @return True if the first one is greater than the second one, false otherwise. 719 | **/ 720 | template 721 | bool operator>(const String& lhs, const std::string& rhs) 722 | { 723 | return lhs.buffer > rhs; 724 | } 725 | 726 | 727 | /** 728 | Checking whether the first string is less or equal than the second one. 729 | 730 | @param lhs First string to compare. 731 | @param rhs Second string to compare. 732 | @return True if the first one is less or equal than the second one, false otherwise. 733 | **/ 734 | template 735 | bool operator<=(const String& lhs, const std::string& rhs) 736 | { 737 | return !operator>(rhs, lhs); 738 | } 739 | 740 | 741 | /** 742 | Checking whether the first string is greater or equal than the second one. 743 | 744 | @param lhs First string to compare. 745 | @param rhs Second string to compare. 746 | @return True if the first one is greater or equal than the second one, false otherwise. 747 | **/ 748 | template 749 | bool operator>=(const String& lhs, const std::string& rhs) 750 | { 751 | return !operator<(rhs, lhs); 752 | } 753 | 754 | 755 | #if defined(__cpp_char8_t) 756 | 757 | // String and std::u8string. 758 | 759 | 760 | /** 761 | Deals only with the buffers. The left character set is taken. 762 | 763 | @param lhs First string to add. 764 | @param rhs Second string to add. 765 | @result Concatenated given strings. 766 | **/ 767 | template 768 | String operator+(const String& lhs, const std::u8string& rhs) 769 | { 770 | String result; 771 | result.buffer = lhs.buffer + rhs; 772 | result.charset = lhs.charset; 773 | return result; 774 | } 775 | 776 | 777 | /** 778 | Deals only with the buffers. The left character set is taken. 779 | 780 | @param lhs String to be added to. 781 | @param rhs String to add. 782 | @result Second string concatenated to the first one. 783 | **/ 784 | template 785 | String& operator+=(String& lhs, const std::u8string& rhs) 786 | { 787 | lhs.buffer += rhs; 788 | return lhs; 789 | } 790 | 791 | 792 | /** 793 | Checking whether the strings are equal by the content. 794 | 795 | @param lhs First string to compare. 796 | @param rhs Second string to compare. 797 | @return True if they are equal, false if not. 798 | **/ 799 | template 800 | bool operator==(const String& lhs, const std::u8string& rhs) 801 | { 802 | return lhs.buffer == rhs; 803 | } 804 | 805 | 806 | /** 807 | Checking whether the strings are not equal by the content. 808 | 809 | @param lhs First string to compare. 810 | @param rhs Second string to compare. 811 | @return True if they are not equal, false if they are. 812 | **/ 813 | template 814 | bool operator!=(const String& lhs, const std::u8string& rhs) 815 | { 816 | return !operator==(lhs, rhs); 817 | } 818 | 819 | 820 | /** 821 | Checking whether the first string is less than the second one. 822 | 823 | @param lhs First string to compare. 824 | @param rhs Second string to compare. 825 | @return True if the first one is less than the second one, false otherwise. 826 | **/ 827 | template 828 | bool operator<(const String& lhs, const std::u8string& rhs) 829 | { 830 | return lhs.buffer < rhs; 831 | } 832 | 833 | 834 | /** 835 | Checking whether the first string is greater than the second one. 836 | 837 | @param lhs First string to compare. 838 | @param rhs Second string to compare. 839 | @return True if the first one is greater than the second one, false otherwise. 840 | **/ 841 | template 842 | bool operator>(const String& lhs, const std::u8string& rhs) 843 | { 844 | return lhs.buffer > rhs; 845 | } 846 | 847 | 848 | /** 849 | Checking whether the first string is less or equal than the second one. 850 | 851 | @param lhs First string to compare. 852 | @param rhs Second string to compare. 853 | @return True if the first one is less or equal than the second one, false otherwise. 854 | **/ 855 | template 856 | bool operator<=(const String& lhs, const std::u8string& rhs) 857 | { 858 | return !operator>(rhs, lhs); 859 | } 860 | 861 | 862 | /** 863 | Checking whether the first string is greater or equal than the second one. 864 | 865 | @param lhs First string to compare. 866 | @param rhs Second string to compare. 867 | @return True if the first one is greater or equal than the second one, false otherwise. 868 | **/ 869 | template 870 | bool operator>=(const String& lhs, const std::u8string& rhs) 871 | { 872 | return !operator<(rhs, lhs); 873 | } 874 | 875 | #endif 876 | 877 | 878 | } // namespace 879 | 880 | 881 | #ifdef _MSC_VER 882 | #pragma warning(pop) 883 | #endif 884 | -------------------------------------------------------------------------------- /include/mailio/dialog.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | dialog.hpp 4 | ---------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "export.hpp" 26 | 27 | 28 | namespace mailio 29 | { 30 | 31 | 32 | /** 33 | Dealing with network in a line oriented fashion. 34 | **/ 35 | class dialog : public std::enable_shared_from_this 36 | { 37 | public: 38 | 39 | /** 40 | Making a connection to the server. 41 | 42 | @param hostname Server hostname. 43 | @param port Server port. 44 | @param timeout Network timeout after which I/O operations fail. If zero, then no timeout is set i.e. I/O operations are synchronous. 45 | @throw dialog_error Server connecting failed. 46 | @throw * `connect_async()`. 47 | **/ 48 | dialog(const std::string& hostname, unsigned port, std::chrono::milliseconds timeout); 49 | 50 | /** 51 | Copy constructor. 52 | 53 | @param other Object to copy. 54 | **/ 55 | dialog(const dialog& other); 56 | 57 | /** 58 | Closing the connection. 59 | **/ 60 | virtual ~dialog() = default; 61 | 62 | dialog(dialog&&) = delete; 63 | 64 | void operator=(const dialog&) = delete; 65 | 66 | void operator=(dialog&&) = delete; 67 | 68 | virtual void connect(); 69 | 70 | /** 71 | Sending a line to network synchronously or asynchronously, depending of the timeout value. 72 | 73 | @param line Line to send. 74 | @throw * `send_sync(Socket&, const std::string&)`, `send_async(Socket&, const std::string&)`. 75 | **/ 76 | virtual void send(const std::string& line); 77 | 78 | /** 79 | Receiving a line from network. 80 | 81 | @param raw Flag if the receiving is raw (no CRLF is truncated) or not. 82 | @return Line read from network. 83 | @throw * `receive_sync(Socket&, bool)`, `receive_async(Socket&, bool)`. 84 | **/ 85 | virtual std::string receive(bool raw = false); 86 | 87 | protected: 88 | 89 | /** 90 | Sending a line to network in synchronous manner. 91 | 92 | @param socket Socket to use for I/O. 93 | @param line Line to send. 94 | @throw dialog_error Network sending error. 95 | **/ 96 | template 97 | void send_sync(Socket& socket, const std::string& line); 98 | 99 | /** 100 | Receiving a line from network in synchronous manner. 101 | 102 | @param socket Socket to use for I/O. 103 | @param raw Flag if the receiving is raw (no CRLF is truncated) or not. 104 | @return line Line received. 105 | @throw dialog_error Network sending error. 106 | **/ 107 | template 108 | std::string receive_sync(Socket& socket, bool raw); 109 | 110 | /** 111 | Connecting to the host within the given timeout period. 112 | 113 | @throw dialog_error Server connecting failed. 114 | @throw dialog_error Server connecting timed out. 115 | **/ 116 | void connect_async(); 117 | 118 | /** 119 | Sending a line over network within the given timeout period. 120 | 121 | @param socket Socket to use for I/O. 122 | @param line Line to send. 123 | @throw dialog_error Network sending failed. 124 | @throw dialog_error Network sending timed out. 125 | **/ 126 | template 127 | void send_async(Socket& socket, std::string line); 128 | 129 | /** 130 | Receiving a line over network within the given timeout period. 131 | 132 | @param socket Socket to use for I/O. 133 | @param raw Flag if the receiving is raw (no CRLF is truncated) or not. 134 | @return line Line received. 135 | @throw dialog_error Network receiving failed. 136 | @throw dialog_error Network receiving timed out. 137 | **/ 138 | template 139 | std::string receive_async(Socket& socket, bool raw); 140 | 141 | 142 | /** 143 | Waiting for an asynchronous, reporting an error when the timer expires. 144 | 145 | @param has_op Asynchronous operation flag whether it finishes. 146 | @param op_error Flag whether an operation encountered an error. 147 | @param expired_msg Message when an operation times out. 148 | @param op_msg Message when a network operation fails. 149 | @todo `op_error` becomes redundant by `error`. 150 | **/ 151 | void wait_async(const bool& has_op, const bool& op_error, const char* expired_msg, const char* op_msg, 152 | const boost::system::error_code& error); 153 | 154 | /** 155 | Checking if the timeout is reached. 156 | **/ 157 | void check_timeout(); 158 | 159 | /** 160 | Timeout handler which sets the timer flag to expired. 161 | 162 | @param error Result of the asynchronous operation. 163 | **/ 164 | void timeout_handler(const boost::system::error_code& error); 165 | 166 | /** 167 | Server hostname. 168 | **/ 169 | const std::string hostname_; 170 | 171 | /** 172 | Server port. 173 | **/ 174 | const unsigned int port_; 175 | 176 | /** 177 | Asio input/output service. 178 | **/ 179 | static boost::asio::io_context ios_; 180 | 181 | /** 182 | Socket connection. 183 | **/ 184 | std::shared_ptr socket_; 185 | 186 | /** 187 | Timer to check the timeout. 188 | **/ 189 | std::shared_ptr timer_; 190 | 191 | /** 192 | Timeout on I/O operations in milliseconds. 193 | **/ 194 | std::chrono::milliseconds timeout_; 195 | 196 | /** 197 | Flag to show whether the timeout has expired. 198 | 199 | @todo Should be atomic? 200 | **/ 201 | bool timer_expired_; 202 | 203 | /** 204 | Stream buffer associated to the socket. 205 | **/ 206 | std::shared_ptr strmbuf_; 207 | 208 | /** 209 | Input stream associated to the buffer. 210 | **/ 211 | std::shared_ptr istrm_; 212 | }; 213 | 214 | 215 | /** 216 | Secure version of `dialog` class. 217 | **/ 218 | class dialog_ssl : public dialog 219 | { 220 | public: 221 | 222 | /** 223 | SSL options to set on a socket. 224 | **/ 225 | struct ssl_options_t 226 | { 227 | /** 228 | Methods as supported by Asio. 229 | **/ 230 | boost::asio::ssl::context::method method; 231 | 232 | /** 233 | Peer verification bitmask supported by Asio. 234 | **/ 235 | boost::asio::ssl::verify_mode verify_mode; 236 | }; 237 | 238 | /** 239 | Making a connection to the server. 240 | 241 | @param hostname Server hostname. 242 | @param port Server port. 243 | @param timeout Network timeout after which I/O operations fail. If zero, then no timeout is set i.e. I/O operations are synchronous. 244 | @param options SSL options to set. 245 | @throw * `dialog::dialog(const std::string&, unsigned)`. 246 | **/ 247 | dialog_ssl(const std::string& hostname, unsigned port, std::chrono::milliseconds timeout, const ssl_options_t& options); 248 | 249 | /** 250 | Calling the parent constructor, initializing the SSL socket. 251 | 252 | @param other Plain connection to use for the SSL. 253 | @param options SSL options to set. 254 | **/ 255 | dialog_ssl(const dialog& other, const ssl_options_t& options); 256 | 257 | /** 258 | Default copy constructor. 259 | **/ 260 | dialog_ssl(const dialog_ssl&) = default; 261 | 262 | /** 263 | Default destructor. 264 | **/ 265 | virtual ~dialog_ssl() = default; 266 | 267 | dialog_ssl(dialog_ssl&&) = delete; 268 | 269 | void operator=(const dialog_ssl&) = delete; 270 | 271 | void operator=(dialog_ssl&&) = delete; 272 | 273 | /** 274 | Sending an encrypted or unecrypted line, depending of SSL flag. 275 | 276 | @param line Line to send. 277 | @throw * `dialog::send(const std::string&)`, `send_sync(Socket&, const std::string&)`, `send_async(Socket&, const std::string&)`. 278 | **/ 279 | void send(const std::string& line); 280 | 281 | /** 282 | Receiving an encrypted or unecrypted line, depending of SSL state. 283 | 284 | @param raw Flag if the receiving is raw (no CRLF is truncated) or not. 285 | @return Line read from network 286 | @throw * `dialog::receive()`, `receive_sync(Socket&, bool)`, `receive_async(Socket&, bool)`. 287 | **/ 288 | std::string receive(bool raw = false); 289 | 290 | protected: 291 | 292 | /** 293 | Flag if SSL is chosen or not. 294 | **/ 295 | bool ssl_; 296 | 297 | /** 298 | SSL context (when used). 299 | **/ 300 | std::shared_ptr context_; 301 | 302 | /** 303 | SSL socket (when used). 304 | **/ 305 | std::shared_ptr> ssl_socket_; 306 | }; 307 | 308 | 309 | /** 310 | Error thrown by `dialog` client. 311 | **/ 312 | class dialog_error : public std::runtime_error 313 | { 314 | public: 315 | 316 | /** 317 | Calling the parent constructor. 318 | 319 | @param msg Error message. 320 | @param details Detailed message. 321 | **/ 322 | dialog_error(const std::string& msg, const std::string& details) : std::runtime_error(msg), details_(details) 323 | { 324 | } 325 | 326 | /** 327 | Calling the parent constructor. 328 | 329 | @param msg Error message. 330 | @param details Detailed message. 331 | **/ 332 | dialog_error(const char* msg, const std::string& details) : std::runtime_error(msg), details_(details) 333 | { 334 | } 335 | 336 | dialog_error(const dialog_error&) = default; 337 | 338 | dialog_error(dialog_error&&) = default; 339 | 340 | ~dialog_error() = default; 341 | 342 | dialog_error& operator=(const dialog_error&) = default; 343 | 344 | dialog_error& operator=(dialog_error&&) = default; 345 | 346 | /** 347 | Gets the detailed error message. 348 | 349 | @return Detailed error message. 350 | **/ 351 | std::string details() const; 352 | 353 | protected: 354 | 355 | /** 356 | Message provided by Asio. 357 | **/ 358 | std::string details_; 359 | }; 360 | 361 | 362 | } // namespace mailio 363 | -------------------------------------------------------------------------------- /include/mailio/mailboxes.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | mailboxes.hpp 4 | ------------- 5 | 6 | Copyright (C) 2017, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #ifdef _MSC_VER 17 | #pragma warning(push) 18 | #pragma warning(disable:4251) 19 | #endif 20 | 21 | #include 22 | #include 23 | #include "codec.hpp" 24 | #include "export.hpp" 25 | 26 | namespace mailio 27 | { 28 | 29 | 30 | /** 31 | Mail as name and address. 32 | **/ 33 | struct MAILIO_EXPORT mail_address 34 | { 35 | /** 36 | Name part of the mail. 37 | **/ 38 | string_t name; 39 | 40 | /** 41 | Address part of the mail. 42 | **/ 43 | std::string address; 44 | 45 | /** 46 | Default constructor. 47 | **/ 48 | mail_address() = default; 49 | 50 | /** 51 | Setting a mail name and address. 52 | 53 | @param mail_name Name to set. 54 | @param mail_address Address to set. 55 | **/ 56 | mail_address(const string_t& mail_name, const std::string& mail_address); 57 | 58 | /** 59 | Checking if a mail is empty, i.e. name and address are empty. 60 | 61 | @return True if empty, false if not. 62 | **/ 63 | bool empty() const; 64 | 65 | /** 66 | Clearing name and address. 67 | **/ 68 | void clear(); 69 | }; 70 | 71 | 72 | /** 73 | Mail group with the name and members. 74 | **/ 75 | struct MAILIO_EXPORT mail_group 76 | { 77 | /** 78 | Mail group name. 79 | **/ 80 | std::string name; 81 | 82 | /** 83 | Mail group members. 84 | **/ 85 | std::vector members; 86 | 87 | /** 88 | Default constructor. 89 | **/ 90 | mail_group() = default; 91 | 92 | /** 93 | Setting a group name and members. 94 | 95 | @param group_name Name of a group. 96 | @param group_mails Members of a group. 97 | **/ 98 | mail_group(const std::string& group_name, const std::vector& group_mails); 99 | 100 | /** 101 | Default destructor. 102 | **/ 103 | ~mail_group() = default; 104 | 105 | /** 106 | Adding a list of mails to members. 107 | 108 | @param mails Mail list to add. 109 | **/ 110 | void add(const std::vector& mails); 111 | 112 | /** 113 | Adding a mail to members. 114 | 115 | @param mail Mail to add. 116 | **/ 117 | void add(const mail_address& mail); 118 | 119 | /** 120 | Clearing the group name and members. 121 | **/ 122 | void clear(); 123 | }; 124 | 125 | 126 | /** 127 | List of mail addresses and groups. 128 | **/ 129 | struct MAILIO_EXPORT mailboxes 130 | { 131 | /** 132 | Mail addresses. 133 | **/ 134 | std::vector addresses; 135 | 136 | /** 137 | Mail groups. 138 | **/ 139 | std::vector groups; 140 | 141 | /** 142 | Default constructor. 143 | **/ 144 | mailboxes() = default; 145 | 146 | /** 147 | Default copy constructor. 148 | **/ 149 | mailboxes(const mailboxes&) = default; 150 | 151 | /** 152 | Default move constructor. 153 | **/ 154 | mailboxes(mailboxes&&) = default; 155 | 156 | /** 157 | Creating a mailbox with the given addresses and groups. 158 | 159 | @param address_list Mail addresses to set. 160 | @param group_list Mail groups to set. 161 | **/ 162 | mailboxes(std::vector address_list, std::vector group_list); 163 | 164 | /** 165 | Default destructor. 166 | **/ 167 | ~mailboxes() = default; 168 | 169 | /** 170 | Default copy assignment operator. 171 | **/ 172 | mailboxes& operator=(const mailboxes&) = default; 173 | 174 | /** 175 | Default move assignment operator. 176 | **/ 177 | mailboxes& operator=(mailboxes&&) = default; 178 | 179 | /** 180 | Checking if the mailbox is empty. 181 | 182 | @return True if empty, false if not. 183 | **/ 184 | bool empty() const; 185 | 186 | /** 187 | Clearing the list of addresses. 188 | **/ 189 | void clear(); 190 | }; 191 | 192 | 193 | } // namespace mailio 194 | 195 | 196 | #ifdef _MSC_VER 197 | #pragma warning(pop) 198 | #endif 199 | -------------------------------------------------------------------------------- /include/mailio/message.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | message.hpp 4 | ----------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #ifdef _MSC_VER 17 | #pragma warning(push) 18 | #pragma warning(disable:4251) 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "q_codec.hpp" 31 | #include "mime.hpp" 32 | #include "mailboxes.hpp" 33 | #include "export.hpp" 34 | 35 | 36 | namespace mailio 37 | { 38 | 39 | /** 40 | Options to customize the formatting of a message. Used by message::format(). 41 | **/ 42 | struct message_format_options_t 43 | { 44 | /** 45 | Flag if the leading dot should be escaped. 46 | **/ 47 | bool dot_escape = false; 48 | 49 | /** 50 | Flag whether bcc addresses should be added. 51 | **/ 52 | bool add_bcc_header = false; 53 | }; 54 | 55 | 56 | /** 57 | Mail message and applied parsing/formatting algorithms. 58 | **/ 59 | class MAILIO_EXPORT message : public mime 60 | { 61 | public: 62 | 63 | /** 64 | Character to separate mail addresses in a list. 65 | **/ 66 | static const char ADDRESS_SEPARATOR = ','; 67 | 68 | /** 69 | Mail group name separator from the list of addresses. 70 | **/ 71 | static const char MAILGROUP_NAME_SEPARATOR = ':'; 72 | 73 | /** 74 | Separator of several mail groups. 75 | **/ 76 | static const char MAILGROUP_SEPARATOR = ';'; 77 | 78 | /** 79 | Calling parent destructor, initializing date and time to local time in utc time zone, other members set to default. 80 | **/ 81 | message(); 82 | 83 | /** 84 | Default copy constructor. 85 | **/ 86 | message(const message&) = default; 87 | 88 | /** 89 | Default move constructor. 90 | 91 | @todo Default implementation is probably a bug, but not manifested yet. 92 | **/ 93 | message(message&&) = default; 94 | 95 | /** 96 | Default destructor. 97 | **/ 98 | ~message() = default; 99 | 100 | /** 101 | Default assignment operator. 102 | **/ 103 | message& operator=(const message&) = default; 104 | 105 | /** 106 | Default move assignment operator. 107 | 108 | @todo Default implementation is probably a bug, but not manifested yet. 109 | **/ 110 | message& operator=(message&&) = default; 111 | 112 | /** 113 | Formatting the message to a string. 114 | 115 | If a line contains leading dot, then it can be escaped as required by mail protocols. 116 | 117 | @param message_str Resulting message as string. 118 | @param opts Options to customize formatting. 119 | @throw * `format_header(format_options)`, `format_content(bool)`, `mime::format(string&, bool)`. 120 | **/ 121 | void format(std::string& message_str, const message_format_options_t& opts = message_format_options_t{}) const; 122 | 123 | /** 124 | Overload of `format(string&, const message_format_options&)`. 125 | 126 | Because of the way the u8string is comverted to string, it's more expensive when used with C++20. 127 | **/ 128 | #if defined(__cpp_char8_t) 129 | void format(std::u8string& message_str, const message_format_options_t& = message_format_options_t{}) const; 130 | #endif 131 | 132 | /** 133 | Parsing a message from a string. 134 | 135 | Essentially, the method calls the same one from `mime` and checks for errors. 136 | 137 | @param message_str String to parse. 138 | @param dot_escape Flag if the leading dot should be escaped. 139 | @throw message_error No author address. 140 | @throw * `mime::parse(const string&, bool)`. 141 | **/ 142 | void parse(const std::string& message_str, bool dot_escape = false); 143 | 144 | /** 145 | Overload of `parse(const string&, bool)`. 146 | 147 | Because of the way the u8string is comverted to string, it's more expensive when used with C++20. 148 | **/ 149 | #if defined(__cpp_char8_t) 150 | void parse(const std::u8string& mime_string, bool dot_escape = false); 151 | #endif 152 | 153 | /** 154 | Checking if the mail is empty. 155 | 156 | @return True if empty, false if not. 157 | **/ 158 | bool empty() const; 159 | 160 | /** 161 | Setting the author to a given address. 162 | 163 | The given address is set as the only one, others are deleted. 164 | 165 | @param mail Mail address to set. 166 | **/ 167 | void from(const mail_address& mail); 168 | 169 | /** 170 | Getting the author address. 171 | 172 | @return Author mail address. 173 | **/ 174 | mailboxes from() const; 175 | 176 | /** 177 | Adding an addrress to the author field. 178 | 179 | @param mail Mail address to set. 180 | **/ 181 | void add_from(const mail_address& mail); 182 | 183 | /** 184 | Formatting the author as string. 185 | 186 | @return Author name and address as formatted string. 187 | @throw * `format_address(const string&, const string&)`. 188 | **/ 189 | std::string from_to_string() const; 190 | 191 | /** 192 | Setting the sender to the given address. 193 | 194 | @param mail Mail address to set. 195 | **/ 196 | void sender(const mail_address& mail); 197 | 198 | /** 199 | Getting the sender address. 200 | 201 | @return Sender mail address. 202 | **/ 203 | mail_address sender() const; 204 | 205 | /** 206 | Formatting the sender as string. 207 | 208 | @return Sender name and address as formatted string. 209 | @throw * `format_address(const string&, const string&)`. 210 | **/ 211 | std::string sender_to_string() const; 212 | 213 | /** 214 | Setting the reply address. 215 | 216 | @param mail Reply mail address. 217 | **/ 218 | void reply_address(const mail_address& mail); 219 | 220 | /** 221 | Getting the reply address. 222 | 223 | @return Reply mail address. 224 | **/ 225 | mail_address reply_address() const; 226 | 227 | /** 228 | Formatting the reply name and address as string. 229 | 230 | @return Reply name and address as string. 231 | @throw * `format_address(const string&, const string&)`. 232 | **/ 233 | std::string reply_address_to_string() const; 234 | 235 | /** 236 | Adding a recipent name and address. 237 | 238 | @param mail Address to add. 239 | **/ 240 | void add_recipient(const mail_address& mail); 241 | 242 | /** 243 | Adding a recipient group. 244 | 245 | @param group Group to add. 246 | **/ 247 | void add_recipient(const mail_group& group); 248 | 249 | /** 250 | Getting the recipients. 251 | 252 | @return List of recipients. 253 | **/ 254 | mailboxes recipients() const; 255 | 256 | /** 257 | Getting the recipients names and addresses as string. 258 | 259 | @return Recipients names and addresses as string. 260 | @throw * `format_mailbox`. 261 | **/ 262 | std::string recipients_to_string() const; 263 | 264 | /** 265 | Adding a CC recipent name and address. 266 | 267 | @param mail Mail address to set. 268 | **/ 269 | void add_cc_recipient(const mail_address& mail); 270 | 271 | /** 272 | Adding a CC recipient group. 273 | 274 | @param group Group to add. 275 | **/ 276 | void add_cc_recipient(const mail_group& group); 277 | 278 | /** 279 | Getting the CC recipients names and addresses. 280 | 281 | @return List of CC recipients. 282 | **/ 283 | mailboxes cc_recipients() const; 284 | 285 | /** 286 | Getting the CC recipients names and addresses as string. 287 | 288 | @return CC recipients names and addresses as string. 289 | @throw * `format_mailbox`. 290 | **/ 291 | std::string cc_recipients_to_string() const; 292 | 293 | /** 294 | Adding a BCC recipent name and address. 295 | 296 | @param mail Mail address to set. 297 | **/ 298 | void add_bcc_recipient(const mail_address& mail); 299 | 300 | /** 301 | Adding a BCC recipient group. 302 | 303 | @param group Group to add. 304 | **/ 305 | void add_bcc_recipient(const mail_group& group); 306 | 307 | /** 308 | Getting the BCC recipients names and addresses. 309 | 310 | @return List of BCC recipients. 311 | **/ 312 | mailboxes bcc_recipients() const; 313 | 314 | /** 315 | Getting the BCC recipients names and addresses as string. 316 | 317 | @return BCC recipients names and addresses as string. 318 | @throw * `format_mailbox`. 319 | **/ 320 | std::string bcc_recipients_to_string() const; 321 | 322 | /** 323 | Setting the disposition notification mail address. 324 | 325 | @param mail Mail address to set. 326 | **/ 327 | void disposition_notification(const mail_address& mail); 328 | 329 | /** 330 | Getting the disposition notification mail address. 331 | 332 | @return Dispostion notification mail address. 333 | **/ 334 | mail_address disposition_notification() const; 335 | 336 | /** 337 | Getting the disposition notification mail address as string. 338 | 339 | @return Disposition notification mail address as string. 340 | @throw * `format_address(const string&, const string&)`. 341 | **/ 342 | std::string disposition_notification_to_string() const; 343 | 344 | /** 345 | Setting the message ID. 346 | 347 | @param id The message ID in the format `string1@string2`. 348 | @throw message_error Invalid message ID. 349 | **/ 350 | void message_id(std::string id); 351 | 352 | /** 353 | Getting the message ID. 354 | 355 | @return Message ID. 356 | **/ 357 | std::string message_id() const; 358 | 359 | /** 360 | Adding the in-reply-to ID. 361 | 362 | @param in-reply ID of the in-reply-to header. 363 | **/ 364 | void add_in_reply_to(const std::string& in_reply); 365 | 366 | /** 367 | Getting the in-reply-to ID. 368 | 369 | @return List of in-reply-to IDs. 370 | **/ 371 | std::vector in_reply_to() const; 372 | 373 | /** 374 | Adding the reference ID to the list. 375 | 376 | @param reference_id Reference ID. 377 | **/ 378 | void add_references(const std::string& reference_id); 379 | 380 | /** 381 | Getting the references list of IDs. 382 | 383 | @return List of references IDs. 384 | **/ 385 | std::vector references() const; 386 | 387 | /** 388 | Setting the subject. 389 | 390 | @param mail_subject Subject to set. 391 | @param sub_codec Codec of the subject to use. 392 | */ 393 | void subject(const std::string& mail_subject, codec::codec_t sub_codec = codec::codec_t::ASCII); 394 | 395 | /** 396 | Setting the raw subject. 397 | 398 | @param mail_subject Subject to set. 399 | */ 400 | void subject_raw(const string_t& mail_subject); 401 | 402 | #if defined(__cpp_char8_t) 403 | 404 | /** 405 | Setting the subject. 406 | 407 | @param mail_subject Subject to set. 408 | @param sub_codec Codec of the subject to use. 409 | */ 410 | void subject(const std::u8string& mail_subject, codec::codec_t sub_codec = codec::codec_t::ASCII); 411 | 412 | /** 413 | Setting the raw subject. 414 | 415 | @param mail_subject Subject to set. 416 | */ 417 | void subject_raw(const u8string_t& mail_subject); 418 | #endif 419 | 420 | /** 421 | Getting the subject. 422 | 423 | @return Subject value. 424 | **/ 425 | std::string subject() const; 426 | 427 | /** 428 | Getting the raw subject. 429 | 430 | @return Subject value. 431 | **/ 432 | string_t subject_raw() const; 433 | 434 | /** 435 | Getting the date, time and zone. 436 | 437 | @return Date, time and zone. 438 | **/ 439 | boost::local_time::local_date_time date_time() const; 440 | 441 | /** 442 | Setting the date, time and zone. 443 | 444 | @param the_date_time Date, time and zone to set. 445 | **/ 446 | void date_time(const boost::local_time::local_date_time& mail_dt); 447 | 448 | /** 449 | Attaching a list of streams. 450 | 451 | If the content is set, attaching a file moves the content to the first MIME part. Thus, the content and the attached files are MIME parts, as described in 452 | RFC 2046 section 5.1. The consequence is that the content remains empty afterwards. 453 | 454 | @param attachments Files to attach. Each tuple consists of a stream, attachment name and content type. 455 | @throw * `mime::content_type(const content_type_t&)`, `mime::content_transfer_encoding(content_transfer_encoding_t)`, 456 | `mime::content_disposition(content_disposition_t)`. 457 | **/ 458 | void attach(const std::list>& attachments); 459 | 460 | /** 461 | Getting the number of attachments. 462 | 463 | @return Number of attachments. 464 | **/ 465 | std::size_t attachments_size() const; 466 | 467 | /** 468 | Getting the attachment at the given index. 469 | 470 | @param index Index of the attachment. 471 | @param att_strm Stream to write the attachment. 472 | @param att_name Name of the attachment. 473 | @throw message_error Bad attachment index. 474 | @todo The attachment name should be also `string_t`. 475 | **/ 476 | void attachment(std::size_t index, std::ostream& att_strm, string_t& att_name) const; 477 | 478 | /** 479 | Adding another header. 480 | 481 | Adding a header defined by other methods leads to the undefined behaviour. 482 | 483 | @param name Header name. 484 | @param value Header value. 485 | @todo Disallowing standard headers defined elsewhere? 486 | **/ 487 | void add_header(const std::string& name, const std::string& value); 488 | 489 | /** 490 | Removing another header. 491 | 492 | Removing a header defined by other methods leads to the undefined behaviour. 493 | 494 | @param name Header to remove. 495 | **/ 496 | void remove_header(const std::string& name); 497 | 498 | /** 499 | Returning the other headers. 500 | 501 | @return Message headers. 502 | **/ 503 | const headers_t& headers() const; 504 | 505 | protected: 506 | 507 | /** 508 | Printable ASCII characters without the alphanumerics, double quote, comma, colon, semicolon, angle and square brackets and monkey. 509 | **/ 510 | static const std::string ATEXT; 511 | 512 | /** 513 | Printable ASCII characters without the alphanumerics, brackets and backslash. 514 | **/ 515 | static const std::string DTEXT; 516 | 517 | /** 518 | `From` header name. 519 | **/ 520 | static const std::string FROM_HEADER; 521 | 522 | /** 523 | `Sender` header name. 524 | **/ 525 | static const std::string SENDER_HEADER; 526 | 527 | /** 528 | `Reply-To` header name. 529 | **/ 530 | static const std::string REPLY_TO_HEADER; 531 | 532 | /** 533 | `To` header name. 534 | **/ 535 | static const std::string TO_HEADER; 536 | 537 | /** 538 | `Cc` header name. 539 | **/ 540 | static const std::string CC_HEADER; 541 | 542 | /** 543 | `Bcc` header name. 544 | **/ 545 | static const std::string BCC_HEADER; 546 | 547 | /** 548 | `Message-ID` header name. 549 | **/ 550 | static const std::string MESSAGE_ID_HEADER; 551 | 552 | /** 553 | `In-Reply-To` header name. 554 | **/ 555 | static const std::string IN_REPLY_TO_HEADER; 556 | 557 | /** 558 | `References` header name. 559 | **/ 560 | static const std::string REFERENCES_HEADER; 561 | 562 | /** 563 | Subject header name. 564 | **/ 565 | static const std::string SUBJECT_HEADER; 566 | 567 | /** 568 | Date header name. 569 | **/ 570 | static const std::string DATE_HEADER; 571 | 572 | /** 573 | Disposition notification header name. 574 | **/ 575 | static const std::string DISPOSITION_NOTIFICATION_HEADER; 576 | 577 | /** 578 | Mime version header name. 579 | **/ 580 | static const std::string MIME_VERSION_HEADER; 581 | 582 | /** 583 | Formatting the header to a string. 584 | 585 | @return Header as string. 586 | @throw message_error No boundary for multipart message. 587 | @throw message_error No author. 588 | @throw message_error No sender for multiple authors. 589 | @throw * `mime::format_header()`. 590 | **/ 591 | virtual std::string format_header(bool add_bcc_header) const; 592 | 593 | /** 594 | Parsing a header line for a specific header. 595 | 596 | @param header_line Header line to be parsed. 597 | @throw message_error Line policy overflow in a header. 598 | @throw message_error Empty author header. 599 | @throw * `mime::parse_header_line(const string&)`, `mime::parse_header_name_value(const string&, string&, string&)`, 600 | `parse_address_list(const string&)`, `parse_subject(const string&)`, `parse_date(const string&)`. 601 | **/ 602 | virtual void parse_header_line(const std::string& header_line); 603 | 604 | /** 605 | Formatting a list of addresses to string. 606 | 607 | Multiple addresses are put into separate lines. 608 | 609 | @param mailbox_list Mailbox to format. 610 | @return Mailbox as string. 611 | @throw message_error Formatting failure of address list, bad group name. 612 | @throw * `format_address(const string&, const string&)`. 613 | **/ 614 | std::string format_address_list(const mailboxes& mailbox_list, const std::string& header_name = "") const; 615 | 616 | /** 617 | Formatting a name and an address. 618 | 619 | If the name is in ASCII or the header codec set to UTF8, then it is written in raw format. Otherwise, the encoding is performed. The header folding is 620 | performed if necessary. 621 | 622 | @param name Mail name. 623 | @param address Mail address. 624 | @param header_name Header name of the address header. 625 | @return The mail name and address formatted. 626 | @throw message_error Formatting failure of name. 627 | @throw message_error Formatting failure of address. 628 | **/ 629 | std::string format_address(const string_t& name, const std::string& address, const std::string& header_name) const; 630 | 631 | /** 632 | Formatting the subject which can be ASCII or UTF-8. 633 | 634 | @return Formatted subject. 635 | **/ 636 | std::string format_subject() const; 637 | 638 | /** 639 | Formatting email date. 640 | 641 | @return Date for the email format. 642 | **/ 643 | std::string format_date() const; 644 | 645 | /** 646 | Parsing a string into vector of names and addresses. 647 | 648 | @param address_list String to parse. 649 | @return Vector of names and addresses. 650 | @throw message_error Parsing failure of address or group at. 651 | @throw message_error Parsing failure of group at. 652 | @throw message_error Parsing failure of name or address at. 653 | @throw message_error Parsing failure of address at. 654 | @throw message_error Parsing failure of name at. 655 | @throw message_error Parsing failure of comment at. 656 | **/ 657 | mailboxes parse_address_list(const std::string& address_list); 658 | 659 | /** 660 | Parsing a string into date and time. 661 | 662 | @param date_str Date string to parse. 663 | @return Date and time translated to local time zone. 664 | @throw message_error Parsing failure of date. 665 | **/ 666 | boost::local_time::local_date_time parse_date(const std::string& date_str) const; 667 | 668 | /** 669 | Splitting string with Q encoded fragments into separate strings. 670 | 671 | @param text String with Q encoded fragments. 672 | @return Q encoded fragments as separate strings. 673 | **/ 674 | static std::vector split_qc_string(const std::string& text); 675 | 676 | /** 677 | Parsing a subject which can be ASCII or UTF-8. 678 | 679 | The result is string either ASCII or UTF-8 encoded. If another encoding is used like ISO-8859-X, then the result is undefined. 680 | 681 | @param subject Subject to parse. 682 | @return Parsed subject and charset. 683 | @throw message_error Parsing failure of Q encoding. 684 | @throw * `q_codec::decode(const string&)`. 685 | **/ 686 | std::tuple 687 | parse_subject(const std::string& subject); 688 | 689 | /** 690 | Parsing a name part of a mail ASCII or UTF-8 encoded. 691 | 692 | The result is string ASCII or UTF-8 encoded. If another encoding is used, then it should be decoded by the method caller. 693 | 694 | @param address_name Name part of mail. 695 | @return Parsed name part of the address. 696 | @throw message_error Inconsistent Q encodings. 697 | @todo Not tested with charsets different than ASCII and UTF-8. 698 | @todo Throwing errors when Q codec is invalid? 699 | **/ 700 | string_t parse_address_name(const std::string& address_name); 701 | 702 | /** 703 | From name and address. 704 | **/ 705 | mailboxes from_; 706 | 707 | /** 708 | Sender name and address. 709 | **/ 710 | mail_address sender_; 711 | 712 | /** 713 | Reply address. 714 | **/ 715 | mail_address reply_address_; 716 | 717 | /** 718 | List of recipients. 719 | **/ 720 | mailboxes recipients_; 721 | 722 | /** 723 | List of CC recipients. 724 | **/ 725 | mailboxes cc_recipients_; 726 | 727 | /** 728 | List of BCC recipients. 729 | **/ 730 | mailboxes bcc_recipients_; 731 | 732 | /** 733 | Disposition notification address. 734 | **/ 735 | mail_address disposition_notification_; 736 | 737 | /** 738 | Message ID. 739 | **/ 740 | std::string message_id_; 741 | 742 | /** 743 | In reply to list of IDs. 744 | **/ 745 | std::vector in_reply_to_; 746 | 747 | /** 748 | References list of IDs. 749 | **/ 750 | std::vector references_; 751 | 752 | /** 753 | Message subject. 754 | **/ 755 | string_t subject_; 756 | 757 | /** 758 | Message date and time with time zone. 759 | **/ 760 | boost::local_time::local_date_time date_time_; 761 | 762 | /** 763 | Other headers not included into the known ones. 764 | **/ 765 | headers_t headers_; 766 | }; 767 | 768 | [[deprecated]] 769 | typedef mime_error message_error; 770 | 771 | 772 | } // namespace mailio 773 | 774 | 775 | #ifdef _MSC_VER 776 | #pragma warning(pop) 777 | #endif 778 | -------------------------------------------------------------------------------- /include/mailio/percent.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | percent.hpp 4 | ----------- 5 | 6 | Copyright (C) 2024, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #include 17 | #include 18 | #include "codec.hpp" 19 | #include "export.hpp" 20 | 21 | 22 | namespace mailio 23 | { 24 | 25 | 26 | /** 27 | Percent encoding and decoding as described in RFC 2231 section 4. 28 | 29 | @todo Line policies not implemented. 30 | **/ 31 | class MAILIO_EXPORT percent : public codec 32 | { 33 | public: 34 | 35 | /** 36 | Setting the encoder and decoder line policies. 37 | 38 | @param line1_policy First line policy to set. 39 | @param lines_policy Other lines policy than the first one to set. 40 | **/ 41 | percent(std::string::size_type line1_policy, std::string::size_type lines_policy); 42 | 43 | percent(const percent&) = delete; 44 | 45 | percent(percent&&) = delete; 46 | 47 | /** 48 | Default destructor. 49 | **/ 50 | ~percent() = default; 51 | 52 | void operator=(const percent&) = delete; 53 | 54 | void operator=(percent&&) = delete; 55 | 56 | /** 57 | Encoding a string. 58 | 59 | @param txt String to encode. 60 | @return Encoded string. 61 | @todo Implement the line policies. 62 | @todo Replace `txt` to be `string_t`, then no need for the charset parameter. 63 | **/ 64 | std::vector encode(const std::string& txt, const std::string& charset) const; 65 | 66 | /** 67 | Decoding a percent encoded string. 68 | 69 | @param txt String to decode. 70 | @return Decoded string. 71 | @todo Implement the line policies. 72 | **/ 73 | std::string decode(const std::string& txt) const; 74 | }; 75 | 76 | 77 | } // namespace mailio 78 | -------------------------------------------------------------------------------- /include/mailio/pop3.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | pop3.hpp 4 | -------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #ifdef _MSC_VER 17 | #pragma warning(push) 18 | #pragma warning(disable:4251) 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "dialog.hpp" 31 | #include "message.hpp" 32 | #include "export.hpp" 33 | 34 | 35 | namespace mailio 36 | { 37 | 38 | 39 | /** 40 | POP3 client implementation. 41 | **/ 42 | class MAILIO_EXPORT pop3 43 | { 44 | public: 45 | 46 | /** 47 | Available authentication methods. 48 | 49 | The following mechanisms are allowed: 50 | - LOGIN: The username and password are sent in plain format. 51 | **/ 52 | enum class auth_method_t {LOGIN}; 53 | 54 | /** 55 | Messages indexed by their order number containing sizes in octets. 56 | **/ 57 | typedef std::map message_list_t; 58 | 59 | /** 60 | Message order numbers and their corresponding unique IDs (a UIDL response from server). 61 | **/ 62 | typedef std::map uidl_list_t; 63 | 64 | /** 65 | Mailbox statistics structure. 66 | **/ 67 | struct mailbox_stat_t 68 | { 69 | /** 70 | Number of messages in the mailbox. 71 | **/ 72 | unsigned int messages_no; 73 | 74 | /** 75 | Size of the mailbox. 76 | **/ 77 | unsigned long mailbox_size; 78 | 79 | /** 80 | Setting the number of messages and mailbox size to zero. 81 | **/ 82 | mailbox_stat_t() : messages_no(0), mailbox_size(0) 83 | { 84 | } 85 | }; 86 | 87 | /** 88 | Making connection to a server. 89 | 90 | @param hostname Hostname of the server. 91 | @param port Port of the server. 92 | @param timeout Network timeout after which I/O operations fail. If zero, then no timeout is set i.e. I/O operations are synchronous. 93 | @throw * `dialog::dialog(const string&, unsigned)`. 94 | **/ 95 | pop3(const std::string& hostname, unsigned port, std::chrono::milliseconds timeout = std::chrono::milliseconds(0)); 96 | 97 | /** 98 | Sending the quit command and closing the connection. 99 | **/ 100 | virtual ~pop3(); 101 | 102 | pop3(const pop3&) = delete; 103 | 104 | pop3(pop3&&) = delete; 105 | 106 | void operator=(const pop3&) = delete; 107 | 108 | void operator=(pop3&&) = delete; 109 | 110 | /** 111 | Authentication with the given credentials. 112 | 113 | The method should be called only once on an existing object - it is not possible to authenticate again within the same connection. 114 | 115 | @param username Username to authenticate. 116 | @param password Password to authenticate. 117 | @param method Authentication method to use. 118 | @return The server greeting message. 119 | @throw * `connect()`, `auth_login(const string&, const string&)`. 120 | **/ 121 | std::string authenticate(const std::string& username, const std::string& password, auth_method_t method); 122 | 123 | /** 124 | Listing the size in octets of a message or all messages in a mailbox. 125 | 126 | @param message_no Number of the message to list. If zero, then all messages are listed. 127 | @return Message list. 128 | @throw pop3_error Listing message failure. 129 | @throw pop3_error Listing all messages failure. 130 | @throw pop3_error Parser failure. 131 | @throw * `parse_status(const string&)`, `dialog::send(const string&)`, `dialog::receive()`. 132 | @todo This method is perhaps useless and should be removed. 133 | **/ 134 | message_list_t list(unsigned message_no = 0); 135 | 136 | /** 137 | Getting the unique ID of a message or all messages in a mailbox (UIDL command; might not be supported by server). 138 | 139 | @param message_no Number of the message to get ID for. If zero, then all messages are listed. 140 | @return Uidl list. 141 | @throw pop3_error Listing message failure. 142 | @throw pop3_error Listing all messages failure. 143 | @throw pop3_error Parser failure. 144 | @throw * `parse_status(const string&)`, `dialog::send(const string&)`, `dialog::receive()`. 145 | **/ 146 | uidl_list_t uidl(unsigned message_no = 0); 147 | 148 | /** 149 | Fetching the mailbox statistics. 150 | 151 | @return Number of messages and mailbox size in octets. 152 | @throw pop3_error Reading statistics failure. 153 | @throw pop3_error Parser failure. 154 | @throw * `parse_status(const string&)`, `dialog::send(const string&)`, `dialog::receive()`. 155 | **/ 156 | mailbox_stat_t statistics(); 157 | 158 | /** 159 | Fetching a message. 160 | 161 | The flag for fetching the header only uses a different POP3 command (than for retrieving the full messsage) which is not mandatory by POP3. In case the 162 | command fails, the method will not report an error but rather the `msg` parameter will be empty. 163 | 164 | @param message_no Message number to fetch. 165 | @param msg Fetched message. 166 | @param header_only Flag if only the message header should be fetched. 167 | @throw pop3_error Fetching message failure. 168 | @throw * `parse_status(const string&)`, `dialog::send(const string&)`, `dialog::receive()`. 169 | **/ 170 | void fetch(unsigned long message_no, message& msg, bool header_only = false); 171 | 172 | /** 173 | Removing a message in the mailbox. 174 | 175 | @param message_no Message number to remove. 176 | @throw pop3_error Removing message failure. 177 | @throw * `parse_status(const string&)`, `dialog::send(const string&)`, `dialog::receive()`. 178 | **/ 179 | void remove(unsigned long message_no); 180 | 181 | protected: 182 | 183 | /** 184 | Character used by POP3 to separate tokens. 185 | **/ 186 | static const char TOKEN_SEPARATOR_CHAR{' '}; 187 | 188 | /** 189 | Initializing a connection to the server. 190 | 191 | @return The server greeting message. 192 | @throw pop3_error Connection to server failure. 193 | @throw * `parse_status(const string&)`, `dialog::receive()`. 194 | **/ 195 | std::string connect(); 196 | 197 | /** 198 | Authentication of a user. 199 | 200 | @param username Username to authenticate. 201 | @param password Password to authenticate. 202 | @throw pop3_error Username rejection. 203 | @throw pop3_error Password rejection. 204 | @throw * `parse_status(const string&)`, `dialog::send(const string&)`, `dialog::receive()`. 205 | **/ 206 | void auth_login(const std::string& username, const std::string& password); 207 | 208 | /** 209 | Parsing a response line for the status. 210 | 211 | @param line Response line to parse. 212 | @return Tuple with the status and rest of the line. 213 | @throw pop3_error Response status unknown. 214 | **/ 215 | std::tuple parse_status(const std::string& line); 216 | 217 | /** 218 | Dialog to use for send/receive operations. 219 | **/ 220 | std::shared_ptr dlg_; 221 | }; 222 | 223 | 224 | /** 225 | Secure version of POP3 client. 226 | **/ 227 | class MAILIO_EXPORT pop3s : public pop3 228 | { 229 | public: 230 | 231 | /** 232 | Available authentication methods over the TLS connection. 233 | 234 | The following mechanisms are allowed: 235 | - LOGIN: The username and password are sent in plain format. 236 | - START_TLS: For the TCP connection, a TLS negotiation is asked before sending the login parameters. 237 | **/ 238 | enum class auth_method_t {LOGIN, START_TLS}; 239 | 240 | /** 241 | Making a connection to server. 242 | 243 | Parent constructor is called to do all the work. 244 | 245 | @param hostname Hostname of the server. 246 | @param port Port of the server. 247 | @param timeout Network timeout after which I/O operations fail. If zero, then no timeout is set i.e. I/O operations are synchronous. 248 | @throw * `pop3::pop3(const string&, unsigned)`. 249 | **/ 250 | pop3s(const std::string& hostname, unsigned port, std::chrono::milliseconds timeout = std::chrono::milliseconds(0)); 251 | 252 | /** 253 | Sending the quit command and closing the connection. 254 | 255 | Parent destructor is called to do all the work. 256 | **/ 257 | ~pop3s() = default; 258 | 259 | pop3s(const pop3s&) = delete; 260 | 261 | pop3s(pop3s&&) = delete; 262 | 263 | void operator=(const pop3s&) = delete; 264 | 265 | void operator=(pop3s&&) = delete; 266 | 267 | /** 268 | Authenticating with the given credentials. 269 | 270 | @param username Username to authenticate. 271 | @param password Password to authenticate. 272 | @param method Authentication method to use. 273 | @return The server greeting message. 274 | @throw * `start_tls()`, `pop3::auth_login(const string&, const string&)`. 275 | **/ 276 | std::string authenticate(const std::string& username, const std::string& password, auth_method_t method); 277 | 278 | /** 279 | Setting SSL options. 280 | 281 | @param options SSL options to set. 282 | **/ 283 | void ssl_options(const dialog_ssl::ssl_options_t& options); 284 | 285 | protected: 286 | 287 | /** 288 | Switching to TLS layer. 289 | 290 | @throw pop3_error Start TLS failure. 291 | @throw * `parse_status(const string&)`, `dialog::send(const string&)`, `dialog::receive()`, `switch_to_ssl()`. 292 | **/ 293 | void start_tls(); 294 | 295 | /** 296 | Replacing a TCP socket with an SSL one. 297 | 298 | @throw * `dialog_ssl::dialog_ssl(dialog&, const ssl_options_t&)`. 299 | **/ 300 | void switch_to_ssl(); 301 | 302 | /** 303 | SSL options to set. 304 | **/ 305 | dialog_ssl::ssl_options_t ssl_options_; 306 | }; 307 | 308 | 309 | /** 310 | Error thrown by POP3 client. 311 | **/ 312 | class pop3_error : public dialog_error 313 | { 314 | public: 315 | 316 | /** 317 | Calling the parent constructor. 318 | 319 | @param msg Error message. 320 | @param details Detailed message. 321 | **/ 322 | pop3_error(const std::string& msg, const std::string& details); 323 | 324 | /** 325 | Calling the parent constructor. 326 | 327 | @param msg Error message. 328 | @param details Detailed message. 329 | **/ 330 | pop3_error(const char* msg, const std::string& details); 331 | 332 | pop3_error(const pop3_error&) = default; 333 | 334 | pop3_error(pop3_error&&) = default; 335 | 336 | ~pop3_error() = default; 337 | 338 | pop3_error& operator=(const pop3_error&) = default; 339 | 340 | pop3_error& operator=(pop3_error&&) = default; 341 | }; 342 | 343 | 344 | } // namespace mailio 345 | 346 | 347 | #ifdef _MSC_VER 348 | #pragma warning(pop) 349 | #endif 350 | -------------------------------------------------------------------------------- /include/mailio/q_codec.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | q_codec.hpp 4 | ----------- 5 | 6 | Copyright (C) 2017, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #ifdef _MSC_VER 17 | #pragma warning(push) 18 | #pragma warning(disable:4251) 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | #include "codec.hpp" 25 | #include "export.hpp" 26 | 27 | 28 | namespace mailio 29 | { 30 | 31 | 32 | /** 33 | Q codec. 34 | 35 | ASCII and UTF-8 charsets are recognized. 36 | **/ 37 | class MAILIO_EXPORT q_codec : public codec 38 | { 39 | public: 40 | 41 | /** 42 | Setting the encoder and decoder line policies. 43 | 44 | @param line1_policy First line policy to set. 45 | @param lines_policy Other lines policy than the first one to set. 46 | @param codec_method Method for encoding/decoding. 47 | @throw codec_error Bad encoding method. 48 | **/ 49 | q_codec(std::string::size_type line1_policy, std::string::size_type lines_policy); 50 | 51 | q_codec(const q_codec&) = delete; 52 | 53 | q_codec(q_codec&&) = delete; 54 | 55 | /** 56 | Default destructor. 57 | **/ 58 | ~q_codec() = default; 59 | 60 | void operator=(const q_codec&) = delete; 61 | 62 | void operator=(q_codec&&) = delete; 63 | 64 | /** 65 | Encoding a text by applying the given method. 66 | 67 | @param text String to encode. 68 | @param charset Charset used by the string. 69 | @param method Allowed encoding methods. 70 | @return Encoded string. 71 | @todo Merge text and charset into a single parameter of type `string_t`. 72 | @todo It must take another parameter for the header name length in order to remove the hardcoded constant. 73 | **/ 74 | std::vector encode(const std::string& text, const std::string& charset, codec_t method) const; 75 | 76 | /** 77 | Decoding a string. 78 | 79 | @param text String to decode. 80 | @return Decoded string, its charset and its codec method. 81 | @throw codec_error Missing Q codec separator for charset. 82 | @throw codec_error Missing Q codec separator for codec type. 83 | @throw codec_error Missing last Q codec separator. 84 | @throw codec_error Bad encoding method. 85 | @throw * `decode_qp(const string&)`, `base64::decode(const string&)`. 86 | **/ 87 | std::tuple decode(const std::string& text) const; 88 | 89 | /** 90 | Checking if a string is Q encoded and decodes it. 91 | 92 | @param text String to decode. 93 | @return Decoded string, its charset and its codec method. 94 | @throw codec_error Bad Q codec format. 95 | @todo Returning value to hold `string_t` instead of two `std::string`. 96 | **/ 97 | std::tuple check_decode(const std::string& text) const; 98 | 99 | private: 100 | 101 | /** 102 | String representation of Base64 method. 103 | **/ 104 | static const std::string BASE64_CODEC_STR; 105 | 106 | /** 107 | String representation of Quoted Printable method. 108 | **/ 109 | static const std::string QP_CODEC_STR; 110 | 111 | /** 112 | Decoding by using variation of the Quoted Printable method. 113 | 114 | @param text String to decode. 115 | @return Decoded string. 116 | @throw * `quoted_printable::decode(const vector&)` 117 | **/ 118 | std::string decode_qp(const std::string& text) const; 119 | 120 | /** 121 | Checking if a character is allowed. 122 | 123 | @param ch Character to check. 124 | @return True if allowed, false if not. 125 | **/ 126 | bool is_q_allowed(char ch) const; 127 | }; 128 | 129 | 130 | } // namespace mailio 131 | 132 | 133 | #ifdef _MSC_VER 134 | #pragma warning(pop) 135 | #endif 136 | -------------------------------------------------------------------------------- /include/mailio/quoted_printable.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | quoted_printable.hpp 4 | -------------------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #include 17 | #include 18 | #include "codec.hpp" 19 | #include "export.hpp" 20 | 21 | 22 | namespace mailio 23 | { 24 | 25 | 26 | /** 27 | Quoted Printable codec. 28 | 29 | @todo Remove the Q codec flag. 30 | **/ 31 | class MAILIO_EXPORT quoted_printable : public codec 32 | { 33 | public: 34 | 35 | /** 36 | Setting the encoder and decoder line policies. 37 | 38 | @param line1_policy First line policy to set. 39 | @param lines_policy Other lines policy than the first one to set. 40 | **/ 41 | quoted_printable(std::string::size_type line1_policy, std::string::size_type lines_policy); 42 | 43 | quoted_printable(const quoted_printable&) = delete; 44 | 45 | quoted_printable(quoted_printable&&) = delete; 46 | 47 | /** 48 | Default destructor. 49 | **/ 50 | ~quoted_printable() = default; 51 | 52 | void operator=(const quoted_printable&) = delete; 53 | 54 | void operator=(quoted_printable&&) = delete; 55 | 56 | /** 57 | Encoding a string into vector of quoted printable encoded strings by applying the line policy. 58 | 59 | @param text String to encode. 60 | @return Vector of quoted printable strings. 61 | @throw codec_error Bad character. 62 | @throw codec_error Bad CRLF sequence. 63 | **/ 64 | std::vector encode(const std::string& text) const; 65 | 66 | /** 67 | Decoding a vector of quoted printable strings to string by applying the line policy. 68 | 69 | @param text Vector of quoted printable encoded strings. 70 | @return Decoded string. 71 | @throw codec_error Bad line policy. 72 | @throw codec_error Bad character. 73 | @throw codec_error Bad hexadecimal digit. 74 | **/ 75 | std::string decode(const std::vector& text) const; 76 | 77 | /** 78 | Setting Q codec mode. 79 | 80 | @param mode True to set, false to unset. 81 | **/ 82 | void q_codec_mode(bool mode); 83 | 84 | private: 85 | 86 | /** 87 | Check if a character is in the Quoted Printable character set. 88 | 89 | @param ch Character to check. 90 | @return True if it is, false if not. 91 | **/ 92 | bool is_allowed(char ch) const; 93 | 94 | /** 95 | Flag for the Q codec mode. 96 | **/ 97 | bool q_codec_mode_; 98 | }; 99 | 100 | 101 | } // namespace mailio 102 | -------------------------------------------------------------------------------- /include/mailio/smtp.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | smtp.hpp 4 | -------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #pragma once 15 | 16 | #ifdef _MSC_VER 17 | #pragma warning(push) 18 | #pragma warning(disable:4251) 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "message.hpp" 30 | #include "dialog.hpp" 31 | #include "export.hpp" 32 | 33 | 34 | namespace mailio 35 | { 36 | 37 | 38 | /** 39 | SMTP client implementation. 40 | **/ 41 | class MAILIO_EXPORT smtp 42 | { 43 | public: 44 | 45 | /** 46 | Available authentication methods. 47 | 48 | The following mechanisms are allowed: 49 | - NONE: No username or password are required, so just use the empty strings when authenticating. Nowadays, it's not probably that such authentication 50 | mechanism is allowed. 51 | - LOGIN: The username and password are sent in Base64 format. 52 | **/ 53 | enum class auth_method_t {NONE, LOGIN}; 54 | 55 | /** 56 | Making a connection to the server. 57 | 58 | @param hostname Hostname of the server. 59 | @param port Port of the server. 60 | @param timeout Network timeout after which I/O operations fail. If zero, then no timeout is set i.e. I/O operations are synchronous. 61 | @throw smtp_error Empty source hostname not allowed. 62 | @throw * `dialog::dialog`, `read_hostname`. 63 | **/ 64 | smtp(const std::string& hostname, unsigned port, std::chrono::milliseconds timeout = std::chrono::milliseconds(0)); 65 | 66 | /** 67 | Sending the quit command and closing the connection. 68 | **/ 69 | virtual ~smtp(); 70 | 71 | smtp(const smtp&) = delete; 72 | 73 | smtp(smtp&&) = delete; 74 | 75 | void operator=(const smtp&) = delete; 76 | 77 | void operator=(smtp&&) = delete; 78 | 79 | /** 80 | Authenticating with the given credentials. 81 | 82 | The method should be called only once on an existing object - it is not possible to authenticate again within the same connection. 83 | 84 | @param username Username to authenticate. 85 | @param password Password to authenticate. 86 | @param method Authentication method to use. 87 | @return The server greeting message. 88 | @throw * `connect()`, `ehlo()`, `auth_login(const string&, const string&)`. 89 | **/ 90 | std::string authenticate(const std::string& username, const std::string& password, auth_method_t method); 91 | 92 | /** 93 | Submitting a message. 94 | 95 | @param msg Mail message to send. 96 | @return The SMTP server's reply on accepting the message. 97 | @throw smtp_error Mail sender rejection. 98 | @throw smtp_error Mail recipient rejection. 99 | @throw smtp_error Mail group recipient rejection. 100 | @throw smtp_error Mail cc recipient rejection. 101 | @throw smtp_error Mail group cc recipient rejection. 102 | @throw smtp_error Mail bcc recipient rejection. 103 | @throw smtp_error Mail group bcc recipient rejection. 104 | @throw smtp_error Mail message rejection. 105 | @throw * `parse_line(const string&)`, `dialog::send(const string&)`, `dialog::receive()`. 106 | **/ 107 | std::string submit(const message& msg); 108 | 109 | /** 110 | Setting the source hostname. 111 | 112 | @param src_host Source hostname to set. 113 | **/ 114 | void source_hostname(const std::string& src_host); 115 | 116 | /** 117 | Getting source hostname. 118 | 119 | @return Source hostname. 120 | **/ 121 | std::string source_hostname() const; 122 | 123 | protected: 124 | 125 | /** 126 | SMTP response status. 127 | **/ 128 | enum smtp_status_t {POSITIVE_COMPLETION = 2, POSITIVE_INTERMEDIATE = 3, TRANSIENT_NEGATIVE = 4, PERMANENT_NEGATIVE = 5}; 129 | 130 | /** 131 | Initializing the connection to the server. 132 | 133 | @throw smtp_error Connection rejection. 134 | @throw * `parse_line(const string&)`, `dialog::receive()`. 135 | **/ 136 | std::string connect(); 137 | 138 | /** 139 | Authenticating with the login method. 140 | 141 | @param username Username to authenticate. 142 | @param password Password to authenticate. 143 | @throw smtp_error Authentication rejection. 144 | @throw smtp_error Username rejection. 145 | @throw smtp_error Password rejection. 146 | @throw * `parse_line(const string&)`, `dialog::send(const string&)`, `dialog::receive()`. 147 | **/ 148 | void auth_login(const std::string& username, const std::string& password); 149 | 150 | /** 151 | Issuing `EHLO` and/or `HELO` commands. 152 | 153 | @throw smtp_error Initial message rejection. 154 | @throw * `parse_line(const string&)`, `dialog::send(const string&)`, `dialog::receive()`. 155 | **/ 156 | void ehlo(); 157 | 158 | /** 159 | Reading the source hostname. 160 | 161 | @return Source hostname. 162 | @throw smtp_error Reading hostname failure. 163 | **/ 164 | std::string read_hostname(); 165 | 166 | /** 167 | Parsing the response line into three tokens. 168 | 169 | @param response Response line to parse. 170 | @return Tuple with a status number, flag if the line is the last one and status message. 171 | @throw smtp_error Parsing server failure. 172 | **/ 173 | static std::tuple parse_line(const std::string& response); 174 | 175 | /** 176 | Checking if the status is 2XX. 177 | 178 | @param status Status to check. 179 | @return True if does, false if not. 180 | **/ 181 | static bool positive_completion(int status); 182 | 183 | /** 184 | Checking if the status is 3XX. 185 | 186 | @param status Status to check. 187 | @return True if does, false if not. 188 | **/ 189 | static bool positive_intermediate(int status); 190 | 191 | /** 192 | Checking if the status is 4XX. 193 | 194 | @param status Status to check. 195 | @return True if does, false if not. 196 | **/ 197 | static bool transient_negative(int status); 198 | 199 | /** 200 | Checking if the status is 5XX. 201 | 202 | @param status Status to check. 203 | @return True if does, false if not. 204 | **/ 205 | static bool permanent_negative(int status); 206 | 207 | /** 208 | Server status when ready. 209 | **/ 210 | static const uint16_t SERVICE_READY_STATUS = 220; 211 | 212 | /** 213 | Name of the host which client is connecting from. 214 | **/ 215 | std::string src_host_; 216 | 217 | /** 218 | Dialog to use for send/receive operations. 219 | **/ 220 | std::shared_ptr dlg_; 221 | }; 222 | 223 | 224 | /** 225 | Secure version of SMTP client. 226 | **/ 227 | class MAILIO_EXPORT smtps : public smtp 228 | { 229 | public: 230 | 231 | /** 232 | Available authentication methods over the TLS connection. 233 | 234 | The following mechanisms are allowed: 235 | - NONE: No username or password are required, so just use the empty strings when authenticating. Nowadays, it's not probably that such authentication 236 | mechanism is allowed. 237 | - LOGIN: The username and password are sent in Base64 format. 238 | - START_TLS: For the TCP connection, a TLS negotiation is asked before sending the login parameters. 239 | **/ 240 | enum class auth_method_t {NONE, LOGIN, START_TLS}; 241 | 242 | /** 243 | Making a connection to the server. 244 | 245 | Parent constructor is called to do all the work. 246 | 247 | @param hostname Hostname of the server. 248 | @param port Port of the server. 249 | @param timeout Network timeout after which I/O operations fail. If zero, then no timeout is set i.e. I/O operations are synchronous. 250 | @throw * `smtp::smtp(const string&, unsigned)`. 251 | **/ 252 | smtps(const std::string& hostname, unsigned port, std::chrono::milliseconds timeout = std::chrono::milliseconds(0)); 253 | 254 | /** 255 | Sending the quit command and closing the connection. 256 | 257 | Parent destructor is called to do all the work. 258 | **/ 259 | ~smtps() = default; 260 | 261 | smtps(const smtps&) = delete; 262 | 263 | smtps(smtps&&) = delete; 264 | 265 | void operator=(const smtps&) = delete; 266 | 267 | void operator=(smtps&&) = delete; 268 | 269 | /** 270 | Authenticating with the given credentials. 271 | 272 | @param username Username to authenticate. 273 | @param password Password to authenticate. 274 | @param method Authentication method to use. 275 | @return The server greeting message. 276 | @throw * `start_tls()`, `switch_to_ssl()`, `ehlo()`, `auth_login(const string&, const string&)`, `connect()`. 277 | **/ 278 | std::string authenticate(const std::string& username, const std::string& password, auth_method_t method); 279 | 280 | /** 281 | Setting SSL options. 282 | 283 | @param options SSL options to set. 284 | **/ 285 | void ssl_options(const dialog_ssl::ssl_options_t& options); 286 | 287 | protected: 288 | 289 | /** 290 | Switching to TLS layer. 291 | 292 | @throw smtp_error Start TLS refused by server. 293 | @throw * `parse_line(const string&)`, `ehlo()`, `dialog::send(const string&)`, `dialog::receive()`, `switch_to_ssl()`. 294 | **/ 295 | void start_tls(); 296 | 297 | /** 298 | Replacing a TCP socket with an SSL one. 299 | 300 | @throw * `dialog_ssl::dialog_ssl(dialog&, const ssl_options_t&)`. 301 | **/ 302 | void switch_to_ssl(); 303 | 304 | /** 305 | SSL options to set. 306 | **/ 307 | dialog_ssl::ssl_options_t ssl_options_; 308 | }; 309 | 310 | 311 | /** 312 | Error thrown by SMTP client. 313 | **/ 314 | class smtp_error : public dialog_error 315 | { 316 | public: 317 | 318 | /** 319 | Calling the parent constructor. 320 | 321 | @param msg Error message. 322 | @param details Detailed message. 323 | **/ 324 | smtp_error(const std::string& msg, const std::string& details); 325 | 326 | /** 327 | Calling the parent constructor. 328 | 329 | @param msg Error message. 330 | @param details Detailed message. 331 | **/ 332 | smtp_error(const char* msg, const std::string& details); 333 | 334 | smtp_error(const smtp_error&) = default; 335 | 336 | smtp_error(smtp_error&&) = default; 337 | 338 | ~smtp_error() = default; 339 | 340 | smtp_error& operator=(const smtp_error&) = default; 341 | 342 | smtp_error& operator=(smtp_error&&) = default; 343 | }; 344 | 345 | 346 | } // namespace mailio 347 | 348 | 349 | #ifdef _MSC_VER 350 | #pragma warning(pop) 351 | #endif 352 | -------------------------------------------------------------------------------- /include/version.hpp.in: -------------------------------------------------------------------------------- 1 | #define MAILIO_VERSION_MAJOR @mailio_VERSION_MAJOR@ 2 | #define MAILIO_VERSION_MINOR @mailio_VERSION_MINOR@ 3 | #define MAILIO_VERSION_PATCH @mailio_VERSION_PATCH@ 4 | -------------------------------------------------------------------------------- /mailio.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | exec_prefix=@exec_prefix@ 3 | libdir=@libdir@ 4 | sharedlibdir=@libdir@ 5 | includedir=@includedir@ 6 | 7 | Name: mailio 8 | Description: Cross-platform MIME library for SMTP, POP3, and IMAP protocols 9 | Version: @PROJECT_VERSION@ 10 | Requires: 11 | Libs: -L${libdir} -L${sharedlibdir} -lmailio 12 | Cflags: -I${includedir} 13 | 14 | -------------------------------------------------------------------------------- /src/base64.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | base64.cpp 4 | ---------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | using std::string; 20 | using std::vector; 21 | using boost::trim_right; 22 | 23 | 24 | namespace mailio 25 | { 26 | 27 | 28 | const string base64::CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 29 | 30 | 31 | base64::base64(string::size_type line1_policy, string::size_type lines_policy) : 32 | codec(line1_policy, lines_policy) 33 | { 34 | // Line policies to be divisible by four. 35 | line1_policy_ -= line1_policy_ % SEXTETS_NO; 36 | lines_policy_ -= lines_policy_ % SEXTETS_NO; 37 | } 38 | 39 | 40 | vector base64::encode(const string& text) const 41 | { 42 | vector enc_text; 43 | unsigned char octets[OCTETS_NO]; 44 | unsigned char sextets[SEXTETS_NO]; 45 | int sextets_counter = 0; 46 | string line; 47 | string::size_type line_len = 0; 48 | string::size_type policy = line1_policy_; 49 | 50 | auto add_new_line = [&enc_text, &line_len, &policy, this](string& line) 51 | { 52 | enc_text.push_back(line); 53 | line.clear(); 54 | line_len = 0; 55 | policy = lines_policy_; 56 | }; 57 | 58 | for (string::size_type cur_char = 0; cur_char < text.length(); cur_char++) 59 | { 60 | octets[sextets_counter++] = text[cur_char]; 61 | if (sextets_counter == OCTETS_NO) 62 | { 63 | sextets[0] = (octets[0] & 0xfc) >> 2; 64 | sextets[1] = ((octets[0] & 0x03) << 4) + ((octets[1] & 0xf0) >> 4); 65 | sextets[2] = ((octets[1] & 0x0f) << 2) + ((octets[2] & 0xc0) >> 6); 66 | sextets[3] = octets[2] & 0x3f; 67 | 68 | for(int i = 0; i < SEXTETS_NO; i++) 69 | line += CHARSET[sextets[i]]; 70 | sextets_counter = 0; 71 | line_len += SEXTETS_NO; 72 | } 73 | 74 | if (line_len >= policy) 75 | add_new_line(line); 76 | } 77 | 78 | // encode remaining characters if any 79 | 80 | if (sextets_counter > 0) 81 | { 82 | // If the remaining three characters match exatcly rest of the line, then move them onto next line. Email clients do not show properly subject when 83 | // the next line has the empty content, containing only the encoding stuff. 84 | if (line_len >= policy - OCTETS_NO) 85 | add_new_line(line); 86 | 87 | for (int i = sextets_counter; i < OCTETS_NO; i++) 88 | octets[i] = '\0'; 89 | 90 | sextets[0] = (octets[0] & 0xfc) >> 2; 91 | sextets[1] = ((octets[0] & 0x03) << 4) + ((octets[1] & 0xf0) >> 4); 92 | sextets[2] = ((octets[1] & 0x0f) << 2) + ((octets[2] & 0xc0) >> 6); 93 | sextets[3] = octets[2] & 0x3f; 94 | 95 | for (int i = 0; i < sextets_counter + 1; i++) 96 | { 97 | if (line_len >= policy) 98 | add_new_line(line); 99 | line += CHARSET[sextets[i]]; 100 | line_len++; 101 | } 102 | 103 | while (sextets_counter++ < OCTETS_NO) 104 | { 105 | if (line_len >= policy) 106 | add_new_line(line); 107 | line += EQUAL_CHAR; 108 | line_len++; 109 | } 110 | } 111 | 112 | if (!line.empty()) 113 | enc_text.push_back(line); 114 | 115 | return enc_text; 116 | } 117 | 118 | 119 | string base64::decode(const vector& text) const 120 | { 121 | string dec_text; 122 | unsigned char sextets[SEXTETS_NO]; 123 | unsigned char octets[OCTETS_NO]; 124 | int count_4_chars = 0; 125 | 126 | for (const auto& line : text) 127 | { 128 | if (line.length() > lines_policy_) 129 | throw codec_error("Bad line policy."); 130 | 131 | for (string::size_type ch = 0; ch < line.length() && line[ch] != EQUAL_CHAR; ch++) 132 | { 133 | if (!is_allowed(line[ch])) 134 | throw codec_error("Bad character `" + string(1, line[ch]) + "`."); 135 | 136 | sextets[count_4_chars++] = line[ch]; 137 | if (count_4_chars == SEXTETS_NO) 138 | { 139 | for (int i = 0; i < SEXTETS_NO; i++) 140 | sextets[i] = static_cast(CHARSET.find(sextets[i])); 141 | 142 | octets[0] = (sextets[0] << 2) + ((sextets[1] & 0x30) >> 4); 143 | octets[1] = ((sextets[1] & 0xf) << 4) + ((sextets[2] & 0x3c) >> 2); 144 | octets[2] = ((sextets[2] & 0x3) << 6) + sextets[3]; 145 | 146 | for (int i = 0; i < OCTETS_NO; i++) 147 | dec_text += octets[i]; 148 | count_4_chars = 0; 149 | } 150 | } 151 | 152 | // decode remaining characters if any 153 | 154 | if (count_4_chars > 0) 155 | { 156 | for (int i = count_4_chars; i < SEXTETS_NO; i++) 157 | sextets[i] = '\0'; 158 | 159 | for (int i = 0; i < SEXTETS_NO; i++) 160 | sextets[i] = static_cast(CHARSET.find(sextets[i])); 161 | 162 | octets[0] = (sextets[0] << 2) + ((sextets[1] & 0x30) >> 4); 163 | octets[1] = ((sextets[1] & 0xf) << 4) + ((sextets[2] & 0x3c) >> 2); 164 | octets[2] = ((sextets[2] & 0x3) << 6) + sextets[3]; 165 | 166 | for (int i = 0; i < count_4_chars - 1; i++) 167 | dec_text += octets[i]; 168 | } 169 | } 170 | 171 | return dec_text; 172 | } 173 | 174 | 175 | string base64::decode(const string& text) const 176 | { 177 | vector v; 178 | v.push_back(text); 179 | return decode(v); 180 | } 181 | 182 | 183 | bool base64::is_allowed(char ch) const 184 | { 185 | return (isalnum(ch) || ch == PLUS_CHAR || ch == SLASH_CHAR); 186 | } 187 | 188 | 189 | } // namespace mailio 190 | -------------------------------------------------------------------------------- /src/binary.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | binary.cpp 4 | ---------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | using std::string; 20 | using std::vector; 21 | 22 | 23 | namespace mailio 24 | { 25 | 26 | 27 | binary::binary(string::size_type line1_policy, string::size_type lines_policy) : 28 | codec(line1_policy, lines_policy) 29 | { 30 | } 31 | 32 | 33 | vector binary::encode(const string& text) const 34 | { 35 | vector enc_text; 36 | enc_text.push_back(text); 37 | return enc_text; 38 | } 39 | 40 | 41 | string binary::decode(const vector& text) const 42 | { 43 | string dec_text; 44 | for (const auto& line : text) 45 | dec_text += line + END_OF_LINE; 46 | return dec_text; 47 | } 48 | 49 | 50 | } // namespace mailio 51 | -------------------------------------------------------------------------------- /src/bit7.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | bit7.cpp 4 | -------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | using std::string; 21 | using std::vector; 22 | using boost::trim_right; 23 | 24 | 25 | namespace mailio 26 | { 27 | 28 | 29 | bit7::bit7(string::size_type line1_policy, string::size_type lines_policy) : 30 | codec(line1_policy, lines_policy) 31 | { 32 | } 33 | 34 | 35 | vector bit7::encode(const string& text) const 36 | { 37 | vector enc_text; 38 | string line; 39 | string::size_type line_len = 0; 40 | const string DELIMITERS = " ,;"; 41 | string::size_type delim_pos = 0; 42 | string::size_type policy = line1_policy_; 43 | const bool is_folding = (line1_policy_ != lines_policy_); 44 | 45 | auto add_new_line = [&enc_text, &line_len, &delim_pos, &policy, this](bool is_folding, string& line) 46 | { 47 | if (is_folding && delim_pos > 0) 48 | { 49 | enc_text.push_back(line.substr(0, delim_pos)); 50 | line = line.substr(delim_pos); 51 | line_len -= delim_pos; 52 | delim_pos = 0; 53 | } 54 | else 55 | { 56 | enc_text.push_back(line); 57 | line.clear(); 58 | line_len = 0; 59 | } 60 | policy = lines_policy_; 61 | }; 62 | 63 | for (auto ch = text.begin(); ch != text.end(); ch++) 64 | { 65 | if (is_allowed(*ch)) 66 | { 67 | line += *ch; 68 | line_len++; 69 | 70 | if (DELIMITERS.find(*ch) != string::npos) 71 | delim_pos = line_len; 72 | } 73 | else if (*ch == '\r' && (ch + 1) != text.end() && *(ch + 1) == '\n') 74 | { 75 | add_new_line(is_folding, line); 76 | // Skip both crlf characters. 77 | ch++; 78 | } 79 | else 80 | throw codec_error("Bad character `" + string(1, *ch) + "`."); 81 | 82 | if (line_len == policy) 83 | add_new_line(is_folding, line); 84 | } 85 | if (!line.empty()) 86 | enc_text.push_back(line); 87 | while (!enc_text.empty() && enc_text.back().empty()) 88 | enc_text.pop_back(); 89 | 90 | return enc_text; 91 | } 92 | 93 | 94 | // TODO: Consider the first line policy. 95 | string bit7::decode(const vector& text) const 96 | { 97 | string dec_text; 98 | for (const auto& line : text) 99 | { 100 | if (line.length() > lines_policy_) 101 | throw codec_error("Line policy overflow."); 102 | 103 | for (auto ch : line) 104 | { 105 | if (!is_allowed(ch)) 106 | throw codec_error("Bad character `" + string(1, ch) + "`."); 107 | 108 | dec_text += ch; 109 | } 110 | dec_text += "\r\n"; 111 | } 112 | trim_right(dec_text); 113 | 114 | return dec_text; 115 | } 116 | 117 | 118 | /* 119 | For details see [rfc 2045, section 2.7]. 120 | */ 121 | bool bit7::is_allowed(char ch) const 122 | { 123 | if (strict_mode_) 124 | return (ch > NIL_CHAR && ch <= TILDE_CHAR && ch != CR_CHAR && ch != LF_CHAR); 125 | else 126 | return (ch != NIL_CHAR && ch != CR_CHAR && ch != LF_CHAR); 127 | } 128 | 129 | 130 | } // namespace mailio 131 | -------------------------------------------------------------------------------- /src/bit8.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | bit8.cpp 4 | -------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | using std::string; 21 | using std::vector; 22 | using boost::trim_right; 23 | 24 | 25 | namespace mailio 26 | { 27 | 28 | 29 | bit8::bit8(string::size_type line1_policy, string::size_type lines_policy) : 30 | codec(line1_policy, lines_policy) 31 | { 32 | } 33 | 34 | 35 | vector bit8::encode(const string& text) const 36 | { 37 | vector enc_text; 38 | string line; 39 | string::size_type line_len = 0; 40 | bool is_first_line = true; 41 | 42 | auto add_new_line = [&enc_text, &line_len](string& line) 43 | { 44 | enc_text.push_back(line); 45 | line.clear(); 46 | line_len = 0; 47 | }; 48 | 49 | for (auto ch = text.begin(); ch != text.end(); ch++) 50 | { 51 | if (is_allowed(*ch)) 52 | { 53 | line += *ch; 54 | line_len++; 55 | } 56 | else if (*ch == '\r' && (ch + 1) != text.end() && *(ch + 1) == '\n') 57 | { 58 | add_new_line(line); 59 | // skip both crlf characters 60 | ch++; 61 | } 62 | else 63 | throw codec_error("Bad character `" + string(1, *ch) + "`."); 64 | 65 | if (is_first_line) 66 | { 67 | if (line_len == line1_policy_) 68 | { 69 | is_first_line = false; 70 | add_new_line(line); 71 | } 72 | } 73 | else if (line_len == lines_policy_) 74 | { 75 | add_new_line(line); 76 | } 77 | } 78 | if (!line.empty()) 79 | enc_text.push_back(line); 80 | while (!enc_text.empty() && enc_text.back().empty()) 81 | enc_text.pop_back(); 82 | 83 | return enc_text; 84 | } 85 | 86 | 87 | // TODO: Consider the first line policy. 88 | string bit8::decode(const vector& text) const 89 | { 90 | string dec_text; 91 | for (const auto& line : text) 92 | { 93 | if (line.length() > lines_policy_) 94 | throw codec_error("Line policy overflow."); 95 | 96 | for (auto ch : line) 97 | { 98 | if (!is_allowed(ch)) 99 | throw codec_error("Bad character `" + string(1, ch) + "`."); 100 | 101 | dec_text += ch; 102 | } 103 | dec_text += "\r\n"; 104 | } 105 | trim_right(dec_text); 106 | 107 | return dec_text; 108 | } 109 | 110 | 111 | /* 112 | For details see [rfc 2045, section 2.8]. 113 | */ 114 | bool bit8::is_allowed(char ch) const 115 | { 116 | return (ch != NIL_CHAR && ch != CR_CHAR && ch != LF_CHAR); 117 | } 118 | 119 | 120 | } // namespace mailio 121 | -------------------------------------------------------------------------------- /src/codec.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | codec.cpp 4 | --------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | using std::string; 20 | 21 | 22 | namespace mailio 23 | { 24 | 25 | const string codec::HEX_DIGITS{"0123456789ABCDEF"}; 26 | const string codec::END_OF_LINE{"\r\n"}; 27 | const string codec::END_OF_MESSAGE{"."}; 28 | const string codec::EQUAL_STR(1, codec::EQUAL_CHAR); 29 | const string codec::SPACE_STR(1, codec::SPACE_CHAR); 30 | const string codec::DOT_STR(1, codec::DOT_CHAR); 31 | const string codec::COMMA_STR(1, codec::COMMA_CHAR); 32 | const string codec::COLON_STR(1, codec::COLON_CHAR); 33 | const string codec::SEMICOLON_STR(1, codec::SEMICOLON_CHAR); 34 | const string codec::QUOTE_STR(1, codec::QUOTE_CHAR); 35 | const string codec::LESS_THAN_STR(1, codec::LESS_THAN_CHAR); 36 | const string codec::GREATER_THAN_STR(1, codec::GREATER_THAN_CHAR); 37 | const string codec::CHARSET_ASCII("ASCII"); 38 | const string codec::CHARSET_UTF8("UTF-8"); 39 | const string codec::ATTRIBUTE_CHARSET_SEPARATOR_STR(1, codec::ATTRIBUTE_CHARSET_SEPARATOR); 40 | 41 | 42 | int codec::hex_digit_to_int(char digit) 43 | { 44 | return digit >= ZERO_CHAR && digit <= NINE_CHAR ? digit - ZERO_CHAR : digit - A_CHAR + 10; 45 | } 46 | 47 | 48 | bool codec::is_8bit_char(char ch) 49 | { 50 | return static_cast(ch) > 127; 51 | } 52 | 53 | 54 | bool codec::is_utf8_string(const string& txt) 55 | { 56 | for (auto ch : txt) 57 | if (static_cast(ch) > 127) 58 | return true; 59 | return false; 60 | } 61 | 62 | 63 | string codec::escape_string(const string& text, const string& escaping_chars) 64 | { 65 | string esc_str; 66 | esc_str.reserve(text.size()); 67 | std::for_each(text.begin(), text.end(), [&](char ch) { 68 | if (escaping_chars.find(ch) != string::npos) 69 | esc_str += "\\"; 70 | esc_str += ch; 71 | }); 72 | return esc_str; 73 | } 74 | 75 | 76 | string codec::surround_string(const string& text, char surround_char) 77 | { 78 | return surround_char + text + surround_char; 79 | } 80 | 81 | 82 | codec::codec(string::size_type line1_policy, string::size_type lines_policy) : 83 | line1_policy_(line1_policy), lines_policy_(lines_policy), strict_mode_(false) 84 | { 85 | } 86 | 87 | 88 | void codec::strict_mode(bool mode) 89 | { 90 | strict_mode_ = mode; 91 | } 92 | 93 | 94 | bool codec::strict_mode() const 95 | { 96 | return strict_mode_; 97 | } 98 | 99 | 100 | } // namespace mailio 101 | -------------------------------------------------------------------------------- /src/dialog.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | dialog.cpp 4 | ---------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | using std::string; 22 | using std::to_string; 23 | using std::move; 24 | using std::istream; 25 | using std::make_shared; 26 | using std::shared_ptr; 27 | using std::bind; 28 | using std::chrono::milliseconds; 29 | using boost::asio::ip::tcp; 30 | using boost::asio::buffer; 31 | using boost::asio::streambuf; 32 | using boost::asio::io_context; 33 | using boost::asio::steady_timer; 34 | using boost::asio::ssl::context; 35 | using boost::system::system_error; 36 | using boost::system::error_code; 37 | using boost::algorithm::trim_if; 38 | using boost::algorithm::is_any_of; 39 | 40 | 41 | namespace mailio 42 | { 43 | 44 | 45 | boost::asio::io_context dialog::ios_; 46 | 47 | 48 | dialog::dialog(const string& hostname, unsigned port, milliseconds timeout) : std::enable_shared_from_this(), 49 | hostname_(hostname), port_(port), socket_(make_shared(ios_)), timer_(make_shared(ios_)), 50 | timeout_(timeout), timer_expired_(false), strmbuf_(make_shared()), istrm_(make_shared(strmbuf_.get())) 51 | { 52 | } 53 | 54 | 55 | dialog::dialog(const dialog& other) : std::enable_shared_from_this(), 56 | hostname_(move(other.hostname_)), port_(other.port_), socket_(other.socket_), timer_(other.timer_), 57 | timeout_(other.timeout_), timer_expired_(other.timer_expired_), strmbuf_(other.strmbuf_), istrm_(other.istrm_) 58 | { 59 | } 60 | 61 | 62 | void dialog::connect() 63 | { 64 | try 65 | { 66 | if (timeout_.count() == 0) 67 | { 68 | tcp::resolver res(ios_); 69 | boost::asio::connect(*socket_, res.resolve(hostname_, to_string(port_))); 70 | } 71 | else 72 | connect_async(); 73 | } 74 | catch (const system_error& exc) 75 | { 76 | throw dialog_error("Server connecting failed.", exc.code().message()); 77 | } 78 | } 79 | 80 | 81 | void dialog::send(const string& line) 82 | { 83 | if (timeout_.count() == 0) 84 | send_sync(*socket_, line); 85 | else 86 | send_async(*socket_, line); 87 | } 88 | 89 | 90 | // TODO: perhaps the implementation should be common with `receive_raw()` 91 | string dialog::receive(bool raw) 92 | { 93 | if (timeout_.count() == 0) 94 | return receive_sync(*socket_, raw); 95 | else 96 | return receive_async(*socket_, raw); 97 | } 98 | 99 | 100 | template 101 | void dialog::send_sync(Socket& socket, const string& line) 102 | { 103 | try 104 | { 105 | string l = line + "\r\n"; 106 | write(socket, buffer(l, l.size())); 107 | } 108 | catch (const system_error& exc) 109 | { 110 | throw dialog_error("Network sending error.", exc.code().message()); 111 | } 112 | } 113 | 114 | 115 | template 116 | string dialog::receive_sync(Socket& socket, bool raw) 117 | { 118 | try 119 | { 120 | read_until(socket, *strmbuf_, "\n"); 121 | string line; 122 | getline(*istrm_, line, '\n'); 123 | if (!raw) 124 | trim_if(line, is_any_of("\r\n")); 125 | return line; 126 | } 127 | catch (const system_error& exc) 128 | { 129 | throw dialog_error("Network receiving error.", exc.code().message()); 130 | } 131 | } 132 | 133 | 134 | void dialog::connect_async() 135 | { 136 | tcp::resolver res(ios_); 137 | check_timeout(); 138 | 139 | bool has_connected{false}, connect_error{false}; 140 | error_code errc; 141 | async_connect(*socket_, res.resolve(hostname_, to_string(port_)), 142 | [&has_connected, &connect_error, &errc](const error_code& error, const boost::asio::ip::tcp::endpoint&) 143 | { 144 | if (!error) 145 | has_connected = true; 146 | else 147 | connect_error = true; 148 | errc = error; 149 | }); 150 | wait_async(has_connected, connect_error, "Network connecting timed out.", "Network connecting failed.", errc); 151 | } 152 | 153 | 154 | template 155 | void dialog::send_async(Socket& socket, string line) 156 | { 157 | check_timeout(); 158 | string l = line + "\r\n"; 159 | bool has_written{false}, send_error{false}; 160 | error_code errc; 161 | async_write(socket, buffer(l, l.size()), 162 | [&has_written, &send_error, &errc](const error_code& error, size_t) 163 | { 164 | if (!error) 165 | has_written = true; 166 | else 167 | send_error = true; 168 | errc = error; 169 | }); 170 | wait_async(has_written, send_error, "Network sending timed out.", "Network sending failed.", errc); 171 | } 172 | 173 | 174 | template 175 | string dialog::receive_async(Socket& socket, bool raw) 176 | { 177 | check_timeout(); 178 | bool has_read{false}, receive_error{false}; 179 | string line; 180 | error_code errc; 181 | async_read_until(socket, *strmbuf_, "\n", 182 | [&has_read, &receive_error, this, &line, &errc, raw](const error_code& error, size_t) 183 | { 184 | if (!error) 185 | { 186 | getline(*istrm_, line, '\n'); 187 | if (!raw) 188 | trim_if(line, is_any_of("\r\n")); 189 | has_read = true; 190 | } 191 | else 192 | receive_error = true; 193 | errc = error; 194 | }); 195 | wait_async(has_read, receive_error, "Network receiving timed out.", "Network receiving failed.", errc); 196 | return line; 197 | } 198 | 199 | 200 | void dialog::wait_async(const bool& has_op, const bool& op_error, const char* expired_msg, const char* op_msg, const error_code& error) 201 | { 202 | do 203 | { 204 | if (timer_expired_) 205 | throw dialog_error(expired_msg, error.message()); 206 | if (op_error) 207 | throw dialog_error(op_msg, error.message()); 208 | ios_.run_one(); 209 | } 210 | while (!has_op); 211 | } 212 | 213 | 214 | void dialog::check_timeout() 215 | { 216 | // Expiring automatically cancels the timer, per documentation. 217 | timer_->expires_after(timeout_); 218 | timer_expired_ = false; 219 | timer_->async_wait(bind(&dialog::timeout_handler, shared_from_this(), std::placeholders::_1)); 220 | } 221 | 222 | 223 | void dialog::timeout_handler(const error_code& error) 224 | { 225 | if (!error) 226 | timer_expired_ = true; 227 | } 228 | 229 | 230 | dialog_ssl::dialog_ssl(const string& hostname, unsigned port, milliseconds timeout, const ssl_options_t& options) : 231 | dialog(hostname, port, timeout), ssl_(false), context_(make_shared(options.method)), 232 | ssl_socket_(make_shared>(*socket_, *context_)) 233 | { 234 | } 235 | 236 | 237 | dialog_ssl::dialog_ssl(const dialog& other, const ssl_options_t& options) : dialog(other), context_(make_shared(options.method)), 238 | ssl_socket_(make_shared>(*socket_, *context_)) 239 | { 240 | try 241 | { 242 | ssl_socket_->set_verify_mode(options.verify_mode); 243 | ssl_socket_->handshake(boost::asio::ssl::stream_base::client); 244 | ssl_ = true; 245 | } 246 | catch (const system_error& exc) 247 | { 248 | // TODO: perhaps the message is confusing 249 | throw dialog_error("Switching to SSL failed.", exc.code().message()); 250 | } 251 | } 252 | 253 | 254 | void dialog_ssl::send(const string& line) 255 | { 256 | if (!ssl_) 257 | { 258 | dialog::send(line); 259 | return; 260 | } 261 | 262 | if (timeout_.count() == 0) 263 | send_sync(*ssl_socket_, line); 264 | else 265 | send_async(*ssl_socket_, line); 266 | } 267 | 268 | 269 | string dialog_ssl::receive(bool raw) 270 | { 271 | if (!ssl_) 272 | return dialog::receive(raw); 273 | 274 | try 275 | { 276 | if (timeout_.count() == 0) 277 | return receive_sync(*ssl_socket_, raw); 278 | else 279 | return receive_async(*ssl_socket_, raw); 280 | } 281 | catch (const system_error& exc) 282 | { 283 | throw dialog_error("Network receiving error.", exc.code().message()); 284 | } 285 | } 286 | 287 | 288 | string dialog_error::details() const 289 | { 290 | return details_; 291 | } 292 | 293 | } // namespace mailio 294 | -------------------------------------------------------------------------------- /src/mailboxes.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | mailboxes.cpp 4 | ------------- 5 | 6 | Copyright (C) 2017, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #include "mailio/mailboxes.hpp" 15 | 16 | 17 | using std::string; 18 | using std::vector; 19 | 20 | 21 | namespace mailio 22 | { 23 | 24 | 25 | mail_address::mail_address(const string_t& mail_name, const string& mail_address) : name(mail_name), address(mail_address) 26 | { 27 | } 28 | 29 | 30 | bool mail_address::empty() const 31 | { 32 | return name.buffer.empty() && address.empty(); 33 | } 34 | 35 | 36 | void mail_address::clear() 37 | { 38 | name.buffer.clear(); 39 | address.clear(); 40 | } 41 | 42 | 43 | mail_group::mail_group(const string& group_name, const vector& group_mails) : name(group_name), members(group_mails) 44 | { 45 | } 46 | 47 | 48 | void mail_group::add(const vector& mails) 49 | { 50 | members.insert(members.end(), mails.begin(), mails.end()); 51 | } 52 | 53 | 54 | void mail_group::add(const mail_address& mail) 55 | { 56 | members.push_back(mail); 57 | } 58 | 59 | 60 | void mail_group::clear() 61 | { 62 | name.clear(); 63 | members.clear(); 64 | } 65 | 66 | 67 | mailboxes::mailboxes(vector address_list, vector group_list) : addresses(address_list), groups(group_list) 68 | { 69 | } 70 | 71 | 72 | bool mailboxes::empty() const 73 | { 74 | return addresses.empty() && groups.empty(); 75 | } 76 | 77 | 78 | void mailboxes::clear() 79 | { 80 | addresses.clear(); 81 | groups.clear(); 82 | } 83 | 84 | } // namespace mailio 85 | -------------------------------------------------------------------------------- /src/percent.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | percent.cpp 4 | ----------- 5 | 6 | Copyright (C) 2024, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | using std::string; 22 | using std::stringstream; 23 | using std::vector; 24 | using boost::to_upper_copy; 25 | 26 | 27 | namespace mailio 28 | { 29 | 30 | 31 | percent::percent(string::size_type line1_policy, string::size_type lines_policy) : 32 | codec(line1_policy, lines_policy) 33 | { 34 | } 35 | 36 | 37 | vector percent::encode(const string& txt, const string& charset) const 38 | { 39 | vector enc_text; 40 | string line; 41 | string::size_type line_len = 0; 42 | // Soon as the first line is added, switch the policy to the other lines policy. 43 | string::size_type policy = line1_policy_; 44 | 45 | stringstream enc_line; 46 | enc_line << to_upper_copy(charset) + ATTRIBUTE_CHARSET_SEPARATOR_STR + ATTRIBUTE_CHARSET_SEPARATOR_STR; 47 | for (string::const_iterator ch = txt.begin(); ch != txt.end(); ch++) 48 | { 49 | if (isalnum(*ch)) 50 | { 51 | enc_line << *ch; 52 | line_len++; 53 | } 54 | else 55 | { 56 | enc_line << codec::PERCENT_HEX_FLAG << std::setfill('0') << std::hex << std::uppercase << std::setw(2) << 57 | static_cast(static_cast(*ch)); 58 | line_len += 3; 59 | } 60 | 61 | if (line_len >= policy - 3) 62 | { 63 | enc_text.push_back(enc_line.str()); 64 | enc_line.str(""); 65 | line_len = 0; 66 | policy = lines_policy_; 67 | } 68 | } 69 | enc_text.push_back(enc_line.str()); 70 | 71 | return enc_text; 72 | } 73 | 74 | 75 | string percent::decode(const string& txt) const 76 | { 77 | string dec_text; 78 | for (string::const_iterator ch = txt.begin(); ch != txt.end(); ch++) 79 | { 80 | if (*ch == codec::PERCENT_HEX_FLAG) 81 | { 82 | if (ch + 1 == txt.end() || ch + 2 == txt.end()) 83 | throw codec_error("Bad character."); 84 | if (std::isxdigit(*(ch + 1)) == 0 || std::isxdigit(*(ch + 2)) == 0) 85 | throw codec_error("Bad character."); 86 | 87 | char next_char = toupper(*(ch + 1)); 88 | char next_next_char = toupper(*(ch + 2)); 89 | int nc_val = codec::hex_digit_to_int(next_char); 90 | int nnc_val = codec::hex_digit_to_int(next_next_char); 91 | dec_text += ((nc_val << 4) + nnc_val); 92 | ch += 2; 93 | } 94 | else 95 | dec_text += *ch; 96 | } 97 | return dec_text; 98 | } 99 | 100 | 101 | } // namespace mailio 102 | -------------------------------------------------------------------------------- /src/pop3.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | pop3.cpp 4 | -------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | 25 | using std::istream; 26 | using std::string; 27 | using std::to_string; 28 | using std::vector; 29 | using std::map; 30 | using std::runtime_error; 31 | using std::out_of_range; 32 | using std::invalid_argument; 33 | using std::stoi; 34 | using std::stol; 35 | using std::pair; 36 | using std::make_pair; 37 | using std::tuple; 38 | using std::make_tuple; 39 | using std::move; 40 | using std::make_shared; 41 | using std::chrono::milliseconds; 42 | using boost::algorithm::trim; 43 | using boost::iequals; 44 | 45 | 46 | namespace mailio 47 | { 48 | 49 | 50 | pop3::pop3(const string& hostname, unsigned port, milliseconds timeout) : dlg_(make_shared(hostname, port, timeout)) 51 | { 52 | dlg_->connect(); 53 | } 54 | 55 | 56 | pop3::~pop3() 57 | { 58 | try 59 | { 60 | dlg_->send("QUIT"); 61 | } 62 | catch (...) 63 | { 64 | } 65 | } 66 | 67 | 68 | string pop3::authenticate(const string& username, const string& password, auth_method_t method) 69 | { 70 | string greeting = connect(); 71 | if (method == auth_method_t::LOGIN) 72 | { 73 | auth_login(username, password); 74 | } 75 | return greeting; 76 | } 77 | 78 | 79 | auto pop3::list(unsigned message_no) -> message_list_t 80 | { 81 | message_list_t results; 82 | try 83 | { 84 | if (message_no > 0) 85 | { 86 | dlg_->send("LIST " + to_string(message_no)); 87 | string line = dlg_->receive(); 88 | tuple stat_msg = parse_status(line); 89 | if (iequals(std::get<0>(stat_msg), "-ERR")) 90 | throw pop3_error("Listing message failure.", std::get<1>(stat_msg)); 91 | 92 | // parse data 93 | string::size_type pos = std::get<1>(stat_msg).find(TOKEN_SEPARATOR_CHAR); 94 | if (pos == string::npos) 95 | throw pop3_error("Parser failure.", std::get<1>(stat_msg)); 96 | unsigned msg_id = stoi(std::get<1>(stat_msg).substr(0, pos)); 97 | unsigned long msg_size = stol(std::get<1>(stat_msg).substr(pos + 1)); 98 | results[msg_id] = msg_size; 99 | } 100 | else 101 | { 102 | dlg_->send("LIST"); 103 | string line = dlg_->receive(); 104 | tuple stat_msg = parse_status(line); 105 | if (iequals(std::get<0>(stat_msg), "-ERR")) 106 | throw pop3_error("Listing all messages failure.", std::get<1>(stat_msg)); 107 | 108 | // parse data 109 | bool end_of_msg = false; 110 | while (!end_of_msg) 111 | { 112 | line = dlg_->receive(); 113 | if (line == codec::END_OF_MESSAGE) 114 | end_of_msg = true; 115 | else 116 | { 117 | string::size_type pos = line.find(TOKEN_SEPARATOR_CHAR); 118 | if (pos == string::npos) 119 | throw pop3_error("Parser failure.", "Line `" + line + "` at position " + to_string(pos)); 120 | unsigned msg_id = stoi(line.substr(0, pos)); 121 | unsigned long msg_size = stol(line.substr(pos + 1)); 122 | results[msg_id] = msg_size; 123 | } 124 | } 125 | } 126 | } 127 | catch (const out_of_range& ex) 128 | { 129 | throw pop3_error("Parser failure.", ex.what()); 130 | } 131 | catch (const invalid_argument& ex) 132 | { 133 | throw pop3_error("Parser failure.", ex.what()); 134 | } 135 | 136 | return results; 137 | } 138 | 139 | 140 | auto pop3::uidl(unsigned message_no) -> uidl_list_t 141 | { 142 | uidl_list_t results; 143 | try 144 | { 145 | if (message_no > 0) 146 | { 147 | dlg_->send("UIDL " + to_string(message_no)); 148 | string line = dlg_->receive(); 149 | tuple stat_msg = parse_status(line); 150 | if (iequals(std::get<0>(stat_msg), "-ERR")) 151 | throw pop3_error("UIDL command not supported.", std::get<1>(stat_msg)); 152 | 153 | // parse data 154 | string::size_type pos = std::get<1>(stat_msg).find(TOKEN_SEPARATOR_CHAR); 155 | if (pos == string::npos) 156 | throw pop3_error("No token separator found.", std::get<1>(stat_msg)); 157 | unsigned msg_id = stoi(std::get<1>(stat_msg).substr(0, pos)); 158 | auto msg_uid = std::get<1>(stat_msg).substr(pos + 1); 159 | results[msg_id] = msg_uid; 160 | } 161 | else 162 | { 163 | dlg_->send("UIDL"); 164 | string line = dlg_->receive(); 165 | tuple stat_msg = parse_status(line); 166 | if (iequals(std::get<0>(stat_msg), "-ERR")) 167 | throw pop3_error("Listing all messages failure.", std::get<1>(stat_msg)); 168 | 169 | // parse data 170 | bool end_of_msg = false; 171 | while (!end_of_msg) 172 | { 173 | line = dlg_->receive(); 174 | if (line == codec::END_OF_MESSAGE) 175 | end_of_msg = true; 176 | else 177 | { 178 | string::size_type pos = line.find(TOKEN_SEPARATOR_CHAR); 179 | if (pos == string::npos) 180 | throw pop3_error("No token separator found.", std::get<1>(stat_msg)); 181 | unsigned msg_id = stoi(line.substr(0, pos)); 182 | auto msg_uid = line.substr(pos + 1); 183 | results[msg_id] = msg_uid; 184 | } 185 | } 186 | } 187 | } 188 | catch (const out_of_range& ex) 189 | { 190 | throw pop3_error("Parser failure.", ex.what()); 191 | } 192 | catch (const invalid_argument& ex) 193 | { 194 | throw pop3_error("Parser failure.", ex.what()); 195 | } 196 | 197 | return results; 198 | } 199 | 200 | 201 | auto pop3::statistics() -> mailbox_stat_t 202 | { 203 | dlg_->send("STAT"); 204 | string line = dlg_->receive(); 205 | tuple stat_msg = parse_status(line); 206 | if (iequals(std::get<0>(stat_msg), "-ERR")) 207 | throw pop3_error("Reading statistics failure.", std::get<1>(stat_msg)); 208 | 209 | // parse data 210 | string::size_type pos = std::get<1>(stat_msg).find(TOKEN_SEPARATOR_CHAR); 211 | if (pos == string::npos) 212 | throw pop3_error("No token separator found.", std::get<1>(stat_msg)); 213 | mailbox_stat_t mailbox_stat; 214 | try 215 | { 216 | mailbox_stat.messages_no = stoul(std::get<1>(stat_msg).substr(0, pos)); 217 | mailbox_stat.mailbox_size = stoul(std::get<1>(stat_msg).substr(pos + 1)); 218 | } 219 | catch (const out_of_range& ex) 220 | { 221 | throw pop3_error("Parser failure.", ex.what()); 222 | } 223 | catch (const invalid_argument& ex) 224 | { 225 | throw pop3_error("Parser failure.", ex.what()); 226 | } 227 | 228 | return mailbox_stat; 229 | } 230 | 231 | 232 | void pop3::fetch(unsigned long message_no, message& msg, bool header_only) 233 | { 234 | string line; 235 | if (header_only) 236 | { 237 | dlg_->send("TOP " + to_string(message_no) + " 0"); 238 | line = dlg_->receive(); 239 | tuple stat_msg = parse_status(line); 240 | if (iequals(std::get<0>(stat_msg), "-ERR")) 241 | return; 242 | } 243 | else 244 | { 245 | dlg_->send("RETR " + to_string(message_no)); 246 | line = dlg_->receive(); 247 | tuple stat_msg = parse_status(line); 248 | if (iequals(std::get<0>(stat_msg), "-ERR")) 249 | throw pop3_error("Fetching message failure.", std::get<1>(stat_msg)); 250 | } 251 | 252 | // end of message is marked with crlf+dot+crlf sequence 253 | // empty_line marks the last empty line, so it could be used to detect end of message when dot is reached 254 | bool empty_line = false; 255 | while (true) 256 | { 257 | line = dlg_->receive(); 258 | // reading line by line ensures that crlf are the last characters read; so, reaching single dot in the line means that it's end of message 259 | if (line == codec::END_OF_MESSAGE) 260 | { 261 | // if header only, then mark the header end with the empty line 262 | if (header_only) 263 | msg.parse_by_line(""); 264 | msg.parse_by_line(codec::END_OF_LINE); 265 | break; 266 | } 267 | else if (line.empty()) 268 | { 269 | // ensure that sequence of empty lines are all included in the message; otherwise, mark that an empty line is reached 270 | if (empty_line) 271 | msg.parse_by_line(""); 272 | else 273 | empty_line = true; 274 | } 275 | else 276 | { 277 | // regular line with the content; if empty line was before this one, ensure that it is included 278 | if (empty_line) 279 | msg.parse_by_line(""); 280 | msg.parse_by_line(line, true); 281 | empty_line = false; 282 | } 283 | } 284 | } 285 | 286 | 287 | void pop3::remove(unsigned long message_no) 288 | { 289 | dlg_->send("DELE " + to_string(message_no)); 290 | string line = dlg_->receive(); 291 | tuple stat_msg = parse_status(line); 292 | if (iequals(std::get<0>(stat_msg), "-ERR")) 293 | throw pop3_error("Removing message failure.", std::get<1>(stat_msg)); 294 | } 295 | 296 | 297 | string pop3::connect() 298 | { 299 | string line = dlg_->receive(); 300 | tuple stat_msg = parse_status(line); 301 | if (iequals(std::get<0>(stat_msg), "-ERR")) 302 | throw pop3_error("Connection to server failure.", std::get<1>(stat_msg)); 303 | return std::get<1>(stat_msg); 304 | } 305 | 306 | 307 | void pop3::auth_login(const string& username, const string& password) 308 | { 309 | { 310 | dlg_->send("USER " + username); 311 | string line = dlg_->receive(); 312 | tuple stat_msg = parse_status(line); 313 | if (iequals(std::get<0>(stat_msg), "-ERR")) 314 | throw pop3_error("Username rejection.", std::get<1>(stat_msg)); 315 | } 316 | 317 | { 318 | dlg_->send("PASS " + password); 319 | string line = dlg_->receive(); 320 | tuple stat_msg = parse_status(line); 321 | if (iequals(std::get<0>(stat_msg), "-ERR")) 322 | throw pop3_error("Password rejection.", std::get<1>(stat_msg)); 323 | } 324 | } 325 | 326 | 327 | tuple pop3::parse_status(const string& line) 328 | { 329 | string::size_type pos = line.find(TOKEN_SEPARATOR_CHAR); 330 | string status = line.substr(0, pos); 331 | if (!iequals(status, "+OK") && !iequals(status, "-ERR")) 332 | throw pop3_error("Response status unknown.", status); 333 | string message; 334 | if (pos != string::npos) 335 | message = line.substr(pos + 1); 336 | return make_tuple(status, message); 337 | } 338 | 339 | 340 | pop3s::pop3s(const string& hostname, unsigned port, milliseconds timeout) : pop3(hostname, port, timeout) 341 | { 342 | ssl_options_ = 343 | { 344 | boost::asio::ssl::context::sslv23, 345 | boost::asio::ssl::verify_none 346 | }; 347 | } 348 | 349 | 350 | string pop3s::authenticate(const string& username, const string& password, auth_method_t method) 351 | { 352 | string greeting; 353 | if (method == auth_method_t::LOGIN) 354 | { 355 | switch_to_ssl(); 356 | greeting = connect(); 357 | auth_login(username, password); 358 | } 359 | if (method == auth_method_t::START_TLS) 360 | { 361 | greeting = connect(); 362 | start_tls(); 363 | auth_login(username, password); 364 | } 365 | return greeting; 366 | } 367 | 368 | 369 | void pop3s::ssl_options(const dialog_ssl::ssl_options_t& options) 370 | { 371 | ssl_options_ = options; 372 | } 373 | 374 | 375 | /* 376 | For details see [rfc 2595/4616]. 377 | */ 378 | void pop3s::start_tls() 379 | { 380 | dlg_->send("STLS"); 381 | string response = dlg_->receive(); 382 | tuple stat_msg = parse_status(response); 383 | if (iequals(std::get<0>(stat_msg), "-ERR")) 384 | throw pop3_error("Start TLS failure.", std::get<1>(stat_msg)); 385 | 386 | switch_to_ssl(); 387 | } 388 | 389 | 390 | void pop3s::switch_to_ssl() 391 | { 392 | dlg_ = make_shared(*dlg_, ssl_options_); 393 | } 394 | 395 | 396 | pop3_error::pop3_error(const string& msg, const string& details) : dialog_error(msg, details) 397 | { 398 | } 399 | 400 | 401 | pop3_error::pop3_error(const char* msg, const string& details) : dialog_error(msg, details) 402 | { 403 | } 404 | 405 | 406 | } // namespace mailio 407 | -------------------------------------------------------------------------------- /src/q_codec.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | q_codec.cpp 4 | ----------- 5 | 6 | Copyright (C) 2017, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | using boost::iequals; 21 | using std::string; 22 | using std::vector; 23 | using std::tuple; 24 | using std::make_tuple; 25 | using std::get; 26 | using boost::to_upper_copy; 27 | 28 | 29 | namespace mailio 30 | { 31 | 32 | 33 | const string q_codec::BASE64_CODEC_STR = "B"; 34 | const string q_codec::QP_CODEC_STR = "Q"; 35 | 36 | 37 | q_codec::q_codec(string::size_type line1_policy, string::size_type lines_policy) : 38 | codec(line1_policy, lines_policy) 39 | { 40 | } 41 | 42 | 43 | vector q_codec::encode(const string& text, const string& charset, codec_t method) const 44 | { 45 | // TODO: The constant has to depend of the header length. 46 | const string::size_type Q_FLAGS_LEN = 12; 47 | vector enc_text, text_c; 48 | string codec_flag; 49 | if (method == codec_t::BASE64) 50 | { 51 | codec_flag = BASE64_CODEC_STR; 52 | base64 b64(line1_policy_ - Q_FLAGS_LEN, lines_policy_ - Q_FLAGS_LEN); 53 | text_c = b64.encode(text); 54 | } 55 | else if (method == codec_t::QUOTED_PRINTABLE) 56 | { 57 | codec_flag = QP_CODEC_STR; 58 | quoted_printable qp(line1_policy_ - Q_FLAGS_LEN, lines_policy_ - Q_FLAGS_LEN); 59 | qp.q_codec_mode(true); 60 | text_c = qp.encode(text); 61 | } 62 | else 63 | throw codec_error("Bad encoding method."); 64 | 65 | // TODO: Name the magic constant for Q delimiters. 66 | for (auto s = text_c.begin(); s != text_c.end(); s++) 67 | enc_text.push_back("=?" + to_upper_copy(charset) + "?" + codec_flag + "?" + *s + "?="); 68 | 69 | return enc_text; 70 | } 71 | 72 | 73 | tuple q_codec::decode(const string& text) const 74 | { 75 | string::size_type charset_pos = text.find(QUESTION_MARK_CHAR); 76 | if (charset_pos == string::npos) 77 | throw codec_error("Missing Q codec separator for charset."); 78 | string::size_type method_pos = text.find(QUESTION_MARK_CHAR, charset_pos + 1); 79 | if (method_pos == string::npos) 80 | throw codec_error("Missing Q codec separator for codec type."); 81 | string charset = to_upper_copy(text.substr(charset_pos + 1, method_pos - charset_pos - 1)); 82 | if (charset.empty()) 83 | throw codec_error("Missing Q codec charset."); 84 | string::size_type content_pos = text.find(QUESTION_MARK_CHAR, method_pos + 1); 85 | if (content_pos == string::npos) 86 | throw codec_error("Missing last Q codec separator."); 87 | string method = text.substr(method_pos + 1, content_pos - method_pos - 1); 88 | codec_t method_type; 89 | string text_c = text.substr(content_pos + 1); 90 | 91 | string dec_text; 92 | if (iequals(method, BASE64_CODEC_STR)) 93 | { 94 | base64 b64(line1_policy_, lines_policy_); 95 | dec_text = b64.decode(text_c); 96 | method_type = codec_t::BASE64; 97 | } 98 | else if (iequals(method, QP_CODEC_STR)) 99 | { 100 | dec_text = decode_qp(text_c); 101 | method_type = codec_t::QUOTED_PRINTABLE; 102 | } 103 | else 104 | throw codec_error("Bad encoding method."); 105 | 106 | return make_tuple(dec_text, charset, method_type); 107 | } 108 | 109 | 110 | tuple q_codec::check_decode(const string& text) const 111 | { 112 | string::size_type question_mark_counter = 0; 113 | const string::size_type QUESTION_MARKS_NO = 4; 114 | bool is_encoded = false; 115 | string dec_text, encoded_part; 116 | string charset = CHARSET_ASCII; 117 | // If there is no q encoding, then it's ascii or utf8. 118 | codec_t method_type = codec_t::ASCII; 119 | 120 | for (auto ch = text.begin(); ch != text.end(); ch++) 121 | { 122 | if (*ch == codec::QUESTION_MARK_CHAR) 123 | ++question_mark_counter; 124 | 125 | if (*ch == codec::EQUAL_CHAR && ch + 1 != text.end() && *(ch + 1) == codec::QUESTION_MARK_CHAR && !is_encoded) 126 | is_encoded = true; 127 | else if (*ch == codec::QUESTION_MARK_CHAR && ch + 1 != text.end() && *(ch + 1) == codec::EQUAL_CHAR && question_mark_counter == QUESTION_MARKS_NO) 128 | { 129 | is_encoded = false; 130 | question_mark_counter = 0; 131 | auto text_charset = decode(encoded_part); 132 | dec_text += get<0>(text_charset); 133 | charset = get<1>(text_charset); 134 | method_type = get<2>(text_charset); 135 | 136 | encoded_part.clear(); 137 | ch++; 138 | } 139 | else if (is_encoded == true) 140 | encoded_part.append(1, *ch); 141 | else 142 | dec_text.append(1, *ch); 143 | } 144 | 145 | if (is_encoded && question_mark_counter < QUESTION_MARKS_NO) 146 | throw codec_error("Bad Q codec format."); 147 | 148 | return make_tuple(dec_text, charset, method_type); 149 | } 150 | 151 | 152 | string q_codec::decode_qp(const string& text) const 153 | { 154 | quoted_printable qp(line1_policy_, lines_policy_); 155 | qp.q_codec_mode(true); 156 | vector lines; 157 | lines.push_back(text); 158 | return qp.decode(lines); 159 | } 160 | 161 | 162 | bool q_codec::is_q_allowed(char ch) const 163 | { 164 | return (ch > SPACE_CHAR && ch <= TILDE_CHAR && ch != QUESTION_MARK_CHAR); 165 | } 166 | 167 | 168 | } // namespace mailio 169 | -------------------------------------------------------------------------------- /src/quoted_printable.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | quoted_printable.cpp 4 | -------------------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | using std::string; 23 | using std::vector; 24 | using std::runtime_error; 25 | using boost::trim_right; 26 | 27 | 28 | namespace mailio 29 | { 30 | 31 | 32 | quoted_printable::quoted_printable(string::size_type line1_policy, string::size_type lines_policy) : 33 | codec(line1_policy, lines_policy), q_codec_mode_(false) 34 | { 35 | } 36 | 37 | 38 | vector quoted_printable::encode(const string& text) const 39 | { 40 | vector enc_text; 41 | string line; 42 | string::size_type line_len = 0; 43 | // Soon as the first line is added, switch the policy to the other lines policy. 44 | string::size_type policy = line1_policy_; 45 | std::stringstream strstream; 46 | strstream << std::hex << static_cast(QUESTION_MARK_CHAR); 47 | const string QMARK_HEX = EQUAL_STR + strstream.str(); 48 | 49 | auto add_new_line = [&enc_text, &line_len, &policy, this](string& line) 50 | { 51 | enc_text.push_back(line); 52 | line.clear(); 53 | line_len = 0; 54 | policy = lines_policy_; 55 | }; 56 | 57 | for (auto ch = text.begin(); ch != text.end(); ch++) 58 | { 59 | if (*ch > SPACE_CHAR && *ch <= TILDE_CHAR && *ch != EQUAL_CHAR && *ch != QUESTION_MARK_CHAR) 60 | { 61 | // Add soft break when not q encoding. 62 | if (line_len >= policy - 3) 63 | { 64 | if (q_codec_mode_) 65 | { 66 | line += *ch; 67 | add_new_line(line); 68 | } 69 | else 70 | { 71 | line += EQUAL_CHAR; 72 | add_new_line(line); 73 | line += *ch; 74 | line_len++; 75 | } 76 | } 77 | else 78 | { 79 | line += *ch; 80 | line_len++; 81 | } 82 | } 83 | else if (*ch == SPACE_CHAR) 84 | { 85 | // Add soft break after the current space character if not q encoding. 86 | if (line_len >= policy - 4) 87 | { 88 | if (q_codec_mode_) 89 | { 90 | line += UNDERSCORE_CHAR; 91 | line_len++; 92 | } 93 | else 94 | { 95 | line += SPACE_CHAR; 96 | line += EQUAL_CHAR; 97 | add_new_line(line); 98 | } 99 | } 100 | // Add soft break before the current space character if not q encoding. 101 | else if (line_len >= policy - 3) 102 | { 103 | if (q_codec_mode_) 104 | { 105 | line += UNDERSCORE_CHAR; 106 | line_len++; 107 | } 108 | else 109 | { 110 | line += EQUAL_CHAR; 111 | enc_text.push_back(line); 112 | line.clear(); 113 | line += SPACE_CHAR; 114 | line_len = 1; 115 | policy = lines_policy_; 116 | } 117 | } 118 | else 119 | { 120 | if (q_codec_mode_) 121 | line += UNDERSCORE_CHAR; 122 | else 123 | line += SPACE_CHAR; 124 | line_len++; 125 | } 126 | } 127 | else if (*ch == QUESTION_MARK_CHAR) 128 | { 129 | if (line_len >= policy - 2) 130 | { 131 | if (q_codec_mode_) 132 | { 133 | enc_text.push_back(line); 134 | line.clear(); 135 | line += QMARK_HEX; 136 | line_len = 3; 137 | policy = lines_policy_; 138 | } 139 | else 140 | { 141 | line += *ch; 142 | line_len++; 143 | } 144 | } 145 | else 146 | { 147 | if (q_codec_mode_) 148 | { 149 | line += QMARK_HEX; 150 | line_len += 3; 151 | } 152 | else 153 | { 154 | line += *ch; 155 | line_len++; 156 | } 157 | 158 | } 159 | } 160 | else if (*ch == CR_CHAR) 161 | { 162 | if (q_codec_mode_) 163 | throw codec_error("Bad character `" + string(1, *ch) + "`."); 164 | 165 | if (ch + 1 == text.end() || (ch + 1 != text.end() && *(ch + 1) != LF_CHAR)) 166 | throw codec_error("Bad CRLF sequence."); 167 | add_new_line(line); 168 | // Two characters have to be skipped. 169 | ch++; 170 | } 171 | else 172 | { 173 | // Encode the character. 174 | 175 | auto encode_char = [this, &policy, &line_len, &enc_text](char ch, string& line) 176 | { 177 | enc_text.push_back(line); 178 | line.clear(); 179 | line += EQUAL_CHAR; 180 | line += HEX_DIGITS[((ch >> 4) & 0x0F)]; 181 | line += HEX_DIGITS[(ch & 0x0F)]; 182 | line_len = 3; 183 | policy = lines_policy_; 184 | }; 185 | 186 | if (line_len >= policy - 5) 187 | { 188 | if (!q_codec_mode_) // Add soft break before the current character. 189 | line += EQUAL_CHAR; 190 | encode_char(*ch, line); 191 | } 192 | else 193 | { 194 | // TODO: This encoding is same as in the lambda above. 195 | line += EQUAL_CHAR; 196 | line += HEX_DIGITS[((*ch >> 4) & 0x0F)]; 197 | line += HEX_DIGITS[(*ch & 0x0F)]; 198 | line_len += 3; 199 | } 200 | } 201 | } 202 | if (!line.empty()) 203 | enc_text.push_back(line); 204 | while (!enc_text.empty() && enc_text.back().empty()) 205 | enc_text.pop_back(); 206 | 207 | return enc_text; 208 | } 209 | 210 | 211 | string quoted_printable::decode(const vector& text) const 212 | { 213 | string dec_text; 214 | for (const auto& line : text) 215 | { 216 | if (line.length() > lines_policy_ - 2) 217 | throw codec_error("Bad line policy."); 218 | 219 | bool soft_break = false; 220 | for (string::const_iterator ch = line.begin(); ch != line.end(); ch++) 221 | { 222 | if (!is_allowed(*ch)) 223 | throw codec_error("Bad character `" + string(1, *ch) + "`."); 224 | 225 | if (*ch == EQUAL_CHAR) 226 | { 227 | if ((ch + 1) == line.end() && !q_codec_mode_) 228 | { 229 | soft_break = true; 230 | continue; 231 | } 232 | 233 | // Avoid exception: Convert to uppercase. 234 | char next_char = toupper(*(ch + 1)); 235 | char next_next_char = toupper(*(ch + 2)); 236 | if (!is_allowed(next_char) || !is_allowed(next_next_char)) 237 | throw codec_error("Bad character."); 238 | 239 | if (HEX_DIGITS.find(next_char) == string::npos || HEX_DIGITS.find(next_next_char) == string::npos) 240 | throw codec_error("Bad hexadecimal digit."); 241 | int nc_val = hex_digit_to_int(next_char); 242 | int nnc_val = hex_digit_to_int(next_next_char); 243 | dec_text += ((nc_val << 4) + nnc_val); 244 | ch += 2; 245 | } 246 | else 247 | { 248 | if (q_codec_mode_ && *ch == UNDERSCORE_CHAR) 249 | dec_text += SPACE_CHAR; 250 | else 251 | dec_text += *ch; 252 | } 253 | } 254 | if (!soft_break && !q_codec_mode_) 255 | dec_text += END_OF_LINE; 256 | } 257 | trim_right(dec_text); 258 | 259 | return dec_text; 260 | } 261 | 262 | 263 | void quoted_printable::q_codec_mode(bool mode) 264 | { 265 | q_codec_mode_ = mode; 266 | } 267 | 268 | 269 | bool quoted_printable::is_allowed(char ch) const 270 | { 271 | return ((ch >= SPACE_CHAR && ch <= TILDE_CHAR) || ch == '\t'); 272 | } 273 | 274 | 275 | } // namespace mailio 276 | -------------------------------------------------------------------------------- /src/smtp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | smtp.cpp 4 | --------------- 5 | 6 | Copyright (C) 2016, Tomislav Karastojkovic (http://www.alepho.com). 7 | 8 | Distributed under the FreeBSD license, see the accompanying file LICENSE or 9 | copy at http://www.freebsd.org/copyright/freebsd-license.html. 10 | 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | 24 | using std::ostream; 25 | using std::istream; 26 | using std::vector; 27 | using std::string; 28 | using std::to_string; 29 | using std::tuple; 30 | using std::stoi; 31 | using std::move; 32 | using std::make_shared; 33 | using std::runtime_error; 34 | using std::out_of_range; 35 | using std::invalid_argument; 36 | using std::chrono::milliseconds; 37 | using boost::asio::ip::host_name; 38 | using boost::system::system_error; 39 | 40 | 41 | namespace mailio 42 | { 43 | 44 | 45 | smtp::smtp(const string& hostname, unsigned port, milliseconds timeout) : dlg_(make_shared(hostname, port, timeout)) 46 | { 47 | src_host_ = read_hostname(); 48 | dlg_->connect(); 49 | } 50 | 51 | 52 | smtp::~smtp() 53 | { 54 | try 55 | { 56 | dlg_->send("QUIT"); 57 | } 58 | catch (...) 59 | { 60 | } 61 | } 62 | 63 | 64 | string smtp::authenticate(const string& username, const string& password, auth_method_t method) 65 | { 66 | string greeting = connect(); 67 | if (method == auth_method_t::NONE) 68 | { 69 | ehlo(); 70 | } 71 | else if (method == auth_method_t::LOGIN) 72 | { 73 | ehlo(); 74 | auth_login(username, password); 75 | } 76 | return greeting; 77 | } 78 | 79 | 80 | string smtp::submit(const message& msg) 81 | { 82 | if (!msg.sender().address.empty()) 83 | dlg_->send("MAIL FROM: " + message::ADDRESS_BEGIN_STR + msg.sender().address + message::ADDRESS_END_STR); 84 | else 85 | dlg_->send("MAIL FROM: " + message::ADDRESS_BEGIN_STR + msg.from().addresses.at(0).address + message::ADDRESS_END_STR); 86 | string line = dlg_->receive(); 87 | tuple tokens = parse_line(line); 88 | if (std::get<1>(tokens) && !positive_completion(std::get<0>(tokens))) 89 | throw smtp_error("Mail sender rejection.", std::get<2>(tokens)); 90 | 91 | for (const auto& rcpt : msg.recipients().addresses) 92 | { 93 | dlg_->send("RCPT TO: " + message::ADDRESS_BEGIN_STR + rcpt.address + message::ADDRESS_END_STR); 94 | line = dlg_->receive(); 95 | tokens = parse_line(line); 96 | if (!positive_completion(std::get<0>(tokens))) 97 | throw smtp_error("Mail recipient rejection.", std::get<2>(tokens)); 98 | } 99 | 100 | for (const auto& rcpt : msg.recipients().groups) 101 | { 102 | dlg_->send("RCPT TO: " + message::ADDRESS_BEGIN_STR + rcpt.name + message::ADDRESS_END_STR); 103 | line = dlg_->receive(); 104 | tokens = parse_line(line); 105 | if (!positive_completion(std::get<0>(tokens))) 106 | throw smtp_error("Mail group recipient rejection.", std::get<2>(tokens)); 107 | } 108 | 109 | for (const auto& rcpt : msg.cc_recipients().addresses) 110 | { 111 | dlg_->send("RCPT TO: " + message::ADDRESS_BEGIN_STR + rcpt.address + message::ADDRESS_END_STR); 112 | line = dlg_->receive(); 113 | tokens = parse_line(line); 114 | if (!positive_completion(std::get<0>(tokens))) 115 | throw smtp_error("Mail cc recipient rejection.", std::get<2>(tokens)); 116 | } 117 | 118 | for (const auto& rcpt : msg.cc_recipients().groups) 119 | { 120 | dlg_->send("RCPT TO: " + message::ADDRESS_BEGIN_STR + rcpt.name + message::ADDRESS_END_STR); 121 | line = dlg_->receive(); 122 | tokens = parse_line(line); 123 | if (!positive_completion(std::get<0>(tokens))) 124 | throw smtp_error("Mail group cc recipient rejection.", std::get<2>(tokens)); 125 | } 126 | 127 | for (const auto& rcpt : msg.bcc_recipients().addresses) 128 | { 129 | dlg_->send("RCPT TO: " + message::ADDRESS_BEGIN_STR + rcpt.address + message::ADDRESS_END_STR); 130 | line = dlg_->receive(); 131 | tokens = parse_line(line); 132 | if (!positive_completion(std::get<0>(tokens))) 133 | throw smtp_error("Mail bcc recipient rejection.", std::get<2>(tokens)); 134 | } 135 | 136 | for (const auto& rcpt : msg.bcc_recipients().groups) 137 | { 138 | dlg_->send("RCPT TO: " + message::ADDRESS_BEGIN_STR + rcpt.name + message::ADDRESS_END_STR); 139 | line = dlg_->receive(); 140 | tokens = parse_line(line); 141 | if (!positive_completion(std::get<0>(tokens))) 142 | throw smtp_error("Mail group bcc recipient rejection.", std::get<2>(tokens)); 143 | } 144 | 145 | dlg_->send("DATA"); 146 | line = dlg_->receive(); 147 | tokens = parse_line(line); 148 | if (!positive_intermediate(std::get<0>(tokens))) 149 | throw smtp_error("Mail message rejection.", std::get<2>(tokens)); 150 | 151 | string msg_str; 152 | msg.format(msg_str, {/*dot_escape*/true}); 153 | dlg_->send(msg_str + codec::END_OF_LINE + codec::END_OF_MESSAGE); 154 | line = dlg_->receive(); 155 | tokens = parse_line(line); 156 | if (!positive_completion(std::get<0>(tokens))) 157 | throw smtp_error("Mail message rejection.", std::get<2>(tokens)); 158 | return std::get<2>(tokens); 159 | } 160 | 161 | 162 | void smtp::source_hostname(const string& src_host) 163 | { 164 | src_host_ = src_host; 165 | } 166 | 167 | 168 | string smtp::source_hostname() const 169 | { 170 | return src_host_; 171 | } 172 | 173 | 174 | string smtp::connect() 175 | { 176 | string greeting; 177 | string line = dlg_->receive(); 178 | tuple tokens = parse_line(line); 179 | while (!std::get<1>(tokens)) 180 | { 181 | greeting += std::get<2>(tokens) + to_string(codec::CR_CHAR) + to_string(codec::LF_CHAR); 182 | line = dlg_->receive(); 183 | tokens = parse_line(line); 184 | } 185 | if (std::get<0>(tokens) != SERVICE_READY_STATUS) 186 | throw smtp_error("Connection rejection.", std::get<2>(tokens)); 187 | greeting += std::get<2>(tokens); 188 | return greeting; 189 | } 190 | 191 | 192 | void smtp::auth_login(const string& username, const string& password) 193 | { 194 | dlg_->send("AUTH LOGIN"); 195 | string line = dlg_->receive(); 196 | tuple tokens = parse_line(line); 197 | if (std::get<1>(tokens) && !positive_intermediate(std::get<0>(tokens))) 198 | throw smtp_error("Authentication rejection.", std::get<2>(tokens)); 199 | 200 | // TODO: Use static encode from the Base64 codec. 201 | base64 b64(static_cast(codec::line_len_policy_t::RECOMMENDED), static_cast(codec::line_len_policy_t::RECOMMENDED)); 202 | auto user_v = b64.encode(username); 203 | string cmd = user_v.empty() ? "" : user_v[0]; 204 | dlg_->send(cmd); 205 | line = dlg_->receive(); 206 | tokens = parse_line(line); 207 | if (std::get<1>(tokens) && !positive_intermediate(std::get<0>(tokens))) 208 | throw smtp_error("Username rejection.", std::get<2>(tokens)); 209 | 210 | auto pass_v = b64.encode(password); 211 | cmd = pass_v.empty() ? "" : pass_v[0]; 212 | dlg_->send(cmd); 213 | line = dlg_->receive(); 214 | tokens = parse_line(line); 215 | if (std::get<1>(tokens) && !positive_completion(std::get<0>(tokens))) 216 | throw smtp_error("Password rejection.", std::get<2>(tokens)); 217 | } 218 | 219 | 220 | void smtp::ehlo() 221 | { 222 | dlg_->send("EHLO " + src_host_); 223 | string line = dlg_->receive(); 224 | tuple tokens = parse_line(line); 225 | while (!std::get<1>(tokens)) 226 | { 227 | line = dlg_->receive(); 228 | tokens = parse_line(line); 229 | } 230 | 231 | if (!positive_completion(std::get<0>(tokens))) 232 | { 233 | dlg_->send("HELO " + src_host_); 234 | 235 | line = dlg_->receive(); 236 | tokens = parse_line(line); 237 | while (!std::get<1>(tokens)) 238 | { 239 | line = dlg_->receive(); 240 | tokens = parse_line(line); 241 | } 242 | if (!positive_completion(std::get<0>(tokens))) 243 | throw smtp_error("Initial message rejection.", std::get<2>(tokens)); 244 | } 245 | } 246 | 247 | 248 | string smtp::read_hostname() 249 | { 250 | try 251 | { 252 | return host_name(); 253 | } 254 | catch (system_error&) 255 | { 256 | throw smtp_error("Reading hostname failure.", ""); 257 | } 258 | } 259 | 260 | 261 | tuple smtp::parse_line(const string& line) 262 | { 263 | try 264 | { 265 | return make_tuple(stoi(line.substr(0, 3)), (line.at(3) == '-' ? false : true), line.substr(4)); 266 | } 267 | catch (out_of_range&) 268 | { 269 | throw smtp_error("Parsing server failure.", ""); 270 | } 271 | catch (invalid_argument&) 272 | { 273 | throw smtp_error("Parsing server failure.", ""); 274 | } 275 | } 276 | 277 | 278 | inline bool smtp::positive_completion(int status) 279 | { 280 | return status / 100 == smtp_status_t::POSITIVE_COMPLETION; 281 | } 282 | 283 | 284 | inline bool smtp::positive_intermediate(int status) 285 | { 286 | return status / 100 == smtp_status_t::POSITIVE_INTERMEDIATE; 287 | } 288 | 289 | 290 | inline bool smtp::transient_negative(int status) 291 | { 292 | return status / 100 == smtp_status_t::TRANSIENT_NEGATIVE; 293 | } 294 | 295 | 296 | inline bool smtp::permanent_negative(int status) 297 | { 298 | return status / 100 == smtp_status_t::PERMANENT_NEGATIVE; 299 | } 300 | 301 | 302 | smtps::smtps(const string& hostname, unsigned port, milliseconds timeout) : smtp(hostname, port, timeout) 303 | { 304 | ssl_options_ = 305 | { 306 | boost::asio::ssl::context::sslv23, 307 | boost::asio::ssl::verify_none 308 | }; 309 | } 310 | 311 | 312 | string smtps::authenticate(const string& username, const string& password, auth_method_t method) 313 | { 314 | string greeting; 315 | if (method == auth_method_t::NONE) 316 | { 317 | switch_to_ssl(); 318 | greeting = connect(); 319 | ehlo(); 320 | } 321 | else if (method == auth_method_t::LOGIN) 322 | { 323 | switch_to_ssl(); 324 | greeting = connect(); 325 | ehlo(); 326 | auth_login(username, password); 327 | } 328 | else if (method == auth_method_t::START_TLS) 329 | { 330 | greeting = connect(); 331 | ehlo(); 332 | start_tls(); 333 | auth_login(username, password); 334 | } 335 | return greeting; 336 | } 337 | 338 | 339 | void smtps::ssl_options(const dialog_ssl::ssl_options_t& options) 340 | { 341 | ssl_options_ = options; 342 | } 343 | 344 | 345 | void smtps::start_tls() 346 | { 347 | dlg_->send("STARTTLS"); 348 | string line = dlg_->receive(); 349 | tuple tokens = parse_line(line); 350 | if (std::get<1>(tokens) && std::get<0>(tokens) != SERVICE_READY_STATUS) 351 | throw smtp_error("Start tls refused by server.", std::get<2>(tokens)); 352 | 353 | switch_to_ssl(); 354 | ehlo(); 355 | } 356 | 357 | 358 | void smtps::switch_to_ssl() 359 | { 360 | dlg_ = make_shared(*dlg_, ssl_options_); 361 | } 362 | 363 | 364 | smtp_error::smtp_error(const string& msg, const string& details) : dialog_error(msg, details) 365 | { 366 | } 367 | 368 | 369 | smtp_error::smtp_error(const char* msg, const string& details) : dialog_error(msg, details) 370 | { 371 | } 372 | 373 | 374 | } // namespace mailio 375 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Boost REQUIRED COMPONENTS unit_test_framework) 2 | 3 | function(add_mailio_tests SOURCE_FILE) 4 | get_filename_component(file_name ${SOURCE_FILE} NAME_WE) 5 | add_executable(${file_name} ${SOURCE_FILE}) 6 | add_test(NAME ${file_name} COMMAND ${file_name}) 7 | include("CTest") 8 | set_tests_properties(${file_name} PROPERTIES WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/test") 9 | if(${MAILIO_DYN_LINK_TESTS}) 10 | add_definitions(-DBOOST_TEST_DYN_LINK) 11 | endif() 12 | target_link_directories(${file_name} PUBLIC ${Boost_LIBRARY_DIRS}) 13 | target_link_libraries(${file_name} PUBLIC ${Boost_LIBRARIES} mailio ${CMAKE_THREAD_LIBS_INIT}) 14 | install(TARGETS ${file_name} DESTINATION "${SHARE_INSTALL_DIR}/${PROJECT_NAME}/test") 15 | endfunction(add_mailio_tests) 16 | 17 | file(GLOB test_files ${CMAKE_CURRENT_SOURCE_DIR}/test*.cpp) 18 | foreach(file_name ${test_files}) 19 | add_mailio_tests(${file_name}) 20 | endforeach(file_name ${test_files}) 21 | -------------------------------------------------------------------------------- /test/aleph0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karastojko/mailio/ebbd2af5a62a4c3b51167fded93951dfdd5a8c3e/test/aleph0.png -------------------------------------------------------------------------------- /test/cv.txt: -------------------------------------------------------------------------------- 1 | Tomislav Karastojković CV 2 | --------------------------------------------------------------------------------