From c3f7717ce53081db721dbb1cc21936b7e4e2671f Mon Sep 17 00:00:00 2001 From: Daniel Samson Date: Thu, 30 Aug 2018 11:30:31 +0100 Subject: [PATCH] Add initial version 0.1.0 --- .github/CONTRIBUTING.md | 46 + .github/ISSUE_TEMPLATE.md | 36 + .github/PULL_REQUEST_TEMPLATE.md | 27 + .gitignore | 11 + .travis.yml | 29 + CODE_OF_CONDUCT.md | 35 + COPYRIGHT | 17 + README.md | 82 + RoboFile.php | 337 ++++ codeception.yml | 19 + composer.json | 42 + docs/_index.md | 1 + docs/config.json | 26 + docs/en/00_Introduction.md | 21 + docs/en/01_Getting_Started.md | 139 ++ docs/en/02_Managers.md | 42 + docs/en/03_Commands.md | 476 ++++++ docs/en/04_Responses.md | 135 ++ docs/en/05_Pipeline.md | 101 ++ docs/en/06_Architecture.md | 164 ++ .../CommandBuildArgumentsInterface.php | 65 + .../CommandBuilder/CommandBuildInterface.php | 34 + .../CommandBuilder/CommandRawInterface.php | 36 + .../Command/NoopCommandValidator.php | 52 + .../CommandValidationInterface.php | 43 + .../CommandValidationStatus.php | 37 + .../CommandValidatorAwareInterface.php | 35 + .../AppendCommandArgumentsInterface.php | 46 + .../Commands/AppendCommandInterface.php | 36 + .../Commands/CapabilityCommandInterface.php | 37 + .../Commands/CheckCommandInterface.php | 36 + .../Commands/CloseCommandInterface.php | 37 + .../CopyCommandArgumentsInterface.php | 41 + .../Commands/CopyCommandInterface.php | 37 + .../Commands/CreateCommandInterface.php | 35 + .../Commands/DeleteCommandInterface.php | 35 + .../Commands/ExamineCommandInterface.php | 37 + .../Commands/ExpungeCommandInterface.php | 37 + .../FetchCommandArgumentsInterface.php | 52 + .../Commands/FetchCommandInterface.php | 58 + .../Commands/IdleCommandInterface.php | 33 + .../Commands/LSubCommandInterface.php | 39 + .../Commands/ListCommandInterface.php | 40 + .../Commands/LoginCommandInterface.php | 35 + .../LoginCommandPasswordArgumentInterface.php | 35 + .../LoginCommandUserArgumentInterface.php | 35 + .../Commands/LogoutCommandInterface.php | 36 + .../Commands/NoopCommandInterface.php | 31 + .../Commands/RenameCommandInterface.php | 36 + .../SearchCommandArgumentsInterface.php | 305 ++++ .../Commands/SearchCommandInterface.php | 38 + .../Commands/SelectCommandInterface.php | 35 + .../SortCommandArgumentsInterface.php | 85 + .../Commands/SortCommandInterface.php | 36 + .../Commands/StartTlsCommandInterface.php | 31 + .../StatusCommandArgumentsInterface.php | 53 + .../Commands/StatusCommandInterface.php | 34 + .../StoreCommandArgumentsInterface.php | 67 + .../Commands/StoreCommandInterface.php | 33 + .../Commands/SubscribeCommandInterface.php | 37 + .../Commands/UidCommandInterface.php | 40 + .../Commands/UnsubscribeCommandInterface.php | 37 + .../Commands/XCommandInterface.php | 38 + .../PhpImapExtensionCommandBuilder.php | 1358 ++++++++++++++++ ...mapExtensionSupportedCommandsInterface.php | 37 + ...sionSupportedTopLevelCommandsInterface.php | 54 + .../CommandBuilder/PimapCommandBuilder.php | 1382 +++++++++++++++++ .../PimapSupportedCommandsInterface.php | 64 + ...imapSupportedTopLevelCommandsInterface.php | 56 + src/Imap/Enumerator/Format.php | 16 + src/Imap/Enumerator/ResponseCode.php | 32 + src/Imap/ImapException.php | 62 + .../Interpreter/CapabilityInterpreter.php | 64 + src/Imap/Interpreter/EncodingInterpreter.php | 43 + src/Imap/Interpreter/LexemeInterpreter.php | 144 ++ src/Imap/Interpreter/MailboxInterpreter.php | 188 +++ .../Interpreter/MailboxListInterpreter.php | 106 ++ src/Imap/Interpreter/MessageInterpreter.php | 490 ++++++ src/Imap/Interpreter/OctetInterpreter.php | 101 ++ src/Imap/Interpreter/ResponseInterpreter.php | 82 + src/Imap/Interpreter/Rfc2822Interpreter.php | 737 +++++++++ src/Imap/Interpreter/SearchInterpreter.php | 60 + .../Interpreter/StringIteratorInterpreter.php | 28 + src/Imap/Lexeme/Lexeme.php | 235 +++ src/Imap/Lexeme/LexemeIteratorInterface.php | 30 + src/Imap/Lexeme/LexemeList.php | 142 ++ src/Imap/Lexeme/LexemeType.php | 1127 ++++++++++++++ src/Imap/Lexeme/Lexemizer.php | 347 +++++ src/Imap/Lexeme/keywords.yaml | 268 ++++ src/Imap/Manager/ManagerInterface.php | 55 + src/Imap/Manager/PhpImapExtensionManager.php | 118 ++ src/Imap/Manager/PimapManager.php | 257 +++ src/Imap/ManagerFactory.php | 128 ++ src/Imap/Pipeline/Pipe.php | 188 +++ src/Imap/Pipeline/PipeInterface.php | 108 ++ src/Imap/Pipeline/PipeLineAwareInterface.php | 35 + src/Imap/Pipeline/Pipeline.php | 172 ++ src/Imap/Pipeline/PipelineInterface.php | 50 + .../Pipeline/PipelineIteratorInterface.php | 67 + src/Imap/Response/Capability.php | 47 + src/Imap/Response/Mailbox.php | 222 +++ src/Imap/Response/MailboxList.php | 121 ++ src/Imap/Response/Message.php | 211 +++ src/Imap/Response/MessageAttachment.php | 190 +++ .../Response/MessageAttachmentInterface.php | 49 + .../Response/MessageAttachmentStructure.php | 149 ++ .../MessageAttachmentStructureInterface.php | 43 + src/Imap/Response/MessageBody.php | 164 ++ src/Imap/Response/MessageBodyInterface.php | 49 + src/Imap/Response/MessageBodyStructure.php | 185 +++ .../MessageBodyStructureInterface.php | 44 + src/Imap/Response/MessageFactory.php | 47 + src/Imap/Response/MessageFlags.php | 141 ++ src/Imap/Response/MessageFlagsInterface.php | 58 + src/Imap/Response/MessageHeader.php | 270 ++++ src/Imap/Response/MessageHeaderInterface.php | 68 + src/Imap/Response/MessageInterface.php | 49 + src/Imap/Response/MessageList.php | 169 ++ src/Imap/Response/Response.php | 76 + .../Stream/CommandTransporterInterface.php | 38 + src/Imap/Stream/MessageTransporter.php | 175 +++ .../PhpImapExtensionMessageTransporter.php | 849 ++++++++++ src/Imap/Stream/PhpImpExtensionConnection.php | 137 ++ src/Imap/Token/Token.php | 185 +++ src/Imap/Token/TokenException.php | 40 + src/Imap/Token/TokenIteratorInterface.php | 41 + src/Imap/Token/TokenList.php | 130 ++ src/Imap/Token/TokenType.php | 322 ++++ src/Imap/Token/Tokenizer.php | 755 +++++++++ src/Iteration/StringIterator.php | 212 +++ src/Iteration/StringIteratorInterface.php | 86 + src/Pattern/ContainerAwareInterface.php | 37 + src/Pattern/Singleton.php | 31 + src/Stream/Connection.php | 162 ++ src/Stream/ConnectionException.php | 49 + src/Stream/MessageTransporter.php | 108 ++ src/Stream/MessageTransporterInterface.php | 54 + src/Stream/MockConnection.php | 155 ++ src/Stream/StreamConnectionInterface.php | 77 + src/Utility/Assert.php | 41 + src/Utility/PimapLogger.php | 96 ++ src/Utility/StringValue.php | 57 + tests/_data/.gitkeep | 0 ...38081_php_imap_extension_command.serialise | Bin 0 -> 854 bytes ...ap_extension_imap_fetch_overview.serialise | 1 + ...ap_extension_imap_fetchstructure.serialise | 1 + ...38084_php_imap_extension_message.serialise | Bin 0 -> 1916 bytes ...38085_php_imap_extension_command.serialise | Bin 0 -> 782 bytes ...ap_extension_imap_fetch_overview.serialise | 1 + ...ap_extension_imap_fetchstructure.serialise | 1 + ...088_php_imap_extension_imap_body.serialise | 2 + ...38089_php_imap_extension_message.serialise | Bin 0 -> 1955 bytes ...38090_php_imap_extension_command.serialise | Bin 0 -> 854 bytes ...ap_extension_imap_fetch_overview.serialise | 1 + ...ap_extension_imap_fetchstructure.serialise | 1 + ...38094_php_imap_extension_message.serialise | Bin 0 -> 1917 bytes ...38095_php_imap_extension_command.serialise | Bin 0 -> 782 bytes ...ap_extension_imap_fetch_overview.serialise | 1 + ...ap_extension_imap_fetchstructure.serialise | 1 + ...hp_imap_extension_imap_fetchbody.serialise | 13 + ...hp_imap_extension_imap_fetchbody.serialise | 2 + ...hp_imap_extension_imap_fetchbody.serialise | 3 + ...hp_imap_extension_imap_fetchbody.serialise | 2 + ...38102_php_imap_extension_message.serialise | Bin 0 -> 2769 bytes tests/_data/BoundaryContainsSeparators.txt | 891 +++++++++++ tests/_data/CAPABILITY.txt | 1 + tests/_data/FETCH_BODY.txt | 1 + tests/_data/FETCH_BODYSTRUCTURE.txt | 1 + tests/_data/FETCH_BODY_HEADER.txt | 20 + .../_data/FETCH_BODY_HEADER_WITH_RESPONSE.txt | 21 + tests/_data/FETCH_EMAIL_EMBEDDED_IMAGE.txt | 39 + tests/_data/FETCH_FLAGS.txt | 1 + ...H_FLAGS_BODY_HEADER_BODYSTRUCTURE_UTF8.txt | 26 + tests/_data/FETCH_RANGE.txt | 1265 +++++++++++++++ tests/_data/FETCH_UID.txt | 1 + ...CH_UID_FLAGS_BODY_HEADER_BODYSTRUCTURE.txt | 21 + ...GS_BODY_HEADER_BODYSTRUCTURE_BODY_TEXT.txt | 49 + ...ID_BODY_HEADER_BODYSTRUCTURE_BODY_TEXT.txt | 58 + tests/_data/LIST.txt | 9 + tests/_data/LineLengthDetectedIncorrectly.txt | 21 + tests/_data/PASSIVE_CAPABILITY.txt | 1 + ...CH_1_20_FLAGS_UID_HEADER_BODYSTRUCTURE.txt | 14 + ...ETCH_UID_BODY_HEADER_BODYSTUCTURE_BODY.txt | 15 + tests/_data/QUOTES_ISSUE.txt | 3 + tests/_data/SEARCH_1_5.txt | 2 + tests/_data/SELECT_INBOX.txt | 8 + tests/_data/server_greeting.txt | 1 + tests/_output/.gitignore | 2 + tests/_support/AcceptanceTester.php | 26 + tests/_support/FunctionalTester.php | 26 + tests/_support/Helper/Acceptance.php | 10 + tests/_support/Helper/Functional.php | 10 + tests/_support/Helper/Unit.php | 10 + tests/_support/UnitTester.php | 26 + tests/_support/_generated/.gitignore | 2 + tests/acceptance.suite.yml | 12 + tests/acceptance/_bootstrap.php | 1 + tests/functional.suite.yml | 13 + tests/functional/_bootstrap.php | 1 + tests/mock/imap-ext.php | 315 ++++ tests/mock/stream.php | 83 + tests/unit.suite.yml | 9 + tests/unit/CommonErrorsTest.php | 46 + .../PhpImapExtensionCommandBuilderTest.php | 1073 +++++++++++++ .../PimapCommandBuilderTest.php | 1086 +++++++++++++ .../Validator/NoopCommandValidatorTest.php | 57 + .../SalesAgility/Imap/ImapExceptionTest.php | 45 + .../Imap/ImapManagerFactoryTest.php | 66 + .../Interpreter/CapabilityInterpreterTest.php | 45 + .../Interpreter/LexemeInterpreterTest.php | 36 + .../Interpreter/MailboxInterpreterTest.php | 46 + .../MailboxListInterpreterTest.php | 41 + .../Interpreter/MessageInterpreterTest.php | 227 +++ .../Interpreter/SearchInterpreterTest.php | 46 + .../Imap/Lexeme/LexemeListTest.php | 229 +++ .../SalesAgility/Imap/Lexeme/LexemeTest.php | 226 +++ .../Imap/Lexeme/LexemeTypeTest.php | 988 ++++++++++++ .../Imap/Lexeme/LexemizerTest.php | 324 ++++ .../Manager/PhpImapExtensionManagerTest.php | 111 ++ .../Imap/Manager/PimapManagerTest.php | 380 +++++ .../SalesAgility/Imap/Pipeline/PipeTest.php | 257 +++ .../Imap/Pipeline/PipelineTest.php | 172 ++ .../Imap/Response/CapabilityTest.php | 55 + .../Imap/Response/MailboxListTest.php | 153 ++ .../Imap/Response/MailboxTest.php | 164 ++ .../MessageAttachmentStructureTest.php | 115 ++ .../Imap/Response/MessageAttachmentTest.php | 150 ++ .../Response/MessageBodyStructureTest.php | 139 ++ .../Imap/Response/MessageBodyTest.php | 137 ++ .../Imap/Response/MessageFlagsTest.php | 155 ++ .../Imap/Response/MessageHeaderTest.php | 270 ++++ .../Imap/Response/MessageListTest.php | 261 ++++ .../Imap/Response/MessageTest.php | 245 +++ .../Imap/Stream/MessageTransporterTest.php | 144 ++ .../Stream/PhpImapExtensionConnectionTest.php | 147 ++ ...PhpImapExtensionMessageTransporterTest.php | 409 +++++ .../SalesAgility/Imap/Token/TokenListTest.php | 161 ++ .../SalesAgility/Imap/Token/TokenTest.php | 63 + .../SalesAgility/Imap/Token/TokenTypeTest.php | 256 +++ .../SalesAgility/Imap/Token/TokenizerTest.php | 877 +++++++++++ .../Iteration/StringIteratorTest.php | 155 ++ .../Iteration/StringValueTest.php | 72 + .../Stream/ConnectionExceptionTest.php | 38 + .../SalesAgility/Stream/ConnectionTest.php | 296 ++++ .../Stream/MessageTransporterTest.php | 115 ++ .../Stream/MockConnectionTest.php | 237 +++ .../unit/SalesAgility/Utility/AssertTest.php | 44 + .../SalesAgility/Utility/PimapLoggerTest.php | 38 + tests/unit/_bootstrap.php | 18 + 249 files changed, 31881 insertions(+) create mode 100755 .github/CONTRIBUTING.md create mode 100755 .github/ISSUE_TEMPLATE.md create mode 100755 .github/PULL_REQUEST_TEMPLATE.md create mode 100755 .gitignore create mode 100755 .travis.yml create mode 100755 CODE_OF_CONDUCT.md create mode 100755 COPYRIGHT create mode 100644 README.md create mode 100755 RoboFile.php create mode 100755 codeception.yml create mode 100755 composer.json create mode 100644 docs/_index.md create mode 100644 docs/config.json create mode 100644 docs/en/00_Introduction.md create mode 100644 docs/en/01_Getting_Started.md create mode 100644 docs/en/02_Managers.md create mode 100644 docs/en/03_Commands.md create mode 100644 docs/en/04_Responses.md create mode 100644 docs/en/05_Pipeline.md create mode 100644 docs/en/06_Architecture.md create mode 100755 src/Imap/CommandBuilder/CommandBuildArgumentsInterface.php create mode 100755 src/Imap/CommandBuilder/CommandBuildInterface.php create mode 100755 src/Imap/CommandBuilder/CommandRawInterface.php create mode 100755 src/Imap/CommandBuilder/CommandValidator/Command/NoopCommandValidator.php create mode 100755 src/Imap/CommandBuilder/CommandValidator/CommandValidationInterface.php create mode 100755 src/Imap/CommandBuilder/CommandValidator/CommandValidationStatus.php create mode 100755 src/Imap/CommandBuilder/CommandValidator/CommandValidatorAwareInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/AppendCommandArgumentsInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/AppendCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/CapabilityCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/CheckCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/CloseCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/CopyCommandArgumentsInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/CopyCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/CreateCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/DeleteCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/ExamineCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/ExpungeCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/FetchCommandArgumentsInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/FetchCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/IdleCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/LSubCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/ListCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/LoginCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/LoginCommandPasswordArgumentInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/LoginCommandUserArgumentInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/LogoutCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/NoopCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/RenameCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/SearchCommandArgumentsInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/SearchCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/SelectCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/SortCommandArgumentsInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/SortCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/StartTlsCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/StatusCommandArgumentsInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/StatusCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/StoreCommandArgumentsInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/StoreCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/SubscribeCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/UidCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/UnsubscribeCommandInterface.php create mode 100755 src/Imap/CommandBuilder/Commands/XCommandInterface.php create mode 100755 src/Imap/CommandBuilder/PhpImapExtensionCommandBuilder.php create mode 100755 src/Imap/CommandBuilder/PhpImapExtensionSupportedCommandsInterface.php create mode 100755 src/Imap/CommandBuilder/PhpImapExtensionSupportedTopLevelCommandsInterface.php create mode 100755 src/Imap/CommandBuilder/PimapCommandBuilder.php create mode 100755 src/Imap/CommandBuilder/PimapSupportedCommandsInterface.php create mode 100755 src/Imap/CommandBuilder/PimapSupportedTopLevelCommandsInterface.php create mode 100644 src/Imap/Enumerator/Format.php create mode 100755 src/Imap/Enumerator/ResponseCode.php create mode 100755 src/Imap/ImapException.php create mode 100755 src/Imap/Interpreter/CapabilityInterpreter.php create mode 100755 src/Imap/Interpreter/EncodingInterpreter.php create mode 100755 src/Imap/Interpreter/LexemeInterpreter.php create mode 100644 src/Imap/Interpreter/MailboxInterpreter.php create mode 100755 src/Imap/Interpreter/MailboxListInterpreter.php create mode 100755 src/Imap/Interpreter/MessageInterpreter.php create mode 100755 src/Imap/Interpreter/OctetInterpreter.php create mode 100755 src/Imap/Interpreter/ResponseInterpreter.php create mode 100755 src/Imap/Interpreter/Rfc2822Interpreter.php create mode 100755 src/Imap/Interpreter/SearchInterpreter.php create mode 100755 src/Imap/Interpreter/StringIteratorInterpreter.php create mode 100755 src/Imap/Lexeme/Lexeme.php create mode 100755 src/Imap/Lexeme/LexemeIteratorInterface.php create mode 100755 src/Imap/Lexeme/LexemeList.php create mode 100755 src/Imap/Lexeme/LexemeType.php create mode 100755 src/Imap/Lexeme/Lexemizer.php create mode 100755 src/Imap/Lexeme/keywords.yaml create mode 100755 src/Imap/Manager/ManagerInterface.php create mode 100755 src/Imap/Manager/PhpImapExtensionManager.php create mode 100755 src/Imap/Manager/PimapManager.php create mode 100755 src/Imap/ManagerFactory.php create mode 100755 src/Imap/Pipeline/Pipe.php create mode 100755 src/Imap/Pipeline/PipeInterface.php create mode 100755 src/Imap/Pipeline/PipeLineAwareInterface.php create mode 100755 src/Imap/Pipeline/Pipeline.php create mode 100755 src/Imap/Pipeline/PipelineInterface.php create mode 100755 src/Imap/Pipeline/PipelineIteratorInterface.php create mode 100755 src/Imap/Response/Capability.php create mode 100755 src/Imap/Response/Mailbox.php create mode 100755 src/Imap/Response/MailboxList.php create mode 100755 src/Imap/Response/Message.php create mode 100755 src/Imap/Response/MessageAttachment.php create mode 100755 src/Imap/Response/MessageAttachmentInterface.php create mode 100755 src/Imap/Response/MessageAttachmentStructure.php create mode 100755 src/Imap/Response/MessageAttachmentStructureInterface.php create mode 100755 src/Imap/Response/MessageBody.php create mode 100755 src/Imap/Response/MessageBodyInterface.php create mode 100755 src/Imap/Response/MessageBodyStructure.php create mode 100755 src/Imap/Response/MessageBodyStructureInterface.php create mode 100755 src/Imap/Response/MessageFactory.php create mode 100755 src/Imap/Response/MessageFlags.php create mode 100755 src/Imap/Response/MessageFlagsInterface.php create mode 100755 src/Imap/Response/MessageHeader.php create mode 100755 src/Imap/Response/MessageHeaderInterface.php create mode 100755 src/Imap/Response/MessageInterface.php create mode 100755 src/Imap/Response/MessageList.php create mode 100755 src/Imap/Response/Response.php create mode 100755 src/Imap/Stream/CommandTransporterInterface.php create mode 100755 src/Imap/Stream/MessageTransporter.php create mode 100644 src/Imap/Stream/PhpImapExtensionMessageTransporter.php create mode 100755 src/Imap/Stream/PhpImpExtensionConnection.php create mode 100755 src/Imap/Token/Token.php create mode 100755 src/Imap/Token/TokenException.php create mode 100755 src/Imap/Token/TokenIteratorInterface.php create mode 100755 src/Imap/Token/TokenList.php create mode 100755 src/Imap/Token/TokenType.php create mode 100755 src/Imap/Token/Tokenizer.php create mode 100755 src/Iteration/StringIterator.php create mode 100755 src/Iteration/StringIteratorInterface.php create mode 100755 src/Pattern/ContainerAwareInterface.php create mode 100755 src/Pattern/Singleton.php create mode 100755 src/Stream/Connection.php create mode 100755 src/Stream/ConnectionException.php create mode 100755 src/Stream/MessageTransporter.php create mode 100755 src/Stream/MessageTransporterInterface.php create mode 100755 src/Stream/MockConnection.php create mode 100755 src/Stream/StreamConnectionInterface.php create mode 100755 src/Utility/Assert.php create mode 100755 src/Utility/PimapLogger.php create mode 100755 src/Utility/StringValue.php create mode 100755 tests/_data/.gitkeep create mode 100755 tests/_data/1534938081_php_imap_extension_command.serialise create mode 100755 tests/_data/1534938082_php_imap_extension_imap_fetch_overview.serialise create mode 100755 tests/_data/1534938083_php_imap_extension_imap_fetchstructure.serialise create mode 100755 tests/_data/1534938084_php_imap_extension_message.serialise create mode 100755 tests/_data/1534938085_php_imap_extension_command.serialise create mode 100755 tests/_data/1534938086_php_imap_extension_imap_fetch_overview.serialise create mode 100755 tests/_data/1534938087_php_imap_extension_imap_fetchstructure.serialise create mode 100755 tests/_data/1534938088_php_imap_extension_imap_body.serialise create mode 100755 tests/_data/1534938089_php_imap_extension_message.serialise create mode 100755 tests/_data/1534938090_php_imap_extension_command.serialise create mode 100755 tests/_data/1534938092_php_imap_extension_imap_fetch_overview.serialise create mode 100755 tests/_data/1534938093_php_imap_extension_imap_fetchstructure.serialise create mode 100755 tests/_data/1534938094_php_imap_extension_message.serialise create mode 100755 tests/_data/1534938095_php_imap_extension_command.serialise create mode 100755 tests/_data/1534938096_php_imap_extension_imap_fetch_overview.serialise create mode 100755 tests/_data/1534938097_php_imap_extension_imap_fetchstructure.serialise create mode 100755 tests/_data/1534938098_php_imap_extension_imap_fetchbody.serialise create mode 100755 tests/_data/1534938099_php_imap_extension_imap_fetchbody.serialise create mode 100755 tests/_data/1534938100_php_imap_extension_imap_fetchbody.serialise create mode 100755 tests/_data/1534938101_php_imap_extension_imap_fetchbody.serialise create mode 100755 tests/_data/1534938102_php_imap_extension_message.serialise create mode 100755 tests/_data/BoundaryContainsSeparators.txt create mode 100755 tests/_data/CAPABILITY.txt create mode 100755 tests/_data/FETCH_BODY.txt create mode 100755 tests/_data/FETCH_BODYSTRUCTURE.txt create mode 100755 tests/_data/FETCH_BODY_HEADER.txt create mode 100755 tests/_data/FETCH_BODY_HEADER_WITH_RESPONSE.txt create mode 100755 tests/_data/FETCH_EMAIL_EMBEDDED_IMAGE.txt create mode 100755 tests/_data/FETCH_FLAGS.txt create mode 100755 tests/_data/FETCH_FLAGS_BODY_HEADER_BODYSTRUCTURE_UTF8.txt create mode 100755 tests/_data/FETCH_RANGE.txt create mode 100755 tests/_data/FETCH_UID.txt create mode 100755 tests/_data/FETCH_UID_FLAGS_BODY_HEADER_BODYSTRUCTURE.txt create mode 100755 tests/_data/FETCH_UID_FLAGS_BODY_HEADER_BODYSTRUCTURE_BODY_TEXT.txt create mode 100755 tests/_data/FLAGS_UID_BODY_HEADER_BODYSTRUCTURE_BODY_TEXT.txt create mode 100755 tests/_data/LIST.txt create mode 100755 tests/_data/LineLengthDetectedIncorrectly.txt create mode 100644 tests/_data/PASSIVE_CAPABILITY.txt create mode 100755 tests/_data/PLAINTEXT_FETCH_1_20_FLAGS_UID_HEADER_BODYSTRUCTURE.txt create mode 100755 tests/_data/PLAIN_FETCH_UID_BODY_HEADER_BODYSTUCTURE_BODY.txt create mode 100755 tests/_data/QUOTES_ISSUE.txt create mode 100644 tests/_data/SEARCH_1_5.txt create mode 100755 tests/_data/SELECT_INBOX.txt create mode 100755 tests/_data/server_greeting.txt create mode 100755 tests/_output/.gitignore create mode 100755 tests/_support/AcceptanceTester.php create mode 100755 tests/_support/FunctionalTester.php create mode 100755 tests/_support/Helper/Acceptance.php create mode 100755 tests/_support/Helper/Functional.php create mode 100755 tests/_support/Helper/Unit.php create mode 100755 tests/_support/UnitTester.php create mode 100755 tests/_support/_generated/.gitignore create mode 100644 tests/acceptance.suite.yml create mode 100755 tests/acceptance/_bootstrap.php create mode 100755 tests/functional.suite.yml create mode 100755 tests/functional/_bootstrap.php create mode 100755 tests/mock/imap-ext.php create mode 100644 tests/mock/stream.php create mode 100755 tests/unit.suite.yml create mode 100755 tests/unit/CommonErrorsTest.php create mode 100755 tests/unit/SalesAgility/Imap/CommandBuilder/PhpImapExtensionCommandBuilderTest.php create mode 100755 tests/unit/SalesAgility/Imap/CommandBuilder/PimapCommandBuilderTest.php create mode 100755 tests/unit/SalesAgility/Imap/CommandBuilder/Validator/NoopCommandValidatorTest.php create mode 100755 tests/unit/SalesAgility/Imap/ImapExceptionTest.php create mode 100755 tests/unit/SalesAgility/Imap/ImapManagerFactoryTest.php create mode 100755 tests/unit/SalesAgility/Imap/Interpreter/CapabilityInterpreterTest.php create mode 100644 tests/unit/SalesAgility/Imap/Interpreter/LexemeInterpreterTest.php create mode 100755 tests/unit/SalesAgility/Imap/Interpreter/MailboxInterpreterTest.php create mode 100755 tests/unit/SalesAgility/Imap/Interpreter/MailboxListInterpreterTest.php create mode 100755 tests/unit/SalesAgility/Imap/Interpreter/MessageInterpreterTest.php create mode 100755 tests/unit/SalesAgility/Imap/Interpreter/SearchInterpreterTest.php create mode 100755 tests/unit/SalesAgility/Imap/Lexeme/LexemeListTest.php create mode 100755 tests/unit/SalesAgility/Imap/Lexeme/LexemeTest.php create mode 100755 tests/unit/SalesAgility/Imap/Lexeme/LexemeTypeTest.php create mode 100755 tests/unit/SalesAgility/Imap/Lexeme/LexemizerTest.php create mode 100755 tests/unit/SalesAgility/Imap/Manager/PhpImapExtensionManagerTest.php create mode 100755 tests/unit/SalesAgility/Imap/Manager/PimapManagerTest.php create mode 100755 tests/unit/SalesAgility/Imap/Pipeline/PipeTest.php create mode 100755 tests/unit/SalesAgility/Imap/Pipeline/PipelineTest.php create mode 100644 tests/unit/SalesAgility/Imap/Response/CapabilityTest.php create mode 100644 tests/unit/SalesAgility/Imap/Response/MailboxListTest.php create mode 100644 tests/unit/SalesAgility/Imap/Response/MailboxTest.php create mode 100755 tests/unit/SalesAgility/Imap/Response/MessageAttachmentStructureTest.php create mode 100755 tests/unit/SalesAgility/Imap/Response/MessageAttachmentTest.php create mode 100755 tests/unit/SalesAgility/Imap/Response/MessageBodyStructureTest.php create mode 100755 tests/unit/SalesAgility/Imap/Response/MessageBodyTest.php create mode 100755 tests/unit/SalesAgility/Imap/Response/MessageFlagsTest.php create mode 100755 tests/unit/SalesAgility/Imap/Response/MessageHeaderTest.php create mode 100755 tests/unit/SalesAgility/Imap/Response/MessageListTest.php create mode 100755 tests/unit/SalesAgility/Imap/Response/MessageTest.php create mode 100755 tests/unit/SalesAgility/Imap/Stream/MessageTransporterTest.php create mode 100755 tests/unit/SalesAgility/Imap/Stream/PhpImapExtensionConnectionTest.php create mode 100755 tests/unit/SalesAgility/Imap/Stream/PhpImapExtensionMessageTransporterTest.php create mode 100755 tests/unit/SalesAgility/Imap/Token/TokenListTest.php create mode 100755 tests/unit/SalesAgility/Imap/Token/TokenTest.php create mode 100755 tests/unit/SalesAgility/Imap/Token/TokenTypeTest.php create mode 100755 tests/unit/SalesAgility/Imap/Token/TokenizerTest.php create mode 100755 tests/unit/SalesAgility/Iteration/StringIteratorTest.php create mode 100755 tests/unit/SalesAgility/Iteration/StringValueTest.php create mode 100755 tests/unit/SalesAgility/Stream/ConnectionExceptionTest.php create mode 100755 tests/unit/SalesAgility/Stream/ConnectionTest.php create mode 100755 tests/unit/SalesAgility/Stream/MessageTransporterTest.php create mode 100755 tests/unit/SalesAgility/Stream/MockConnectionTest.php create mode 100755 tests/unit/SalesAgility/Utility/AssertTest.php create mode 100644 tests/unit/SalesAgility/Utility/PimapLoggerTest.php create mode 100755 tests/unit/_bootstrap.php diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100755 index 0000000..7573f7a --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,46 @@ +## How to contribute to Pimap + +### **Code of Conduct** + +This project and all community members are expected to uphold the [Pimap Code of Conduct.](CODE_OF_CONDUCT.md) + +#### **Bug Reporting** + +- **Please do not open a GitHub issue if the bug is a security vulnerability**, and instead email us at security@suitecrm.com. This will be delivered to the product team who handle security issues. Please don't disclose security bugs publicly until they have been handled by the security team. +- Your email will be acknowledged within 24 hours during the business week (Mon Fri), and you’ll receive a more detailed response to your email within 72 hours during the business week (Mon Fri) indicating the next steps in handling your report. +- **Ensure the bug was not already reported** - by searching on GitHub under [Issues.](https://github.com/salesagility/Pimap/issues) +- If you're unable to find an open issue that relates to your problem, [open a new one.](https://github.com/salesagility/Pimap/issues/new) Please be sure to follow the issue template as much as possible. +- **Ensure that the bug you are reporting is a core Pimap issue** - and not specific to your individual setup. + +#### **Did you fix a bug?** + +- To provide a code contribution for an issue you will need to set up your own fork of the Pimap repository, make your code changes, commit the changes and make a Pull Request to the appropriate branch on the Pimap repository. +- Determine which base branch your bug fix should use. Branches are named based on the next minor release version, eg v0.1.x, v0.2.x etc... +- When committing to your individual fix branch, please try and use the following as your commit message +```Fixed #1234 ```. E.g. ```Fixed #1436 inline attachments```. + +- If you are new to Writing Commit Messages in git follow the guide [here](https://chris.beams.io/posts/git-commit/) +- After you have made your commits and pushed them up to your forked repository you then create a [Pull Request](https://help.github.com/articles/about-pull-requests/) to be reviewed and merged into the Pimap repository. Make a new Pull Request for each issue you fix – do not combine multiple bugfixes into one Pull Request. + Ensure that in your Pull Request that the base fork is salesagility/Pimap and base branch is v0.1.x. and the head fork is your repository and the base branch is your unique bugfix branch e.g. fix_1234 + We will automatically reject any Pull Requests made to the wrong branch! +- If you have not signed our CLA [Contributor License Agreement](https://www.clahub.com/agreements/salesagility/Pimap) then your pull request will fail a check and we will be unable to merge it into the project. You will only be required to sign this once. +- When a new Pull Request is opened, Travis CI will test the merging of the origin and upstream branch and update the Pull Request. If this check fails you can review the test results and resolve accordingly. To test prior to making a Pull Request install Codeception via composer into your development environment then cd into the tests directory and run: ```$./vendor/bin/codecept run unit -vvv -d``` +- Ensure that you follow the pull request [template](https://github.com/salesagility/Pimap/blob/master/.github/PULL_REQUEST_TEMPLATE.md) as much as possible. +- When committing to your individual feature branch, please try and use the following as your commit message +```Fixed #1234 ```. E.g. ```Fixed #1436 Reports with nested Parentheses are removing parameters```. + + +#### **Did you create a new feature or enhancement?** + +- Changes that can be considered a new feature or enhancement should be made to the **v0.1.x**. +- To contribute a feature to Pimap, similar to providing a Bug Fix, you must create a forked repository of SuiteCRM and set up your git and development environment. + Once done, create a new branch from **v0.1.x** - and name it relevant to the feature's purpose e.g feature_sort_extension. + Make sure your commit messages are relevant and descriptive. When ready to submit for review make a Pull Request detailing your feature's functionality. + Ensure that in your Pull Request that the base fork is **salesagility/Pimap** - and base branch is **v0.1.x** - and the head fork is your repository and the base branch is your feature branch. + Add any new automated tests to the new feature branch if required e.g new modules or classes. +- We will review the code and provide feedback within the Pull Request and issues relating to your feature. If the feature is to be included in the core product we will request for the forked repo to have an Issues tab so we can raise any bugs from our testing. This will also allow you to fix those issues using the below commit message format similar to how to submit bug fixes to the v0.1.x branch. + ```$ git commit m "Feature #1436 Sort Extension"```. You can add an Issues tab to your forked repository via the 'Settings' tab. + +Thanks! + +SalesAgility Team diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100755 index 0000000..56b1516 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,36 @@ + + + + +#### Issue + + +#### Expected Behavior + + +#### Actual Behavior + + + +#### Possible Fix + + +#### Steps to Reproduce + + +1. +2. +3. +4. + +#### Context + + + +#### Your Environment + +* Pimap Version used: +* Browser name and version (e.g. Chrome Version 51.0.2704.63 (64-bit)): +* Environment name and version (e.g. MySQL, PHP 7): +* Operating System and version (e.g Ubuntu 16.04): + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100755 index 0000000..a12c019 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,27 @@ + + +## Description + + + + +## Motivation and Context + + +## How To Test This + + +## Types of changes + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) + +### Final checklist + + +- [ ] My change requires a change to the documentation. + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..f19520b --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.idea/ +*.lock +*.ignore +vendor/ +nbproject/ +.buildpath +.project +.settings +.ignore/ +tags +static/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100755 index 0000000..2d049c4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +language: php +php: + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + +matrix: + fast_finish: true +jobs: + include: + - stage: codecoverage + if: type IN (pull_request) OR branch in (master) + php: 7.0 + script: ./vendor/bin/codecept run unit -f --steps -v -d --coverage-xml;bash < (curl -s https://codecov.io/bash) -f tests/_output/coverage.xml; +sudo: false +dist: trusty + +install: + - composer self-update && composer --version + +script: + # Run the unit tests + - ./vendor/bin/codecept run -f unit + +branches: + only: + - master \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100755 index 0000000..a061ca0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,35 @@ +# Code of Conduct # + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +Examples of behaviour that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community in a professional manner +* Showing empathy towards other community members + +Examples of unacceptable behaviour by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +The Pimap project maintainers are responsible for clarifying the standards of acceptable behaviour and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behaviour. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. + +The Pimap project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviours that they deem inappropriate, threatening, offensive, or harmful. + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, within project forums, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +Instances of abusive, harassing, or otherwise unacceptable behaviour may be reported by contacting the project team at [community@suitecrm.com][community_email]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[community_email]: mailto:community@suitecrm.com +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100755 index 0000000..0eb31d5 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,17 @@ +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d1ff33c --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# PHP Internet Message Access Protocol (Pimap) +A PHP Library for communication with IMAP Servers. + +[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) + +### Rationale +When communication with an IMAP email server involves a high amount of latency, +it helps to be able to hand craft commands, so that we can reduce the quantity of commands being sent to the server. The PHP IMAP extension +does not offer such control of over the transport layer of the IMAP protocol. This library replaces the PHP IMAP extension +and provides full control over the entire process. + +### Features +- Fewer combined calls +- Decrease latency +- Command builder +- IMAP Connection Manager +- IMAP Response Interpreters +- Pipeline for caching commands and responses +- PSR 3 logger aware +- Mock objects for automated testing + +### Compliant +- [RFC 3501](https://tools.ietf.org/html/rfc3501) IMAP version 4rev1 +- [RFC 2822](https://tools.ietf.org/html/rfc2822) Internet Message Format +- [RFC 2045](https://tools.ietf.org/html/rfc2045) Multipurpose Internet Mail Extensions + + +### Dependencies +Dependencies are managed by [composer](https://getcomposer.org/). Please see the composer.json file for a complete list of dependencies. + +### Contributing to the Pimap project +Please read and sign the following [contributor agreement][cont_agrmt] + +[cont_agrmt]: https://www.clahub.com/agreements/salesagility/Pimap + +The Contributor Agreement only needs to be signed once for all pull requests and contributions. + +Once signed and confirmed, any pull requests will be considered for inclusion in the Pimap project. + +#### Security + +We take security seriously here at SalesAgility so if you have discovered a security risk report it by +emailing security@suitecrm.com. This will be delivered to the product team who handle security issues. +Please don't disclose security bugs publicly until they have been handled by the security team. + +Your email will be acknowledged within 24 hours during the business week (Mon - Fri), and you’ll receive a more +detailed response to your email within 72 hours during the business week (Mon - Fri) indicating the next steps in +handling your report. + +### Add to your project (Composer) + +```bash +composer require "SalesAgility/pimap" +``` + +### Getting Help +All the documentation is situated in the docs directory. The documentation in this project is written in mark down and generated using [Daux.io](https://daux.io/). + +#### Generate Documentation +To build the documentation + +```bash +cd /path/to/pimap/ +./vendor/bin/daux +``` + +open the generated _static/index.html_ file. + +#### Host Documentation +When updating the documentation, it helps to be able to see a live copy of your changes. Daux.io provides a built in web server. + +```bash +cd /path/to/pimap/ +./vendor/bin/daux serve +``` + +this command will output something similar to: +```bash +Daux development server started on http://localhost:8085/ +``` + +in this case you can access **http://localhost:8085/** in your web browser. \ No newline at end of file diff --git a/RoboFile.php b/RoboFile.php new file mode 100755 index 0000000..4f7b69f --- /dev/null +++ b/RoboFile.php @@ -0,0 +1,337 @@ +. + *********************************************************************************/ + +require_once __DIR__ . '/vendor/autoload.php'; + +use Monolog\Logger; +use Monolog\Handler\StreamHandler; +use \SalesAgility\Imap\ManagerFactory; +use \SalesAgility\Stream\Connection; + +class RoboFile extends \Robo\Tasks +{ + /** + * Connect to a real imap server and view messages + * @param array $opt + * @throws ErrorException + * @throws \SalesAgility\Imap\Token\TokenException + */ + public function runEmailClient($opt = [ + 'address' => '', + 'port' => '', + 'tls' => '', + 'user' => '', + 'password' => '' + ]) + { + $log = new Logger('name'); + $log->pushHandler(new StreamHandler(__DIR__ . '/tests/_output/functional_tests.log', Logger::DEBUG)); + $log->info('Robo task: runTests'); + + $opt['address'] = $this->askIfEmpty($opt['address'], 'address', '127.0.0.1'); + $opt['port'] = $this->askIfEmpty($opt['port'], 'port', '143'); + $opt['tls'] = $this->confirmIfEmpty($opt['tls'], 'tls'); + $opt['user'] = $this->askIfEmpty($opt['user'], 'user', 'user'); + $opt['password'] = $this->askIfEmpty($opt['password'], 'password', ''); + + + $log->info(var_export($opt, true)); + + + $log = new Logger('name'); + $log->pushHandler(new StreamHandler(__DIR__ . '/tests/_output/functional_tests.log', Logger::DEBUG)); + $log->info('loading global bootstrap file'); + $managerFactory = ManagerFactory::instance(); + $manager = $managerFactory->PimapManager(); + $manager->setLogger($log); + $manager->transporter()->connection()->setLogger($log); + $address = $opt['address']; + $port = $opt['port']; + $tls = $opt['tls']; + $user = $opt['user']; + $password = $opt['password']; + + + $this->say('Connecting to '. "tcp://$address:$port"); + + $manager->transporter()->connection()->setConnectionString("tcp://$address:$port"); + + if($tls == 'false') { + $manager->transporter()->connection()->disableEncryption(); + } else { + $manager->transporter()->connection()->enableEncryption(); + } + + + $manager->transporter()->connection()->connect(); + $serverGreeting = $manager->transporter()->connection()->readMessage(); + $this->writeln($serverGreeting); + $this->writeln('Sending login details'); + $loginResponse = $manager->run($manager->command()->login()->user($user)->password($password)); + $this->writeln($loginResponse->message()->toString()); + $this->emailClient($manager); + + } + + /** + * Connect to a real imap server and view messages + * @param array $opt + * @throws ErrorException + * @throws \SalesAgility\Imap\Token\TokenException + * @throws \SalesAgility\Imap\ImapException + */ + public function runPhpImapExtensionEmailClient($opt = [ + 'address' => '', + 'port' => '', + 'tls' => '', + 'user' => '', + 'password' => '' + ]) + { + $log = new Logger('name'); + $log->pushHandler(new StreamHandler(__DIR__ . '/tests/_output/functional_tests.log', Logger::DEBUG)); + $log->info('Robo task: runTests'); + + $opt['address'] = $this->askIfEmpty($opt['address'], 'address', '127.0.0.1'); + $opt['port'] = $this->askIfEmpty($opt['port'], 'port', '143'); + $opt['tls'] = $this->confirmIfEmpty($opt['tls'], 'tls'); + $opt['user'] = $this->askIfEmpty($opt['user'], 'user', 'user'); + $opt['password'] = $this->askIfEmpty($opt['password'], 'password', ''); + + $log->info(var_export($opt, true)); + + $log = new Logger('name'); + $log->pushHandler(new StreamHandler(__DIR__ . '/tests/_output/functional_tests.log', Logger::DEBUG)); + $log->info('loading global bootstrap file'); + $managerFactory = ManagerFactory::instance(); + $manager = $managerFactory->PhpImapExtensionManager(); + $manager->setLogger($log); + $address = $opt['address']; + $port = $opt['port']; + $tls = $opt['tls']; + $user = $opt['user']; + $password = $opt['password']; + + + $this->say('Connecting to '. "tcp://$address:$port"); + + $manager->transporter()->connection()->setConnectionString("tcp://$address:$port"); + + if($tls == 'false') { + $manager->transporter()->connection()->disableEncryption(); + } else { + $manager->transporter()->connection()->enableEncryption(); + } + + + $manager->transporter()->connection()->connect(); + $this->writeln('Sending login details'); + $loginResponse = $manager->run($manager->command()->login()->user($user)->password($password)); + $this->writeln($loginResponse->message()->toString()); + + $this->emailClient($manager); + } + + + /** + * @param \SalesAgility\Imap\Manager\PimapManager | \SalesAgility\Imap\Manager\PhpImapExtensionManager $manager + * @throws ErrorException + * @throws \SalesAgility\Imap\ImapException + * @throws \SalesAgility\Imap\Token\TokenException + */ + private function emailClient( $manager) + { + $mailbox = 'INBOX'; + $this->writeln('Selecting Mailbox: '. $mailbox); + /** @var \SalesAgility\Imap\Response\Mailbox $selectMailBox */ + $selectMailBox = $manager->run($manager->command()->select($mailbox)); + + + $this->writeln('Fetching 20 Emails:'); + /** @var \SalesAgility\Imap\Response\MessageList $messages */ + $messages = $manager->run($manager->command()->fetchRange(1, 20)->flags()->uids()->header()->build()); + + while(1) { + // Display List View + foreach ($messages as $message) { + $line = ''; + $line .= 'Message: '; + $line .= $message->number(); + $line .= "\t"; + $line .= 'From: '; + $line .= $message->header()->from()[0] ; + $line .= "\t"; + $line .= 'Subject: '; + $line .= $message->header()->subject(); + $line .= "\t\t"; + $line .= 'Attachments: '; + $line .= $message->body()->structure()->attachmentsExists() ? '[A]' : '[X]'; + $line .= "\t\t"; + $line .= 'Date: '; + $line .= $message->header()->date()->format(DateTime::RFC2822); + $this->writeln($line); + } + $messageInput = $this->ask('Select message number (just press enter to exit):'); + + if(empty(trim($messageInput)) || trim($messageInput) == 0) { + break; + } else { + // Display Detail View + /** @var \SalesAgility\Imap\Response\MessageList $messageSelected */ + $messageSelected = $manager->run($manager->command()->fetch($messageInput)->flags()->uids()->header()->body()->build()); + $line = ''; + $line .= 'Message Number: ' . $messageSelected->offsetGet(0)->number() . PHP_EOL; + $line .= 'From: ' . $messageSelected->offsetGet(0)->header()->from()[0] . PHP_EOL; + $line .= 'Subject: ' . $messageSelected->offsetGet(0)->header()->subject() . PHP_EOL; + $line .= 'Message: ' . PHP_EOL . $messageSelected->offsetGet(0)->body()->text() . PHP_EOL; + $line .= 'Attachments: '; + $line .= $messageSelected->offsetGet(0)->body()->structure()->attachmentsExists() ? '[A]' : '[X]'; + $line .= PHP_EOL; + if($messageSelected->offsetGet(0)->body()->structure()->attachmentsExists()) { + $line .= PHP_EOL; + $attachments = $messageSelected->offsetGet(0)->body()->attachments(); + foreach ($attachments as $attachment) { + $line .= $attachment->structure()->name(); + $line .= ' ('. $attachment->structure()->type().') '; + $line .= $attachment->structure()->size(); + $line .= PHP_EOL; + } + } + + $line .= 'Date: ' . $messageSelected->offsetGet(0)->header()->date()->format(DateTime::RFC2822) . PHP_EOL; + $this->writeln($line); + + $messageInput = $this->ask('Press enter to see list view:'); + } + }; + + $this->writeln('Logging out'); + $logoutResponse = $manager->run($manager->command()->logout()); + $this->writeln($logoutResponse->message()->toString()); + $manager->transporter()->connection()->disconnect(); + + } + + /** + * Print out the command and responses for use of test data + * @param array $opt + * @throws ErrorException + * @throws \SalesAgility\Imap\Token\TokenException + */ + public function generateTestData($opt = [ + 'address' => '', + 'port' => '', + 'tls' => '', + 'user' => '', + 'password' => '' + ]) + { + $log = new Logger('name'); + $log->pushHandler(new StreamHandler(__DIR__ . '/tests/_output/functional_tests.log', Logger::DEBUG)); + $log->info('Robo task: runTests'); + + $opt['address'] = $this->askIfEmpty($opt['address'], 'address', '127.0.0.1'); + $opt['port'] = $this->askIfEmpty($opt['port'], 'port', '143'); + $opt['tls'] = $this->confirmIfEmpty($opt['tls'], 'tls'); + $opt['user'] = $this->askIfEmpty($opt['user'], 'user', 'user'); + $opt['password'] = $this->askIfEmpty($opt['password'], 'password', ''); + + $managerFactory = ManagerFactory::instance(); + $manager = $managerFactory->PimapManager(); + $manager->transporter()->connection()->setLogger($log); + $address = $opt['address']; + $port = $opt['port']; + $tls = $opt['tls']; + $user = $opt['user']; + $password = $opt['password']; + + + $this->say('Connecting to '. "tcp://$address:$port"); + + $manager->transporter()->connection()->setConnectionString("tcp://$address:$port"); + + if($tls == 'false') { + $manager->transporter()->connection()->disableEncryption(); + } else { + $manager->transporter()->connection()->enableEncryption(); + } + + + $manager->transporter()->connection()->connect(); + $serverGreeting = $manager->transporter()->connection()->readMessage(); + $this->writeln($serverGreeting); + $this->writeln('Sending login details'); + $loginResponse = $manager->run($manager->command()->login()->user($user)->password($password)); + $this->writeln($loginResponse->message()); + $this->writeln('Selecting Mailbox: INBOX'); + $selectMailBox = $manager->run($manager->command()->select('INBOX')); + $this->writeln($selectMailBox->message()); + + + $this->writeln('Fetching 20 Emails:'); + /** @var \SalesAgility\Imap\Response\MessageList $messages */ + $messages = $manager->run($manager->command()->raw('FETCH 2672:2682 (UID FLAGS BODY[HEADER] BODYSTRUCTURE BODY[TEXT])')); + $response = $manager->pipeline()->getLastPipe()->getResponse(); + $this->writeln('Output response: '.__DIR__.'/tests/_output/testdata.log'); + file_put_contents(__DIR__ . '/tests/_output/testdata.log', $response); + + + + $logoutResponse = $manager->run($manager->command()->logout()); + $this->writeln($logoutResponse->message()); + $manager->transporter()->connection()->disconnect(); + + } + + + public function addCopyright($opt = ['directory' => '']) + { + $copyright = file_get_contents(__DIR__.'/COPYRIGHT'); + $opt['directory'] = $this->askIfEmpty($opt['directory'], 'Directory', '.'); + $directory = new RecursiveDirectoryIterator(realpath($opt['directory'])); + $iterator = new RecursiveIteratorIterator($directory); + $regex = new RegexIterator($iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH); + foreach ($regex as $files) { + foreach ($files as $file) { + $contents = file_get_contents($file); + $replaced = preg_replace('/^<\?php/i', "writeln('Replaced '. $file); + } else { + $this->writeln('Error writing to '. $file); + } + } + } + } + + private function askIfEmpty($option, $text, $default) { + if(empty($option)) { + return $this->askDefault($text, $default); + } + return $option; + } + + private function confirmIfEmpty($option, $text) { + if(empty($option)) { + return (string)$this->confirm($text); + } + return $option; + } +} \ No newline at end of file diff --git a/codeception.yml b/codeception.yml new file mode 100755 index 0000000..29d8a6d --- /dev/null +++ b/codeception.yml @@ -0,0 +1,19 @@ +paths: + tests: tests + output: tests/_output + data: tests/_data + support: tests/_support + envs: tests/_envs +actor_suffix: Tester +settings: + bootstrap: _bootstrap.php +extensions: + enabled: + - Codeception\Extension\RunFailed +coverage: + enabled: true + remote: false + low_limit: 60 + high_limit: 90 + include: + - src/* \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..df7041f --- /dev/null +++ b/composer.json @@ -0,0 +1,42 @@ +{ + "name": "SalesAgility/Pimap", + "description": "A PHP Library for communicating with IMAP Servers using stream socket connection", + "type": "project", + "license": "GPL-3.0-or-later", + "authors": [], + "version": "v0.1.0", + "minimum-stability": "stable", + "config": { + "optimize-autoloader": true, + "vendor-dir": "vendor", + "platform": { + "php": "5.5.9" + } + }, + "require": { + "php": ">=5.5.9", + "ext-imap": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-json": "*", + "ext-fileinfo": "*", + "psr/log": "^1.0.0", + "psr/container": "^1.0.0" + }, + "require-dev": { + "codeception/codeception": "^2.4.1", + "consolidation/robo": "^1.0.0", + "daux/daux.io": "^0.3.2" + }, + "scripts": {}, + "prefer-stable": true, + "autoload": { + "psr-4": { + "SalesAgility\\": [ + "src/", + "tests/unit/SalesAgility" + ] + } + }, + "extra": {} +} diff --git a/docs/_index.md b/docs/_index.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/_index.md @@ -0,0 +1 @@ + diff --git a/docs/config.json b/docs/config.json new file mode 100644 index 0000000..c0aafa2 --- /dev/null +++ b/docs/config.json @@ -0,0 +1,26 @@ +{ + "title": "SalesAgility Pimap", + "tagline": "PHP Internet Message Access Protocol (PIMAP)", + "description": "PHP Internet Message Access Protocol (PIMAP)", + "format": "html", + "html": { + "search": false, + "theme": "daux-navy", + "auto_toc": false, + "inherit_index": true, + "repo": "SalesAgility/Pimap", + "breadcrumbs": true, + "breadcrumb_separator" : " > ", + "float": false, + "date_modified": false, + "jump_buttons": true + }, + "ignore": { + "files": ["ignore.md"], + "folders": ["ignore/"] + }, + "timezone": "Europe/London", + "languages": { + "en": "View Documentation (English)" + } +} \ No newline at end of file diff --git a/docs/en/00_Introduction.md b/docs/en/00_Introduction.md new file mode 100644 index 0000000..b7af90f --- /dev/null +++ b/docs/en/00_Introduction.md @@ -0,0 +1,21 @@ +### Rationale +When communication with an IMAP email server involves a high amount of latency, you will want to reduce the commands being sent to the server. The php imap extension does not offer such control of over the transport layer of the IMAP protocol. This library replaces the php imap extension +and provides full control over the entire process. + +### Features +- Fewer combined calls +- Decrease latency +- Command builder +- IMAP Manager +- IMAP Response Interpreters +- Pipeline for caching commands and responses +- PSR 3 logger aware +- Mock objects for automated testing + +### Dependencies +Dependencies are manages by [composer](https://getcomposer.org/). Please see the composer.json file for a complete list of dependencies. + +### Compliant +- [RFC 3501](https://tools.ietf.org/html/rfc3501) IMAP version 4rev1 +- [RFC 2822](https://tools.ietf.org/html/rfc2822) Internet Message Format +- [RFC 2045](https://tools.ietf.org/html/rfc2045) Multipurpose Internet Mail Extensions \ No newline at end of file diff --git a/docs/en/01_Getting_Started.md b/docs/en/01_Getting_Started.md new file mode 100644 index 0000000..eb11d2d --- /dev/null +++ b/docs/en/01_Getting_Started.md @@ -0,0 +1,139 @@ +### Installation +It is recommended that you use [composer](https://getcomposer.org/) to manage the library. + +```php +composer require SalesAgility/pimap +``` + +### Set Up Connection Manager And Transporter +Before we can communicate with an IMAP server, we need to set up a [manager](02_Managers.md) in order to create a connection to the IMAP server. + +```php +try { + $manager = ImapManagerFactory::instance()->PimapManager(); + $transporter = $manager->transporter(); + $connection = $transporter->connection(); + $connection = $transporter->transporter()->connection()->enableEncryption(); + $connection->setConnectionString('tcp://imap.emailservice.com:993/'); +} catch (Exception $e) { + // report error + die($e->getMessage()); +} +``` + +> **Note:** the _setConnectionString_ format is _tcp:/hostname:portnumber/_ + +### Send Login Command +Once we have established a connection with the IMAP server, we need to send the login credentials to authenticate with the IMAP server. + +```php +$user = 'username@emailservice.com'; +$password = 'secret'; + +$loginCommand = $manager->command()->login()->user($user)->password($pass)->build(); +$response = $manager->run($loginCommand); +``` + +To create a command, use the _$manager->command()_ to get a new instance of a [command builder](03_Commands.md). + +> **Note:** you must call the _->build()_ method at the end of command, before passing it to the _$manager->run($command)_ + +### Select Mailbox Command +In order to get access the email messages, we need to tell the IMAP server which mailbox our messages belong to. +```php +$selectCommand = $manager->command()->select('INBOX')->build(); +$mailbox = $manager->run($selectCommand); +``` + +This returns a [mailbox](04_Responses.md) response. You can use the response to find out the information about the mailbox + +```php +echo "Total messages:". $mailbox->exists() .PHP_EOL; +echo "Recent messages:". $mailbox->recent() .PHP_EOL; +``` + + +### Fetch Multiple Messages Command +Now that we have selected the mailbox, we can begin fetch the messages inside the mailbox. Typically, you will want to display a list of messages to the user. Lets get the email headers. + +```php +$fetchCommand = $manager->command() + ->fetchRange(1, 5) + ->uids() + ->flags() + ->header() + ->build(); + +$messages = $manager->run($fetchCommand); + +foreach ($messages as $message) { + // Handle header + echo "UID:". $message->uid() ." "; + echo "Number:". $message->number() ." "; + echo "Seen:". (($message->flags()->isSeen()) ? "Y":"N") ." "; + echo "Date:". $message->header()->date()->format("r") ." "; + echo "To:". $message->header()->to() ." "; + echo "From:". $message->header()->from() ." "; + echo "Subject:". $message->header()->subject() ." "; + echo "Attachments:". (($message->body->structure()->hasAttachments()) ? "Y":"N") ." "; + echo PHP_EOL; +} +``` + +> **Tip:** The _->header()_ method also includes the body structure of a message. + +### Fetch Single Message Command +Typically the user will want to view a single email at a time. We already have the headers, all we need is the body of the message. + +```php +$fetchCommand = $manager->command() + ->fetch(1) + ->body() + ->build(); + +$messages = $manager->run($fetchCommand); + +foreach($messages as $message) { + // Check to see body is available + if( $message->body()->structure()->plainTextBodyExists() + || $message->body()->structure()->htmlBodyExists()) { + + // Get Attachments + foreach($message->body->attachments as $attachment) { + if($attachment->isInline()) { + // handle embedded content + $embeddedContent = 'data:'.trim($attachment->structure()->type()).';base64,'.base64_encode($attachment->content()); + $cid = 'cid:'.$attachment->contentId(); + $message->body()->offsetSet('html' str_replace($cid, $encodedContent, $message->body()->html())); + } + + // Get file properties + $name = $attachment->structure()->name(); + $type = $attachment->structure()->type(); + $size = $attachment->structure()->size(); + + // Get the file contents + if ($attachment->hasContent()) { + $content = $attachment->content(); + } + + // Handle attachment + } + + // Get the body or the content of the message + echo "html:" . $message->body()->html() . PHP_EOL; + } +} +``` + +### Disconnect from the server +Now that we have what we need, to disconnect we must send the log out command before we disconnect. + +```php +if ($manager->transporter()->connection()->isConnected()) { + $logoutResponse = $manager->run($manager->command()->logout()); + $manager->transporter()->connection()->disconnect(); +} +``` + +> **Note:** you must use the disconnect method after issuing a LOGOUT command diff --git a/docs/en/02_Managers.md b/docs/en/02_Managers.md new file mode 100644 index 0000000..b02928c --- /dev/null +++ b/docs/en/02_Managers.md @@ -0,0 +1,42 @@ +Pimap can use different implementations of the IMAP protocol. An adapter (called a manager) is provided for each implementation. + +**ManagerInterface Methods:** +- **setTransporter(CommandTransporterInterface $connection)** - set the transporter. +- **transporter()** - get the transporter. +- **command()** - get an instance of the corresponding command builder. +- **run(CommandBuildArgumentsInterface $command)** - run command and get response. + +### Pimap Manager +The Pimap Manager is a [stream socket client](http://php.net/manual/en/function.stream-socket-client.php) which provides you the ability to control the transport layer of the IMAP protocol. The Pimap manager lets for dig into the inner workings of the IMAP protocol. So that you can choose exactly what is communicated with the IMAP server. You can use this manager to help speed up your IMAP client. + +#### Example +```php +try { + $manager = ImapManagerFactory::instance()->PimapManager(); + $transporter = $manager->transporter(); + $connection = $transporter->connection(); + $connection = $transporter->transporter()->connection()->enableEncryption(); + $connection->setConnectionString('tcp://imap.emailservice.com:993/'); +} catch (Exception $e) { + // report error + die($e->getMessage()); +} +``` + +### Php Imap Extension Manager +The Php Imap Extension Manager provides an adapter to php imap extension that comes with PHP. This manager was added to provide backwards compatibility with older email servers. You can use it with the pipeline for caching purposes, however, this manager does not give you +any control over the commands sent ot the IMAP server. + +#### Example +```php +try { + $manager = ImapManagerFactory::instance()->PhpImapExtensionManager(); + $transporter = $manager->transporter(); + $connection = $transporter->connection(); + $connection = $transporter->transporter()->connection()->enableEncryption(); + $connection->setConnectionString('tcp://imap.emailservice.com:993/'); +} catch (Exception $e) { + // report error + die($e->getMessage()); +} +``` \ No newline at end of file diff --git a/docs/en/03_Commands.md b/docs/en/03_Commands.md new file mode 100644 index 0000000..50ee56a --- /dev/null +++ b/docs/en/03_Commands.md @@ -0,0 +1,476 @@ + +To create a command, use the _$manager->command()_ to get a new instance of a command builder. + +```php +$command = $manager->command()->command()->commandArgument()->build()); +``` +The command build will provide you with a set of methods which you can chain together to build the command. Use the _->build()_ method to finalise the command. + +If you have an Integrated Development Environment (IDE). You can use your IDE with auto-complete to assist you to make the right choices. + +> **Note:** you must call the _->build()_ method at the end of command, before passing it to the _$manager->run($command)_ method + +**Table Of Contents** +[TOC] + +### Verify Command Support + +Pimap offers multiple managers, which may not support some commands. There is a command builder for each manager. A command builder +implements a set of interfaces which indicates which commands are supported. + +```php +use SalesAgility\Imap\CommandBuilder\Commands; +if($manager->command() instanceof Commands\StartTlsCommandInterface) { + // Command is supported + $manager->run($manager->command()->startTLS()->build()); +} +``` + +### Raw Commands +When you need to create a custom command to support an IMAP extension you can use the _->raw()_ method. + + +```php + +$response = $manager->command()->raw('THREAD ORDEREDSUBJECT UTF-8 SINCE 5-MAR-2000')->build(); +``` + +The manager will return a [Response](04_Responses.md) from raw command . To start interpreting the response you can use the _->include()_ +method to get the untagged responses which was received from the server. + +```php +$responses = $response->included(); +$interpreter = new CustomInterpreter() +$response = $interpreter->parse($responses); +``` + +> **Note:** The manager will not attempt to interpret the response of a raw command + +### IMAP version 4rev1 Commands +Pimap current has support for [RFC 3501](https://tools.ietf.org/html/rfc3501) IMAP version 4rev1 commands. + +#### LOGIN +Once a connection established, you will need send the login credentials to authenticate with the IMAP server. + +**Usage**: + +```php +$user = 'username@emailservice.com'; +$password = 'secret'; + +$loginCommand = $manager->command()->login()->user($user)->password($pass)->build()); +$response = $manager->run($loginCommand); +``` + +**Returns**: [Response](04_Responses.md) + +#### CAPABILITY +The CAPABILITY command requests a listing of capabilities that the server supports. + +**Usage**: + +```php +$response = $manager->command->capability()->build() +``` + +**Returns**: [Response](04_Responses.md) + +> **Note:** capability may be detect passively in the Server Greeting, and Responses for LOGIN, EXAMINE and SELECT commands +> **Note:** Php Imap Extension Manager does not support the capability command + +#### LIST +The LIST command returns a subset of names from the complete set of all names available to the client. + +**Usage**: + +```php +$mailboxList = $manager->command->listMailbox("", "*")->build()); +``` + +**Returns**: MailboxList + +#### SELECT +The SELECT command selects a mailbox so that messages in the mailbox can be accessed. + +**Usage**: + +```php +$mailbox = $manager->command->select("INBOX")->build()); +``` + +**Returns**: [Mailbox](04_Responses.md) + +#### EXAMINE +The EXAMINE command is identical to SELECT and returns the same output. It is used to get the most recent information +about the mailbox. + +**Usage**: + +```php +$mailbox = $manager->command->examine("INBOX")->build()); +``` + +**Returns**: [Mailbox](04_Responses.md) + +#### UID +The UID command takes as its arguments a COPY, FETCH, or STORE command with arguments +appropriate for the associated command. However, the numbers in +the sequence set argument are unique identifiers instead of +message sequence numbers. Sequence set ranges are permitted, but +there is no guarantee that unique identifiers will be contiguous. + + +**Usage**: + +```php +$messageList = $manager->command->uid()->fetch(1)->flags()->build()); +``` + +**Returns**: The object for the command as its arguments eg [MessageList](04_Responses.md) / [Response](04_Responses.md) + + +> **Note:** some imap servers do not support UIDs + +#### FETCH + +The FETCH command retrieves data associated with a message in the mailbox. + +**Usage**: + +```php +$fetchCommand = $manager->command() + ->fetchRange(1, 5) + ->header() + ->flags() + ->build(); + +$messageList = $manager->run($fetchCommand); +``` + +**Returns**: [MessageList](04_Responses.md) + +**Methods**: +- **fetch($message)** - fetch a single message +- **fetchRange($messageFrom, $messageTo)** - fetch messages from a contiguous range +- **fetchSequence(MailboxList)** - fetch messages from a non-contiguous range. eg the output from SEARCH command. +- **header()** - include header and body structure of a message. +- **body()** - include the text and attachments of a message. Body must be run separately from the header. +- **uids()** - include the uid of a message +- **flags()** - include the flags of a message. eg seen, answered + +#### SEARCH +The SEARCH command searches the mailbox for messages that match the given searching criteria. + +**Usage**: + +```php +$searchCommand = $manager->command() + ->search() + ->searchUnseen() + ->searchOr() + ->searchRecent() + ->build(); + +$messageList = $manager->run($searchCommand); +``` + +**Returns**: [MessageList](04_Responses.md) + +> **Note:** The Messages return only contain uids / message numbers + +> **Tip:** Use the _$manager->command()->fetchRange($messageList)_ to fetch message(s) + +**Methods**: +- **withRange($messageFrom, $messageTo)** - search with in a contiguous range of messages +- **withSequence([MessageList](04_Responses.md) $messages)** - search with a non-contiguous range of messages +- **searchAll()** - All messages in the mailbox +- **searchAnswered()** - Messages with the \Answered flag set +- **searchBcc($string)** - Messages that contain the specified string in the envelope structure's BCC field +- **searchBefore(\DateTime $dateTime)** - Messages whose internal date (disregarding time and timezone) is earlier than the specified date. +- **searchBody($string)** - Messages that contain the specified string in the body of the message +- **searchCc($string)** - Messages that contain the specified string in the envelope structure's CC field +- **searchDeleted()** - Messages with the \Deleted flag set +- **searchDraft()** - Messages with the \Draft flag set +- **searchFlagged()** - Messages with the \Flagged flag set +- **searchFrom($string)** - Messages that contain the specified string in the envelope structure's FROM field +- **searchHeader($name, $string)** - Messages that have a header with the specified field-name (as defined in [RFC-2822](https://tools.ietf.org/html/rfc2822)) and that contains the specified string in the text of the header (what comes after the colon) +- **searchKeyword($flag)** - Messages with the specified keyword flag set +- **searchLarger($n)** - Messages with an [RFC-2822](https://tools.ietf.org/html/rfc2822) size larger than the specified number of octets +- **searchNew()** - Messages that have the \Recent flag set but not the \Seen flag +- **searchNot()** - Messages that do not match the specified search key +- **searchOld()** - Messages that do not have the \Recent flag set +- **searchOn(\DateTime $date)** - Messages whose internal date (disregarding time and timezone) is within the specified date +- **searchOr()** - Messages that match either search key before and after +- **searchRecent()** - Messages that have the \Recent flag set +- **searchSeen()** - Messages that have the \Seen flag set +- **searchSentBefore(\DateTime $date)** - Messages whose [RFC-2822](https://tools.ietf.org/html/rfc2822) Date: header (disregarding time and timezone) is earlier than the specified date +- **searchSentOn(\DateTime $date)** - Messages whose [RFC-2822](https://tools.ietf.org/html/rfc2822) Date: header (disregarding time and timezone) is within the specified date. +- **searchSentSince(\DateTime $date)** - Messages whose [RFC-2822](https://tools.ietf.org/html/rfc2822) Date: header (disregarding time and timezone) is within or later than the specified date. +- **searchSince(\DateTime $date)** - Messages whose internal date (disregarding time and timezone) is within or later than the specified date +- **searchSmaller($n)** - Messages with an [RFC-2822](https://tools.ietf.org/html/rfc2822) size smaller than the specified number of octets +- **searchSubject($string)** - Messages that contain the specified string in the envelope structure's SUBJECT field +- **searchText($string)** - Messages that contain the specified string in the header or body of the message +- **searchTo($string)** - Messages that contain the specified string in the envelope structure's TO field +- **searchUid($string)** - Messages with unique identifiers corresponding to the specified unique identifier set. Sequence set ranges are permitted +- **searchUnanswered()** - Messages that do not have the \Answered flag set +- **searchUndeleted()** - Messages that do not have the \Deleted flag set +- **searchUndraft()** - Messages that do not have the \Draft flag set +- **searchUnflagged()** - Messages that do not have the \Flagged flag set +- **searchUnkeyword($keyword)** - Messages that do not have the specified keyword flag set +- **searchUnseen()** - Messages that do not have the \Seen flag set + + +#### LOGOUT +The LOGOUT command informs the server that the client is done with the connection + +**Usage**: + +```php +if ($manager->transporter()->connection()->isConnected()) { + $logoutResponse = $manager->run($manager->command()->logout()->build()); + $manager->transporter()->connection()->disconnect(); +} +``` + +**Returns**: [Response](04_Responses.md) + +> **Note:** you must use the disconnect method after issuing a LOGOUT command + +#### NOOP +Since any command can return a status update as untagged data, the +NOOP command can be used as a periodic poll for new messages or +message status updates during a period of inactivity (this is the +preferred method to do this). The NOOP command can also be used +to reset any inactivity autologout timer on the server + + +**Usage**: + +```php +$response = $manager->run($manager->command()->noop()->build()); +``` + +**Returns**: [Response](04_Responses.md) + + +#### CHECK +The CHECK command requests a checkpoint of the currently selected +mailbox. A checkpoint refers to any implementation-dependent +housekeeping associated with the mailbox (e.g., resolving the +server's in-memory state of the mailbox with the state on its +disk) that is not normally executed as part of each command. A +checkpoint MAY take a non-instantaneous amount of real time to +complete. If a server implementation has no such housekeeping +considerations, CHECK is equivalent to NOOP + + +**Usage**: + +```php +$response = $manager->run($manager->command()->check()->build()); +``` + +**Returns**: [Response](04_Responses.md) + +#### APPEND +The APPEND command appends the literal argument as a new message +to the end of the specified destination mailbox. This argument +SHOULD be in the format of an [RFC-2822](https://tools.ietf.org/html/rfc2822) message. + +**Usage**: + +```php +// Rfc 2822 Message Truncated to keep the mark down formatting +// to see an example of $rawMessage: https://tools.ietf.org/html/rfc3501#page-46 +$response = $manager->run($manager->command()->append($rawMessage)->build()); +``` + +**Returns**: [Response](04_Responses.md) + + +#### CLOSE +The CLOSE command permanently removes all messages that have the +\Deleted flag set from the currently selected mailbox, and returns +to the authenticated state from the selected state + + +**Usage**: + +```php +$response = $manager->run($manager->command()->close()->build()); +``` + +**Returns**: [Response](04_Responses.md) + + +#### COPY +The COPY command copies the specified message(s) to the end of the +specified destination mailbox. + +**Usage**: + +```php +$response = $manager->run($manager->command()->copy()->withRange(1,2)->toMailbox('Invoices')->build()); +``` + +**Returns**: [Response](04_Responses.md) + +#### CREATE +The CREATE command creates a mailbox with the given name. + +**Usage**: + +```php +$response = $manager->run($manager->command()->create('INVOICES')->build()); +``` + +**Returns**: [Response](04_Responses.md) + +#### EXAMINE +The EXAMINE command is identical to SELECT and returns the same +output; however, the selected mailbox is identified as read-only. +No changes to the permanent state of the mailbox. + +**Usage**: + +```php +$response = $manager->run($manager->command()->examine('INVOICES')->build()); +``` + +**Returns**: [Response](04_Responses.md) + + +#### DELETE + +The DELETE command permanently removes the mailbox with the given name. + +**Usage**: + +```php +$response = $manager->run($manager->command()->delete('INVOICES')->build()); +``` + +**Returns**: [Response](04_Responses.md) + +#### EXPUNGE +The EXPUNGE command permanently removes all messages that have the +\Deleted flag set from the currently selected mailbox. + + +**Usage**: + +```php +$response = $manager->run($manager->command()->expunge()->build()); +``` + +**Returns**: [Response](04_Responses.md) + +#### LSUB +The LSUB command returns a subset of names from the set of names +that the user has declared as being "active" or "subscribed". + +**Usage**: + +```php +$response = $manager->run($manager->command()->lsub("#news.", "comp.mail.*")->build()); +``` + +**Returns**: MailboxList + +#### RENAME +The RENAME command changes the name of a mailbox. + +**Usage**: + +```php +$response = $manager->run($manager->command()->rename('INVOICES, 'QUOTES')->build()); +``` + +**Returns**: [Response](04_Responses.md) + + +#### STATUS +The STATUS command requests the status of the indicated mailbox. + +**Usage**: + +```php +$response = $manager->run($manager->command()->status()->build()); +``` + +**Returns**: [Mailbox](04_Responses.md) + +#### STORE +The STORE command alters data associated with a message in the mailbox. The new value of the data associated is returned as if a FETCH COMMAND is run. + +**Usage**: + +```php +$response = $manager->run($manager->command()->store()->withMessage(1)->addFlag('Answered')->build()); +``` + +**Returns**: [Response](04_Responses.md) + +**Methods**: +- **withMessage($message)** - set the flags of a single message +- **withRange($messageFrom, $messageTo)** - set the flags or a contiguous set of messages +- **replaceFlag($flag)** - Replace the flags for the message (other than \Recent) with the argument +- **addFlag($flag)** - Add the argument to the flags for the message +- **removeFlag($flag)** - Remove the argument from the flags for the message + + +#### SUBSCRIBE + +The SUBSCRIBE command adds the specified mailbox name to the +server's set of "active" or "subscribed" mailboxes as returned by +the LSUB command. + +**Usage**: + +```php +$response = $manager->run($manager->command()->subscribe('INVOICES')->build()); +``` + +**Returns**: [Response](04_Responses.md) + + +#### UNSUBSCRIBE + +The UNSUBSCRIBE command removes the specified mailbox name from +the server's set of "active" or "subscribed" mailboxes as returned +by the LSUB command + +**Usage**: + +```php +$response = $manager->run($manager->command()->unsubscribe('INVOICES')->build()); +``` + +**Returns**: [Response](04_Responses.md) + +### RFC 2177 - IDLE Command + +The Internet Message Access Protocol requires a client to +poll the server for changes to the selected mailbox. It's often more desirable to have the server transmit +updates to the client in real time. The IDLE command which is defined in +[RFC 2177](https://tools.ietf.org/html/rfc2177) enabled the client to receive updates in real time. + +The IDLE has a different process from the other supported commands. + + +```php +while(true) { + $response = $manager->run($manager->command()->idle()->build()); + $message = $manager->run($manager->command()->fetch($response->number())->build()); + // import message +} +``` + +**Returns**: [Mailbox](04_Responses.md) + +### Not Supported + +Some commands are currently not supported: +- STARTTLS for port 143 is not supported because most IMAP servers use port 993 via a TLS. +- AUTHENTICATE - LOGIN over TLS connection is more secure that SASL AUTHENTICATE +- [RFC 5256](https://tools.ietf.org/html/rfc5256) SORT - Support the SORT extension IMAP servers is limited diff --git a/docs/en/04_Responses.md b/docs/en/04_Responses.md new file mode 100644 index 0000000..3dad01c --- /dev/null +++ b/docs/en/04_Responses.md @@ -0,0 +1,135 @@ +IMAP has a set of common responses for each command. These responses are defined in the [RFC 3501](https://tools.ietf.org/html/rfc3501). + +**Table Of Contents** + +[TOC] + +## Response +A Response object represents is the foundation of all of the responses. It holds both the tagged and untagged responses which +were received from an IMAP server. Typically, an IMAP server will send a OK/BAD/NO response. BAD/NO Responses are thrown +as exceptions. However, a Response object will typically be a OK response. + +**Methods:** +- **status()** - status code sent from server eg OK / BAD / NO +- **included()** - the non tagged responses which are included +- **message()** - the tagged response + +## List Containers +When Pimap has to supply a list of objects, it will use one of the following containers. All list containers implement +the ArrayAccess interface. Which allows you to access it's elements like an array. + +### Capability +A Capability is an object which implements the array access interface. Each offset or element represents a capability which is supported by the IMAP server + +**Methods:** +- offsetExists($capability) - is capability supported? + +### MailboxList +A MailboxList is an object which implements the array access interface. Each offset or element represents a mailbox (also known as a folder or a filter) + +### MessageList +A MailboxList is an object which implements the array access interface. Each offset or element represents a message. + + +## Response Containers + +When Pimap has to supply a specialisation of a response, it will use one of the following containers. All Response containers implement the ArrayAccess interface. Which allows you to access it's elements like an array. + + +### Mailbox +A Mailbox is an object which represents a mailbox (also known as a folder or a filter). + +**Methods:** +- **hierarchy()** - The hierarchy delimiter is a character used to delimit levels of hierarchy in a mailbox name. A client can use it to create child +mailboxes, and to search higher or lower levels of naming hierarchy. All children of a top-level hierarchy node MUST use the same separator character. A NIL hierarchy delimiter means +that no hierarchy exists; the name is a "flat" name. +- **name()** - The name of the mailbox +- **attributes()** + - _Noinferiors_ - It is not possible for any child levels of hierarchy to exist under this name; no child levels exist now and none can be created in the future. + - _Noselect_ - It is not possible to use this name as a selectable mailbox. + - _Marked_ - The mailbox has been marked "interesting" by the server; the mailbox probably contains messages that have been added since the last time the mailbox was selected. + - _Unmarked_ - The mailbox does not contain any additional messages since the last time the mailbox was selected. +- **flags()** - Defined flags in the mailbox. +- **exists()** - The number of messages in the mailbox. +- **recent()** - The number of messages with the \Recent flag set. +- **unseen()** - The message sequence number of the first unseen message in the mailbox. +- **uidValidity()** - The unique identifier validity value. +- **uidNext()** - The next unique identifier value. + + +### Message +A Message is used to represent a IMAP message (also known as an email). The information kept in an email message may change depending on which command was executed. + +**Required Methods:** +- **number()** - the index or position in the mailbox. +- **uid()** - the unique identifier of a message. +- **hasHeader()** - has the header been included in the message +- **hasBody()** - has body been included in the message + +**Optional Methods:** may return null or an empty container. +- **flags()** - the flags of the message +- **header()** - the message header +- **body()** - the message body + +#### MessageFlags +A MessageFlags is a container which contains a list of flags. + +**Methods:** +- **isSeen()** - has Message been read +- **isAnswered()** - has been Message answered +- **isFlagged()** - is "flagged" for urgent/special attention +- **isDeleted()** - is "deleted" for removal later by the EXPUNGE command +- **isDraft()** - has Message not completed composition (marked as a draft). +- **isRecent()** - is "recently" arrived in this mailbox. + + +#### MessageHeader +A MessageHeader represents information about the email. + +**Methods:** +- **date()** - The origination date specifies the date and time at which the creator of the message indicated that the message was complete and ready to enter the mail delivery system. +- **to()** - contains the address(es) of the primary recipient(s) of the message. +- **from()** - where the email originates from. +- **replyTo()** - where the email originates from / an email to reply to. +- **cc()** - "carbon copy" - contains the addresses of the primary recipient(s) of the message. +- **bcc()** - "blind carbon copy" - contains addresses of recipients of the message whose addresses are not to be revealed to other recipients of the message. +- **subject()** - brief description of what the email is about. +- **messageId()** - contains a single unique message identifier. + +#### MessageBody +A MessageHeader represents the content of email. + +**Methods:** +- **structure()** - get the body structure or information about the body. +- **html()** - get the html part of the body. +- **text()** - get the plain text part of the body. +- **attachments()** - get the attachments or embedded content of the body. + +#### MessageBodyStructure +A MessageHeader represents information about the content of email. + +**Methods:** +- **htmlBodyExists()** - does message contains a html part? +- **plainTextBodyExists()** - does message contain a plain text part? +- **attachmentsExists()** - does message contain attachments or embedded content? + +#### MessageAttachment +A MessageHeader represents an attachment or the mime parts of email. This also includes content which is emedded in the +message text or html. + +**Attachment Methods:** +- **structure()** - get the information about the attachment. +- **hasContent()** - has the attachments content been included? +- **content()** - get attachments content been included. + +**Embedded Content Methods:** +- **isInline()** - is the attachment embedded content? +- **contentId()** - get the content id (cid:) which is referenced in the text or html of the body + +#### MessageAttachmentStructure +A MessageHeader represents information about an attachment included of email. + +**Methods:** +- **type()** - get the mime type of the content. Eg text/plain, text/html, image/jpeg etc... +- **name()** - get the file name of the content +- **size()** - get the size (in bytes) of the content diff --git a/docs/en/05_Pipeline.md b/docs/en/05_Pipeline.md new file mode 100644 index 0000000..ca36f63 --- /dev/null +++ b/docs/en/05_Pipeline.md @@ -0,0 +1,101 @@ +The pipeline is used to cache the commands and responses. It is up to you to choose how you wish to +persist the pipeline. This library does not provide any persistence mechanisms. It only provides the +objects to cache. With the pipeline in conjunction with the provided interpreters you can also passively detect +details about server capability, mailboxes, and messages. All tags are automatically managed by the pipeline. + +### Pipe +A Pipe is a container for all pipeline caching activity. + +**Pimap Manager Supports:** +- **addResponse($response)** - Add a response from the IMAP server. +- **getResponse()** - get the response from the IMAP server. +- **getTag()** - get the tag used for the command. +- **getCommand()** - get the command used. +- **buildCommand()** - build the command string. +- **addTokenList(TokenList $tokenList)** - add result from the tokenizer. +- **isTokenized()** - has the response been tokenized. +- **tokenList()** - get the result from the tokenizer. +- **addLexemeList(LexemeList $lexemeList)** - add result from the lexeme interpreter. +- **isLexemized()** - has the response been broken up into lexemes. +- **lexemeList()** - get the result from the lexeme interpreter. +- **addParsed($parsedContent)** - add the parsed Result/Message/List. +- **parsed()** - get the parsed Result/Message/List. + +**Php Imap Extension Manager Supports:** +- **addResponse($response)** - Add a response from the IMAP server. +- **getResponse()** - get the response from the IMAP server. +- **getTag()** - get the tag used for the command. +- **getCommand()** - get the command used. +- **addParsed($parsedContent)** - add the parsed Result/Message/List. +- **parsed()** - get the parsed Result/Message/List. + +### Pipeline +A Pipeline is a list of Pipes. + +**Methods:** +- **add(CommandBuildInterface $command)** - create a pipe from a command. +- **pipes()** - get the iterator for the list of pipes. +- **getLastPipe()** - the last pipe which was created. +- **pipeByCommand($command)** - find a pipe based on the command. +- **mergePipeline($pipeline)** - merge an other pipeline into the current pipeline. + + +### Session Caching +Caching the pipeline in the users session is a relatively simple method to persist a temporary pipeline between HTTP requests. You may want to define this caching mechanism using the following methods. + +```php +use SalesAgility\Imap\Pipeline\PipelineInterface; + +/** + * restores pipeline from cache + * @param string $cacheId - identifer of the connection eg tcp://hostname:port/username + * @returns null|PipelineInterface pipeline object that has been cache + */ +function restoreCachePipeLine($cacheId) +{ + return unserialize($_SESSION[$connectionString]); +} + +/** + * saves pipeline to cache + * @param string $cacheId - identifer of the connection eg tcp://hostname:port/username + * @param PipelineInterface $pipeline - pipeline object to cache + * @returns PipelineInterface + */ +function cachePipeline($connectionString, PipelineInterface $pipeline) +{ + $_SESSION[$connectionString] = serialize($pipeline); +} +``` + +Then you can choose to use the cache copy of the response. +```php +use SalesAgility\Imap\Manager\ManagerInterface; + +/** + * Example of how to use session caching + * @param ManagerInterface $manager + * @return MessageList + */ +function fetchList(ManagerInterface $manager) +{ + $cachedPipeline = restoreCachePipeLine('tcp://imap.emailservice.com:993/username', $manager->pipeline()); + $fetchCommand = $manager->command() + ->fetchRange(1, 5) + ->header() + ->flags() + ->build(); + + // is cached version of reponse available? + $cachedPipe = $cachedPipeline->pipeByCommand($command) + if(!empty($cachedPipe) { + // use cached response + return $cachedPipe->parsed(); + } else { + // run command and get the response from the server + return $manager->run($fetchCommand); + } + + cachePipeline('tcp://imap.emailservice.com:993/username', $manager->pipeline()); +} +``` \ No newline at end of file diff --git a/docs/en/06_Architecture.md b/docs/en/06_Architecture.md new file mode 100644 index 0000000..66edc37 --- /dev/null +++ b/docs/en/06_Architecture.md @@ -0,0 +1,164 @@ + +### Overview + +```text ++-----------------------+ +| | +| Client | +| | ++-----------------------+ + ^ + | + V ++-----------------------+ +| | +| Protocol Manager | +| | ++-----------------------+ + ^ + | + V ++-----------------------+ +| | +| Message Transporter | +| | ++-----------------------+ + ^ + | + V ++-----------------------+ +| | +| Connection Manager | +| | ++-----------------------+ + ^ + | + V ++-----------------------+ +| | +| Imap Server | +| | ++-----------------------+ +``` +#### Protocol Manager +- Manages the connection manager +- Manages the imap protocol requests and responses +- Manages the evaluation of commands and responses + +#### Message Transporter +- Maintain the protocol link between the client and the server +- Relays protocol messages to the Connection Manager and Protocol Manager +- Builds the server responses +- Determines what constitutes as a the end of file for the protocol + +#### Connection Manager +- Manages connection configuration +- Manages network socket +- Manages network level encryption SSL/TLS +- Transmits client messages +- Waits for server response messages + + +### Processes +#### Get Manager via Factory +``` +|Client |Command Builder |Manager |Pipeline |Transporter |Connection +| | | | | | +| +<----------------------------------------------------------------------------| +| | | | | | | +| +<-----------------------------------------------------------| | +| | | | | | | +| +<------------------------------------------| | | +| | | | | | | +| V | | | | | +|-1->Factory------------------------>| | | | +| | |--------------->| | | +| | |-------------------------------->| | +| | | | |<---------------| +|<-2---------------------------------|<--------------------------------| | +``` + +#### Configure Connection +``` +|Client |Command Builder |Manager |Pipeline |Transporter |Connection +|<--1------------------------------------------------------------------| | +| | | | | | +|<--2------------------------------------------------------------------|<---------------| +| | | | | | +|---3---------------------------------------------------------------------------------->| +``` + +#### Command Builder +- Provides a set of interfaces to build imap commands. +- Uses PHPDoc to be IDE Friendly +- Builds / Optimises the commands sent via the protocol manager to the server + +### Pipeline +- Caches the messages which have been transmitted to the server +- Caches the messages which have been received from the server +- Caches the data structures used to create commands and parse responses +- Manages the tags in the requests and responses +- Cache last for the life time of user http request + +#### Running commands + +``` +|Client |Command Builder |Manager |Pipeline |Transporter |Connection +| | | | | | +|<--1---------------| | | | | +| | | | | | +|---2------------------------------->| | | | +| | |-3------------->| | | +| | | | | | +| | |-4------------------------------>| | +| | | | | | +| | | | |-5------------->| +| | | | | | +| | | | |<-6-------------| +| | |<--7-----------------------------| | +| | |---8----------->| | | +| | | +| | | |Intepreter |Lexemizer |Tokenizer +| | |---9----------->| | | +| | | |-10------------>| | +| | | | |--11----------->| +| | | | | | +| | | | |<-12------------| +| | | |<-13------------| | +| | |<-14------------| | | +| | | +| | | |Pipeline +| | |--15----------->| +|<--16-------------------------------| | +``` + +#### Command Validator +- Validates the structure of the command builder + +#### Token Parser (Tokenizer) +- Low Level Parser +- Iterates through the response messages and produces a RFC 2882 Token List + +A Token List is a collection of Tokens. + +#### Tokens +- store the positions of where a token starts and ends in a request/response +- store the type of token +- provide an easy to read interface to determine the type of token +- Types may be whitespace, folding whitespace, special, group, optional + +#### Lexeme Interpreter (Lexemizer) +- Creates a lexeme list from a token list RFC 822/2822 rules +- Provides the initial level of message interpretation +- Reduces protocol noise by removing folding space + +A Lexeme List is an collection of lexemes + +#### Lexemes +- Store a collection of Tokens +- Adds higher level token types to the tokens +- Types may include Keyword, Number, Text, Whitespace, Group, Optional, CText + +#### Interpreter +- Creates a Response or a List Of Responses from the Lexeme List +- Provides the final level of interpretation. \ No newline at end of file diff --git a/src/Imap/CommandBuilder/CommandBuildArgumentsInterface.php b/src/Imap/CommandBuilder/CommandBuildArgumentsInterface.php new file mode 100755 index 0000000..88f38be --- /dev/null +++ b/src/Imap/CommandBuilder/CommandBuildArgumentsInterface.php @@ -0,0 +1,65 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder; + + +/** + * Interface CommandBuildArgumentsInterface + * @package SalesAgility\Imap\CommandBuilder + */ +interface CommandBuildArgumentsInterface +{ + /** + * @param $tag + * @return CommandBuildArgumentsInterface + */ + public function tagged($tag); + + /** + * @return CommandBuildArgumentsInterface + */ + public function untagged(); + + /** + * @return string + */ + public function asString(); + + /** + * @return array + */ + public function asArray(); + + /** + * @return string + */ + public function command(); + + /** + * @return string + */ + public function commandPrefix(); + + /** + * @return array + */ + public function commandArguments(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/CommandBuildInterface.php b/src/Imap/CommandBuilder/CommandBuildInterface.php new file mode 100755 index 0000000..5bb19a8 --- /dev/null +++ b/src/Imap/CommandBuilder/CommandBuildInterface.php @@ -0,0 +1,34 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder; + + +/** + * Interface CommandBuildInterface + * @package SalesAgility\Imap\CommandBuilder + */ +interface CommandBuildInterface +{ + /** + * @return CommandBuildArgumentsInterface + */ + public function build(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/CommandRawInterface.php b/src/Imap/CommandBuilder/CommandRawInterface.php new file mode 100755 index 0000000..abd367f --- /dev/null +++ b/src/Imap/CommandBuilder/CommandRawInterface.php @@ -0,0 +1,36 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder; + + +/** + * Interface CommandRawInterface + * @package SalesAgility\Imap\CommandBuilder + */ +interface CommandRawInterface +{ + /** + * Call a command raw (Not Recommended) + * @param string $command + * @return CommandBuildInterface + */ + public function raw($command); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/CommandValidator/Command/NoopCommandValidator.php b/src/Imap/CommandBuilder/CommandValidator/Command/NoopCommandValidator.php new file mode 100755 index 0000000..fbc5f30 --- /dev/null +++ b/src/Imap/CommandBuilder/CommandValidator/Command/NoopCommandValidator.php @@ -0,0 +1,52 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\CommandBuilder\CommandValidator\Command; + + +use SalesAgility\Imap\CommandBuilder\CommandBuildArgumentsInterface; +use SalesAgility\Imap\CommandBuilder\CommandValidator\CommandValidationInterface; +use SalesAgility\Utility\Assert; + +/** + * Class NoopCommandValidator + * @package SalesAgility\Imap\CommandBuilder\CommandValidator\Command + */ +class NoopCommandValidator implements CommandValidationInterface +{ + /** + * @return string + */ + public function command() + { + return 'NOOP'; + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return bool + * @throws \Exception + */ + public function validate(CommandBuildArgumentsInterface $command) + { + Assert::is(empty($command->commandArguments()), 'NOOP does not take any arguments.'); + Assert::is($command->command() === 'NOOP', 'NOOP validation failed.'); + return true; + } +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/CommandValidator/CommandValidationInterface.php b/src/Imap/CommandBuilder/CommandValidator/CommandValidationInterface.php new file mode 100755 index 0000000..9aa2b11 --- /dev/null +++ b/src/Imap/CommandBuilder/CommandValidator/CommandValidationInterface.php @@ -0,0 +1,43 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\CommandValidator; + + +use SalesAgility\Imap\CommandBuilder\CommandBuildArgumentsInterface; + +/** + * Interface CommandValidationInterface + * @package SalesAgility\Imap\CommandBuilder\CommandValidator + */ +interface CommandValidationInterface +{ + + /** + * @return string + */ + public function command(); + + /** + * @param CommandBuildArgumentsInterface $command + * @throws \Exception + */ + public function validate(CommandBuildArgumentsInterface $command); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/CommandValidator/CommandValidationStatus.php b/src/Imap/CommandBuilder/CommandValidator/CommandValidationStatus.php new file mode 100755 index 0000000..60f545b --- /dev/null +++ b/src/Imap/CommandBuilder/CommandValidator/CommandValidationStatus.php @@ -0,0 +1,37 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\CommandValidator; + + +/** + * Class CommandValidationStatus + * @package SalesAgility\Imap\CommandBuilder\CommandValidator + */ +class CommandValidationStatus +{ + const MISSING_COMMAND = -100; + const MISSING_ARGUMENT = -99; + const UNDEFINED = -1; + const VALID = 0; + const INVALID_MESSAGE_NUMBER = 1; + const INVALID_COMMAND = 2; + const INVALID_ARGUMENT = 3; +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/CommandValidator/CommandValidatorAwareInterface.php b/src/Imap/CommandBuilder/CommandValidator/CommandValidatorAwareInterface.php new file mode 100755 index 0000000..798191a --- /dev/null +++ b/src/Imap/CommandBuilder/CommandValidator/CommandValidatorAwareInterface.php @@ -0,0 +1,35 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\CommandValidator; + + +/** + * Interface CommandValidatorAwareInterface + * @package SalesAgility\Imap\CommandBuilder\CommandValidator + */ +interface CommandValidatorAwareInterface +{ + /** + * @param CommandValidationInterface $commandValidation + * @return mixed + */ + public function addCommandValidator(CommandValidationInterface $commandValidation); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/AppendCommandArgumentsInterface.php b/src/Imap/CommandBuilder/Commands/AppendCommandArgumentsInterface.php new file mode 100755 index 0000000..c110081 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/AppendCommandArgumentsInterface.php @@ -0,0 +1,46 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface AppendCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface AppendCommandArgumentsInterface +{ + /** + * @param $flag + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|AppendCommandArgumentsInterface + */ + public function withFlag($flag); + + /** + * @param \DateTimeImmutable $datetime + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|AppendCommandArgumentsInterface + */ + public function withDateTime(\DateTimeImmutable $datetime); + + /** + * @param string $message eg. RFC822 message + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|AppendCommandArgumentsInterface + */ + public function withMessage($message); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/AppendCommandInterface.php b/src/Imap/CommandBuilder/Commands/AppendCommandInterface.php new file mode 100755 index 0000000..29a423f --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/AppendCommandInterface.php @@ -0,0 +1,36 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface AppendCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface AppendCommandInterface +{ + /** + * The APPEND command appends the literal argument as a new message + * to the end of the specified destination mailbox. + * @param string $mailbox eg. INBOX + * @return AppendCommandArgumentsInterface + */ + public function append($mailbox); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/CapabilityCommandInterface.php b/src/Imap/CommandBuilder/Commands/CapabilityCommandInterface.php new file mode 100755 index 0000000..a744d3f --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/CapabilityCommandInterface.php @@ -0,0 +1,37 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface CapabilityCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface CapabilityCommandInterface +{ + /** + * The CAPABILITY command requests a listing of capabilities that the + * server supports. The server MUST send a single untagged + * CAPABILITY response with "IMAP4rev1" as one of the listed + * capabilities before the (tagged) OK response. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function capability(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/CheckCommandInterface.php b/src/Imap/CommandBuilder/Commands/CheckCommandInterface.php new file mode 100755 index 0000000..88f4fb6 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/CheckCommandInterface.php @@ -0,0 +1,36 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface CheckCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface CheckCommandInterface +{ + /** + * The CHECK command requests a checkpoint of the currently selected + * mailbox. A checkpoint refers to any implementation-dependent + * housekeeping associated with the mailbox + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function check(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/CloseCommandInterface.php b/src/Imap/CommandBuilder/Commands/CloseCommandInterface.php new file mode 100755 index 0000000..8128adc --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/CloseCommandInterface.php @@ -0,0 +1,37 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface CloseCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface CloseCommandInterface +{ + /** + * The CLOSE command permanently removes all messages that have the + * \Deleted flag set from the currently selected mailbox, and returns + * to the authenticated state from the selected state. No untagged + * EXPUNGE responses are sent. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function close(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/CopyCommandArgumentsInterface.php b/src/Imap/CommandBuilder/Commands/CopyCommandArgumentsInterface.php new file mode 100755 index 0000000..0bf8a17 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/CopyCommandArgumentsInterface.php @@ -0,0 +1,41 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface CopyCommandArgumentsInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface CopyCommandArgumentsInterface +{ + /** + * @param string $messageFrom eg 1 + * @param string $messageTo eg. 20 + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface | CopyCommandArgumentsInterface + */ + public function withRange($messageFrom, $messageTo); + + /** + * @param $mailbox + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface | CopyCommandArgumentsInterface + */ + public function toMailbox($mailbox); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/CopyCommandInterface.php b/src/Imap/CommandBuilder/Commands/CopyCommandInterface.php new file mode 100755 index 0000000..09839e6 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/CopyCommandInterface.php @@ -0,0 +1,37 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface CopyCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface CopyCommandInterface +{ + /** + * The COPY command copies the specified message(s) to the end of the + * specified destination mailbox. The flags and internal date of the + * message(s) SHOULD be preserved, and the Recent flag SHOULD be set, + * in the copy. + * @return CopyCommandArgumentsInterface + */ + public function copy(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/CreateCommandInterface.php b/src/Imap/CommandBuilder/Commands/CreateCommandInterface.php new file mode 100755 index 0000000..938ce68 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/CreateCommandInterface.php @@ -0,0 +1,35 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface CreateCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface CreateCommandInterface +{ + /** + * Creates a mailbox with the given name + * @param string $mailbox eg. Invoices + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function create($mailbox); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/DeleteCommandInterface.php b/src/Imap/CommandBuilder/Commands/DeleteCommandInterface.php new file mode 100755 index 0000000..92c9048 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/DeleteCommandInterface.php @@ -0,0 +1,35 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface DeleteCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface DeleteCommandInterface +{ + /** + * Permanently removes the mailbox with the given name + * @param string $mailbox + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function delete($mailbox); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/ExamineCommandInterface.php b/src/Imap/CommandBuilder/Commands/ExamineCommandInterface.php new file mode 100755 index 0000000..58b054e --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/ExamineCommandInterface.php @@ -0,0 +1,37 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface ExamineCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface ExamineCommandInterface +{ + /** + * Selects a mailbox returns the same output; however, + * the selected mailbox is identified as read-only. + * No changes to the permanent state of the mailbox are made + * @param string $mailbox eg INBOX + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function examine($mailbox); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/ExpungeCommandInterface.php b/src/Imap/CommandBuilder/Commands/ExpungeCommandInterface.php new file mode 100755 index 0000000..05f7c13 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/ExpungeCommandInterface.php @@ -0,0 +1,37 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface ExpungeCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface ExpungeCommandInterface +{ + /** + * The EXPUNGE command permanently removes all messages that have the + * \Deleted flag set from the currently selected mailbox. Before + * returning an OK to the client, an untagged EXPUNGE response is + * sent for each message that is removed. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function expunge(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/FetchCommandArgumentsInterface.php b/src/Imap/CommandBuilder/Commands/FetchCommandArgumentsInterface.php new file mode 100755 index 0000000..48750f4 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/FetchCommandArgumentsInterface.php @@ -0,0 +1,52 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface FetchCommandArgumentsInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface FetchCommandArgumentsInterface +{ + /** + * header (and BODYSTRUCTURE) of the message + * @return FetchCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function header(); + + /** + * body of the message + * @return FetchCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function body(); + + /** + * uid of the message + * @return FetchCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function uids(); + + /** + * flags of the message + * @return FetchCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function flags(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/FetchCommandInterface.php b/src/Imap/CommandBuilder/Commands/FetchCommandInterface.php new file mode 100755 index 0000000..74d72c7 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/FetchCommandInterface.php @@ -0,0 +1,58 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +use SalesAgility\Imap\Response\MessageList; + + +/** + * Interface FetchCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface FetchCommandInterface +{ + /** + * The FETCH command retrieves data associated with a message in the + * mailbox. + * @param int|string $message + * @return FetchCommandArgumentsInterface + */ + public function fetch($message); + + /** + * The FETCH command retrieves data associated with a message in the + * mailbox. + * @param int|string $messageFrom + * @param int|string $messageTo + * @return FetchCommandArgumentsInterface + */ + public function fetchRange($messageFrom, $messageTo); + + /** + * The FETCH command retrieves data associated with a message in the + * mailbox. + * + * Typically used to fetch the messages from the output of a search command + * @param MessageList $messages + * @return FetchCommandArgumentsInterface + */ + public function fetchSet(MessageList $messages); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/IdleCommandInterface.php b/src/Imap/CommandBuilder/Commands/IdleCommandInterface.php new file mode 100755 index 0000000..44e5cc9 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/IdleCommandInterface.php @@ -0,0 +1,33 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface IdleCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface IdleCommandInterface +{ + /** + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function idle(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/LSubCommandInterface.php b/src/Imap/CommandBuilder/Commands/LSubCommandInterface.php new file mode 100755 index 0000000..8b6e466 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/LSubCommandInterface.php @@ -0,0 +1,39 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface LSubCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface LSubCommandInterface +{ + /** + * The LSUB command returns a subset of names from the set of names + * that the user has declared as being "active" or "subscribed". + * Zero or more untagged LSUB replies are returned. The arguments to + * LSUB are in the same form as those for LIST. + * @param string $reference eg. "" - if empty list shows all content from root + * @param string $mailbox eg. "*" - if empty list shows all content from root + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function listSubsetMailbox($reference = "", $mailbox = "*"); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/ListCommandInterface.php b/src/Imap/CommandBuilder/Commands/ListCommandInterface.php new file mode 100755 index 0000000..d257570 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/ListCommandInterface.php @@ -0,0 +1,40 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface ListCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface ListCommandInterface +{ + /** + * The LIST command returns a subset of names from the complete set + * of all names available to the client. Zero or more untagged LIST + * replies are returned, containing the name attributes, hierarchy + * delimiter, and name; see the description of the LIST reply for + * more detail + * @param string $reference eg. "" - if empty list shows all content from root + * @param string $mailbox eg. "*" - if empty list shows all content from root + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function listMailbox($reference = "", $mailbox = "*"); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/LoginCommandInterface.php b/src/Imap/CommandBuilder/Commands/LoginCommandInterface.php new file mode 100755 index 0000000..1e47e68 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/LoginCommandInterface.php @@ -0,0 +1,35 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface LoginCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface LoginCommandInterface +{ + /** + * Identifies the client to the server and carries the plaintext password authenticating this user + * Note: it is recommended to use TLS before running this command + * @return LoginCommandUserArgumentInterface + */ + public function login(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/LoginCommandPasswordArgumentInterface.php b/src/Imap/CommandBuilder/Commands/LoginCommandPasswordArgumentInterface.php new file mode 100755 index 0000000..a262469 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/LoginCommandPasswordArgumentInterface.php @@ -0,0 +1,35 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + + +/** + * Interface LoginCommandPasswordArgumentInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface LoginCommandPasswordArgumentInterface +{ + /** + * @param string $password + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function password($password); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/LoginCommandUserArgumentInterface.php b/src/Imap/CommandBuilder/Commands/LoginCommandUserArgumentInterface.php new file mode 100755 index 0000000..fac3cec --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/LoginCommandUserArgumentInterface.php @@ -0,0 +1,35 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + + +/** + * Interface LoginCommandUserArgumentInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface LoginCommandUserArgumentInterface +{ + /** + * @param string $user + * @return LoginCommandPasswordArgumentInterface + */ + public function user($user); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/LogoutCommandInterface.php b/src/Imap/CommandBuilder/Commands/LogoutCommandInterface.php new file mode 100755 index 0000000..d7bd813 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/LogoutCommandInterface.php @@ -0,0 +1,36 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + + +/** + * Interface LogoutCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface LogoutCommandInterface +{ + /** + * The LOGOUT command informs the server that the client is done with + * the connection. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function logout(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/NoopCommandInterface.php b/src/Imap/CommandBuilder/Commands/NoopCommandInterface.php new file mode 100755 index 0000000..6f29ea5 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/NoopCommandInterface.php @@ -0,0 +1,31 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + + +/** + * Interface NoopCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface NoopCommandInterface +{ + public function noop(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/RenameCommandInterface.php b/src/Imap/CommandBuilder/Commands/RenameCommandInterface.php new file mode 100755 index 0000000..2ec08ae --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/RenameCommandInterface.php @@ -0,0 +1,36 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface RenameCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface RenameCommandInterface +{ + /** + * Changes the name of a mailbox + * @param string $mailbox the current name of the mailbox eg. Invoices + * @param string $newMailbox the new name for the mailbox eg. Receipts + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function rename($mailbox, $newMailbox); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/SearchCommandArgumentsInterface.php b/src/Imap/CommandBuilder/Commands/SearchCommandArgumentsInterface.php new file mode 100755 index 0000000..114b497 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/SearchCommandArgumentsInterface.php @@ -0,0 +1,305 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +use SalesAgility\Imap\Response\MessageList; + +/** + * Interface SearchCommandArgumentsInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface SearchCommandArgumentsInterface +{ + /** + * Messages with message sequence numbers corresponding to the + * specified message sequence number set. + * @param string $messageFrom 1 + * @param string $messageTo 2 + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function withRange($messageFrom, $messageTo); + + /** + * Messages with message sequence numbers corresponding to the + * specified message sequence number set. + * @param MessageList $messages eg array(1, 2, 3, 5, 8) + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function withSequence(MessageList $messages); + + /** + * All messages in the mailbox; the default initial key for + * ANDing. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchAll(); + + /** + * Messages with the \Answered flag set. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchAnswered(); + + /** + * Messages that contain the specified string in the envelope + * structure's BCC field. + * @param $string + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchBcc($string); + + /** + * Messages whose internal date (disregarding time and timezone) + * is earlier than the specified date. + * @param \DateTimeImmutable $dateTime + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchBefore(\DateTimeImmutable $dateTime); + + /** + * Messages that contain the specified string in the body of the + * message. + * @param $string + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchBody($string); + + /** + * Messages that contain the specified string in the envelope + * structure's CC field. + * @param $string + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchCc($string); + + /** + * Messages with the \Deleted flag set. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchDeleted(); + + /** + * Messages with the \Draft flag set. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchDraft(); + + /** + * Messages with the \Flagged flag set. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchFlagged(); + + /** + * Messages that contain the specified string in the envelope + * structure's FROM field. + * @param $string + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchFrom($string); + + /** + * Messages that have a header with the specified field-name (as + * defined in [RFC-2822]) and that contains the specified string + * in the text of the header (what comes after the colon). If the + * string to search is zero-length, this matches all messages that + * have a header line with the specified field-name regardless of + * the contents. + * @param $name + * @param $string + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchHeader($name, $string); + + /** + * Messages with the specified keyword flag set. + * @param string $keyword + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchKeyword($keyword); + + /** + * Messages with an [RFC-2822] size larger than the specified + * number of octets. + * @param $n + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchLarger($n); + + /** + * Messages that have the \Recent flag set but not the \Seen flag. + * This is functionally equivalent to "(RECENT UNSEEN)". + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchNew(); + + /** + * Messages that do not match the specified search key. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchNot(); + + /** + * Messages that do not have the \Recent flag set. This is + * functionally equivalent to "NOT RECENT" (as opposed to "NOT + * NEW"). + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchOld(); + + /** + * Messages whose internal date (disregarding time and timezone) + * is within the specified date. + * @param \DateTimeImmutable $date + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchOn(\DateTimeImmutable $date); + + /** + * Messages that match either search key. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchOr(); + + /** + * Messages that have the \Recent flag set. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchRecent(); + + /** + * Messages that have the \Seen flag set. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchSeen(); + + /** + * Messages whose [RFC-2822] Date: header (disregarding time and + * timezone) is earlier than the specified date. + * @param \DateTimeImmutable $date + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchSentBefore(\DateTimeImmutable $date); + + /** + * Messages whose [RFC-2822] Date: header (disregarding time and + * timezone) is within the specified date. + * @param \DateTimeImmutable $date + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchSentOn(\DateTimeImmutable $date); + + /** + * Messages whose [RFC-2822] Date: header (disregarding time and + * timezone) is within or later than the specified date. + * @param \DateTimeImmutable $date + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchSentSince(\DateTimeImmutable $date); + + /** + * Messages whose internal date (disregarding time and timezone) + * is within or later than the specified date. + * @param \DateTimeImmutable $date + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchSince(\DateTimeImmutable $date); + + /** + * Messages with an [RFC-2822] size smaller than the specified + * number of octets. + * @param $n + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchSmaller($n); + + /** + * Messages that contain the specified string in the envelope + * structure's SUBJECT field. + * @param $string + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchSubject($string); + + /** + * Messages that contain the specified string in the header or + * body of the message. + * @param $string + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchText($string); + + /** + * Messages that contain the specified string in the envelope + * structure's TO field. + * @param $string + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchTo($string); + + /** + * Messages with unique identifiers corresponding to the specified + * unique identifier set. Sequence set ranges are permitted. + * + * @see searchRange() + * @see searchSequence() + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchUid($string); + + /** + * Messages that do not have the \Answered flag set. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchUnanswered(); + + /** + * Messages that do not have the \Deleted flag set. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchUndeleted(); + + /** + * Messages that do not have the \Draft flag set. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchUndraft(); + + /** + * Messages that do not have the \Flagged flag set. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchUnflagged(); + + /** + * Messages that do not have the specified keyword flag set. + * @param string $keyword + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchUnkeyword($keyword); + + /** + * Messages that do not have the \Seen flag set. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SearchCommandArgumentsInterface + */ + public function searchUnseen(); + +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/SearchCommandInterface.php b/src/Imap/CommandBuilder/Commands/SearchCommandInterface.php new file mode 100755 index 0000000..79f5fe0 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/SearchCommandInterface.php @@ -0,0 +1,38 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface SearchCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface SearchCommandInterface +{ + /** + * The SEARCH command searches the mailbox for messages that match + * the given searching criteria. Searching criteria consist of one + * or more search keys. The untagged SEARCH response from the server + * contains a listing of message sequence numbers corresponding to + * those messages that match the searching criteria. + * @return SearchCommandArgumentsInterface + */ + public function search(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/SelectCommandInterface.php b/src/Imap/CommandBuilder/Commands/SelectCommandInterface.php new file mode 100755 index 0000000..95cfe39 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/SelectCommandInterface.php @@ -0,0 +1,35 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface SelectCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface SelectCommandInterface +{ + /** + * Selects a mailbox so that messages in the mailbox can be accessed + * @param string $mailbox eg INBOX + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function select($mailbox); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/SortCommandArgumentsInterface.php b/src/Imap/CommandBuilder/Commands/SortCommandArgumentsInterface.php new file mode 100755 index 0000000..e4dea9e --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/SortCommandArgumentsInterface.php @@ -0,0 +1,85 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + + +/** + * Interface SortCommandArgumentsInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface SortCommandArgumentsInterface +{ + /** + * Internal date and time of the message. This differs from the + * ON criteria in SEARCH, which uses just the internal date. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SortCommandArgumentsInterface + */ + public function byArrival(); + + /** + * [IMAP] addr-mailbox of the first "cc" address. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SortCommandArgumentsInterface + */ + public function byCc(); + + /** + * Sent date and time + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SortCommandArgumentsInterface + */ + public function byDate(); + + /** + * [IMAP] addr-mailbox of the first "From" address. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SortCommandArgumentsInterface + */ + public function byFrom(); + + /** + * Followed by another sort criterion, has the effect of that + * criterion but in reverse (descending) order. + * Note: REVERSE only reverses a single criterion, and does not + * affect the implicit "sequence number" sort criterion if all + * other criteria are identical. Consequently, a sort of + * REVERSE SUBJECT is not the same as a reverse ordering of a + * SUBJECT sort. This can be avoided by use of additional + * criteria, e.g., SUBJECT DATE vs. REVERSE SUBJECT REVERSE + * DATE. In general, however, it's better (and faster, if the + * client has a "reverse current ordering" command) to reverse + * the results in the client instead of issuing a new SORT. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SortCommandArgumentsInterface + */ + public function byReverse(); + + /** + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SortCommandArgumentsInterface + */ + public function bySize(); + + /** + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SortCommandArgumentsInterface + */ + public function bySubject(); + + /** + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface|SortCommandArgumentsInterface + */ + public function byTo(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/SortCommandInterface.php b/src/Imap/CommandBuilder/Commands/SortCommandInterface.php new file mode 100755 index 0000000..9d1edec --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/SortCommandInterface.php @@ -0,0 +1,36 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + + +/** + * Interface SortCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface SortCommandInterface +{ + /** + * The SORT command is a variant of SEARCH with sorting semantics for + * the results. + * @return SortCommandArgumentsInterface + */ + public function sort(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/StartTlsCommandInterface.php b/src/Imap/CommandBuilder/Commands/StartTlsCommandInterface.php new file mode 100755 index 0000000..f33c5bb --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/StartTlsCommandInterface.php @@ -0,0 +1,31 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + + +/** + * Interface StartTlsCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface StartTlsCommandInterface +{ + +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/StatusCommandArgumentsInterface.php b/src/Imap/CommandBuilder/Commands/StatusCommandArgumentsInterface.php new file mode 100755 index 0000000..d6f8b97 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/StatusCommandArgumentsInterface.php @@ -0,0 +1,53 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface StatusCommandArgumentsInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface StatusCommandArgumentsInterface +{ + /** + * @return StatusCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function withMessages(); + + /** + * @return StatusCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function withRecent(); + + /** + * @return StatusCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function withUidNext(); + + /** + * @return StatusCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function withUidValidity(); + + /** + * @return StatusCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function withUnseen(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/StatusCommandInterface.php b/src/Imap/CommandBuilder/Commands/StatusCommandInterface.php new file mode 100755 index 0000000..6d5078a --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/StatusCommandInterface.php @@ -0,0 +1,34 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface StatusCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface StatusCommandInterface +{ + /** + * @param string $mailbox eg INBOX + * @return StatusCommandArgumentsInterface + */ + public function status($mailbox); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/StoreCommandArgumentsInterface.php b/src/Imap/CommandBuilder/Commands/StoreCommandArgumentsInterface.php new file mode 100755 index 0000000..e8f968a --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/StoreCommandArgumentsInterface.php @@ -0,0 +1,67 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface StoreCommandArgumentsInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface StoreCommandArgumentsInterface +{ + /** + * @param string $message + * @return StoreCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function withMessage($message); + + /** + * @param $messageFrom + * @param $messageTo + * @return StoreCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function withRange($messageFrom, $messageTo); + + /** + * Replace the flags for the message (other than \Recent) with the + * argument. The new value of the flags is returned as if a FETCH + * of those flags was done. + * @param string $flag + * @return StoreCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function replaceFlag($flag); + + /** + * Add the argument to the flags for the message. The new value + * of the flags is returned as if a FETCH of those flags was done. + * @param string $flag + * @return StoreCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function addFlag($flag); + + /** + * Remove the argument from the flags for the message. The new + * value of the flags is returned as if a FETCH of those flags was + * done. + * @param string $flag + * @return StoreCommandArgumentsInterface|\SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function removeFlag($flag); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/StoreCommandInterface.php b/src/Imap/CommandBuilder/Commands/StoreCommandInterface.php new file mode 100755 index 0000000..16df6fc --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/StoreCommandInterface.php @@ -0,0 +1,33 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface StoreCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface StoreCommandInterface +{ + /** + * @return StoreCommandArgumentsInterface + */ + public function store(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/SubscribeCommandInterface.php b/src/Imap/CommandBuilder/Commands/SubscribeCommandInterface.php new file mode 100755 index 0000000..8742004 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/SubscribeCommandInterface.php @@ -0,0 +1,37 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface SubscribeCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface SubscribeCommandInterface +{ + /** + * Adds the specified mailbox name to the + * server's set of "active" or "subscribed" mailboxes as returned by + * the LSUB command. + * @param string $mailbox eg. Important + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function subscribe($mailbox); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/UidCommandInterface.php b/src/Imap/CommandBuilder/Commands/UidCommandInterface.php new file mode 100755 index 0000000..55c0645 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/UidCommandInterface.php @@ -0,0 +1,40 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + + +/** + * Interface UidCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface UidCommandInterface +{ + /** + * The UID command has two forms. In the first form, it takes as its + * arguments a COPY, FETCH, or STORE command with arguments + * appropriate for the associated command. However, the numbers in + * the sequence set argument are unique identifiers instead of + * message sequence numbers. Sequence set ranges are permitted, but + * there is no guarantee that unique identifiers will be contiguous. + * @return FetchCommandInterface|StoreCommandInterface|CopyCommandInterface|SearchCommandInterface + */ + public function uid(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/UnsubscribeCommandInterface.php b/src/Imap/CommandBuilder/Commands/UnsubscribeCommandInterface.php new file mode 100755 index 0000000..d0da4ff --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/UnsubscribeCommandInterface.php @@ -0,0 +1,37 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface UnsubscribeCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + */ +interface UnsubscribeCommandInterface +{ + /** + * Removes the specified mailbox name to the + * server's set of "active" or "subscribed" mailboxes as returned by + * the LSUB command. + * @param string $mailbox eg. Important + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function unsubscribe($mailbox); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/Commands/XCommandInterface.php b/src/Imap/CommandBuilder/Commands/XCommandInterface.php new file mode 100755 index 0000000..d9f7190 --- /dev/null +++ b/src/Imap/CommandBuilder/Commands/XCommandInterface.php @@ -0,0 +1,38 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder\Commands; + +/** + * Interface XCommandInterface + * @package SalesAgility\Imap\CommandBuilder\Commands + * Interface for commands X- (IMAP extensions) + */ +interface XCommandInterface +{ + /** + * Any command prefixed with an X is an experimental command. + * Commands which are not part of this specification, a standard or + * standards-track revision of this specification, or an + * IESG-approved experimental protocol, MUST use the X prefix. + * @return \SalesAgility\Imap\CommandBuilder\CommandBuildInterface + */ + public function x(); +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/PhpImapExtensionCommandBuilder.php b/src/Imap/CommandBuilder/PhpImapExtensionCommandBuilder.php new file mode 100755 index 0000000..db8d66e --- /dev/null +++ b/src/Imap/CommandBuilder/PhpImapExtensionCommandBuilder.php @@ -0,0 +1,1358 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder; + + +use SalesAgility\Imap\CommandBuilder\CommandValidator\CommandValidationInterface; +use SalesAgility\Imap\CommandBuilder\CommandValidator\CommandValidatorAwareInterface; +use SalesAgility\Imap\Enumerator\Format; +use SalesAgility\Imap\Response\MessageList; +use SalesAgility\Pattern\Singleton; + +/** + * Class PhpImapExtensionCommandBuilder + * @package SalesAgility\Imap\CommandBuilder + */ +class PhpImapExtensionCommandBuilder implements Singleton, PhpImapExtensionSupportedCommandsInterface, CommandBuildInterface, CommandBuildArgumentsInterface, CommandValidatorAwareInterface +{ + private $commandPrefix = ''; + private $command = ''; + private $arguments = array(); + private $asString = ''; + private $isValidated = false; + /** @var CommandValidationInterface[] */ + private $validators = array(); + private $isRaw = false; + + /** + * @return PimapSupportedTopLevelCommandsInterface + */ + public static function instance() + { + return new self(); + } + + // Builder calls for Imap library use only + + /** + * {@inheritDoc} + */ + public function build() + { + switch ($this->command) { + case 'UID': + if (array_key_exists('FETCH', $this->arguments)) { + $this->buildFetch(); + } elseif (array_key_exists('SEARCH', $this->arguments)) { + $this->buildSearch(); + } elseif (array_key_exists('STORE', $this->arguments)) { + $this->buildStore(); + } elseif (array_key_exists('COPY', $this->arguments)) { + $this->buildCopy(); + } + break; + case 'FETCH': + $this->buildFetch(); + break; + case 'SEARCH': + $this->buildSearch(); + break; + case 'STORE': + $this->buildStore(); + break; + case 'COPY': + $this->buildCopy(); + break; + case 'STATUS': + $this->asString = $this->prefixWhitespace($this->command) + . $this->prefixWhitespace($this->arguments['MAILBOX']) + . $this->prefixWhitespace($this->buildGroup($this->arguments['INCLUDE'])); + break; + case 'APPEND': + $this->asString = $this->prefixWhitespace($this->command); + + if (!empty($this->arguments['FLAGS'])) { + $this->asString .= $this->prefixWhitespace($this->buildGroup($this->arguments['FLAGS'])); + } + + if (!empty($this->arguments['DATE'])) { + $this->asString .= $this->prefixWhitespace($this->arguments['DATE']); + } + + $this->asString .= $this->prefixWhitespace($this->arguments['MESSAGE']); + break; + case 'LIST': + $this->asString = $this->prefixWhitespace($this->command) + . $this->prefixWhitespace('"' . $this->arguments['REFERENCE_NAME'] . '"') + . $this->prefixWhitespace('"' . $this->arguments['MAILBOX'] . '"'); + break; + case 'LSUB': + $this->asString = $this->prefixWhitespace($this->command) + . $this->prefixWhitespace('"' . $this->arguments['REFERENCE_NAME'] . '"') + . $this->prefixWhitespace('"' . $this->arguments['MAILBOX'] . '"'); + break; + case 'SELECT': + $this->asString = $this->prefixWhitespace($this->command) + . $this->prefixWhitespace('"' . $this->arguments['MAILBOX'] . '"'); + break; + default: + break; + } + + if (array_key_exists($this->command, $this->validators)) { + $this->isValidated = $this->validators[$this->command]->validate($this); + } + + return $this; + } + + /** + * {@inheritDoc} + */ + public function command() + { + return $this->command; + } + + /** + * {@inheritDoc} + */ + public function commandPrefix() + { + return $this->commandPrefix; + } + + /** + * {@inheritDoc} + */ + public function commandArguments() + { + return $this->arguments; + } + + /** + * {@inheritDoc} + */ + public function tagged($tag) + { + $this->commandPrefix = $tag; + return $this; + } + + /** + * {@inheritDoc} + */ + public function untagged() + { + $this->commandPrefix = '*'; + return $this; + } + + /** + * {@inheritDoc} + */ + public function asString() + { + return $this->commandPrefix . $this->asString; + } + + /** + * {@inheritDoc} + */ + public function asArray() + { + return array( + 'command' => $this->command, + 'argument' => $this->arguments, + 'validated' => $this->isValidated, + 'raw' => $this->isRaw, + ); + } + + // Commands For Library Consumer + + /** + * {@inheritDoc} + */ + public function raw($command) + { + $this->arguments = array(); + $this->command = $command; + $this->asString = $this->prefixWhitespace($this->command); + $this->isRaw = true; + return $this; + } + + /** + * {@inheritDoc} + */ + public function logout() + { + $this->command = 'LOGOUT'; + $this->arguments = array(); + $this->isValidated = false; + $this->asString = $this->prefixWhitespace($this->command); + return $this; + } + + /** + * {@inheritDoc} + */ + public function noop() + { + $this->command = 'NOOP'; + $this->arguments = array(); + $this->isValidated = false; + $this->asString = $this->prefixWhitespace($this->command); + return $this; + } + + /** + * {@inheritDoc} + */ + public function login() + { + $this->command = 'LOGIN'; + $this->arguments = array( + 'USER' => '', + 'PASSWORD' => '', + ); + $this->isValidated = false; + $this->asString .= $this->prefixWhitespace($this->command); + return $this; + } + + /** + * {@inheritDoc} + */ + public function user($user) + { + $this->arguments['USER'] = $user; + $this->asString .= $this->prefixWhitespace($user); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function password($password) + { + $this->arguments['PASSWORD'] = $password; + $this->asString .= $this->prefixWhitespace($password); + return $this; + } + + /** + * {@inheritDoc} + */ + public function select($mailbox) + { + $this->command = 'SELECT'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function examine($mailbox) + { + $this->command = 'EXAMINE'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function create($mailbox) + { + $this->command = 'CREATE'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function delete($mailbox) + { + $this->command = 'DELETE'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function subscribe($mailbox) + { + $this->command = 'SUBSCRIBE'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function unsubscribe($mailbox) + { + $this->command = 'UNSUBSCRIBE'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function listMailbox($reference = "", $mailbox = "*") + { + $this->command = 'LIST'; + $this->arguments = array( + 'REFERENCE_NAME' => $reference, + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($reference); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function listSubsetMailbox($reference = "", $mailbox = "*") + { + $this->command = 'LSUB'; + $this->arguments = array( + 'REFERENCE_NAME' => $reference, + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($reference); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function rename($mailbox, $newMailbox) + { + $this->command = 'RENAME'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + 'NEW_MAILBOX' => $newMailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + $this->asString .= $this->prefixWhitespace($newMailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function close() + { + $command = 'CLOSE'; + $this->command = $command; + $this->asString .= $this->prefixWhitespace($command); + $this->arguments = array(); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function expunge() + { + $command = 'EXPUNGE'; + $this->command = $command; + $this->asString .= $this->prefixWhitespace($command); + $this->arguments = array(); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function append($mailbox) + { + $command = 'APPEND'; + $this->command = $command; + $this->asString .= $this->prefixWhitespace($command); + $this->arguments = array( + 'MAILBOX' => $mailbox, + 'FLAGS' => array(), + 'DATE' => '', + 'MESSAGE' => '{0}' . "\r\n\r\n" + ); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withFlag($flag) + { + $this->arguments['FLAGS'][] = $flag; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withDateTime(\DateTimeImmutable $datetime) + { + $this->arguments['DATE'] = $datetime->format(Format::RFC2822_DATE); + return $this; + } + + /** + * {@inheritDoc} + */ + public function withMessage($message) + { + $this->arguments['MESSAGE'] = $message; + return $this; + } + + + /** + * {@inheritDoc} + */ + public function check() + { + $this->command = 'CHECK'; + $this->asString .= $this->prefixWhitespace($this->command); + $this->arguments = array(); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function copy() + { + $command = 'COPY'; + $arguments = array( + 'MESSAGE' => '', + 'MAILBOX' => '' + ); + + if ($this->command === 'UID') { + $this->arguments[$command] = $arguments; + } else { + $this->command = 'COPY'; + $this->arguments = $arguments; + } + + $this->asString .= $this->prefixWhitespace($command); + + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withRange($messageFrom, $messageTo) + { + + $arguments = $messageFrom . ':' . $messageTo; + + if ($this->command === 'UID') { + reset($this->arguments); + $command = key($this->arguments); + if ($command === 'SEARCH') { + $this->arguments[$command][] = $arguments; + } else { + $this->arguments[$command]['MESSAGE'] = $arguments; + } + } else { + if ($this->command === 'SEARCH') { + $this->arguments[] = $arguments; + } else { + $this->arguments['MESSAGE'] = $arguments; + } + } + + $this->asString .= $this->prefixWhitespace($this->command); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function toMailbox($mailbox) + { + $command = 'COPY'; + $arguments = $mailbox; + + if ($this->command === 'UID') { + $this->arguments[$command]['MAILBOX'] = $arguments; + } else { + $this->arguments['MAILBOX'] = $arguments; + } + + $this->asString .= $this->prefixWhitespace($this->command); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function search() + { + $command = 'SEARCH'; + + $this->asString .= $this->prefixWhitespace($command); + $this->arguments = array(); + $this->isValidated = false; + + if ($this->command === 'UID') { + $this->arguments[$command] = array(); + } else { + $this->command = $command; + } + + return $this; + } + + + /** + * {@inheritDoc} + */ + public function withSequence(MessageList $messages) + { + $this->buildSearchArgument('SEARCH', $this->buildSequence($messages, 'SEARCH')); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchAll() + { + $this->buildSearchArgument('SEARCH', 'ALL'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchAnswered() + { + $this->buildSearchArgument('SEARCH', 'ANSWERED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchBcc($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'BCC', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchBefore(\DateTimeImmutable $dateTime) + { + $comparator = $dateTime->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'BEFORE', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchBody($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'BODY', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchCc($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'CC', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchDeleted() + { + $this->buildSearchArgument('SEARCH', 'DELETED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchDraft() + { + $this->buildSearchArgument('SEARCH', 'DRAFT'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchFlagged() + { + $this->buildSearchArgument('SEARCH', 'FLAGGED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchFrom($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'FROM', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchHeader($name, $string) + { + $comparator = $name . ':' . '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'HEADER', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchKeyword($keyword) + { + $comparator = '"' . $keyword . '"'; + $this->buildSearchArgument('SEARCH', 'KEYWORD', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchLarger($n) + { + $this->buildSearchArgument('SEARCH', 'LARGER', $n); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchNew() + { + $this->buildSearchArgument('SEARCH', 'NEW'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchNot() + { + $this->buildSearchArgument('SEARCH', 'NOT'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchOld() + { + $this->buildSearchArgument('SEARCH', 'OLD'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchOn(\DateTimeImmutable $date) + { + $comparator = $date->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'ON', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchOr() + { + $this->buildSearchArgument('SEARCH', 'OR'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchRecent() + { + $this->buildSearchArgument('SEARCH', 'RECENT'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSeen() + { + $this->buildSearchArgument('SEARCH', 'SEEN'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSentBefore(\DateTimeImmutable $date) + { + $comparator = $date->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'SENTBEFORE', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSentOn(\DateTimeImmutable $date) + { + $comparator = $date->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'SENTON', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSentSince(\DateTimeImmutable $date) + { + $comparator = $date->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'SENTSINCE', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSince(\DateTimeImmutable $date) + { + $comparator = $date->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'SINCE', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSmaller($n) + { + $this->buildSearchArgument('SEARCH', 'SMALLER', $n); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSubject($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'SUBJECT', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchText($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'TEXT', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchTo($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'TEXT', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUid($string) + { + $comparator = $string; + $this->buildSearchArgument('SEARCH', 'UID', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUnanswered() + { + $this->buildSearchArgument('SEARCH', 'UNANSWERED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUndeleted() + { + $this->buildSearchArgument('SEARCH', 'UNDELETED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUndraft() + { + $this->buildSearchArgument('SEARCH', 'UNDRAFT'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUnflagged() + { + $this->buildSearchArgument('SEARCH', 'UNFLAGGED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUnkeyword($keyword) + { + $comparator = '"' . $keyword . '"'; + $this->buildSearchArgument('SEARCH', 'UNKEYWORD', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUnseen() + { + $this->buildSearchArgument('SEARCH', 'UNSEEN'); + return $this; + + } + + /** + * {@inheritDoc} + */ + public function status($mailbox) + { + $command = 'STATUS'; + $this->command = $command; + $this->asString .= $this->prefixWhitespace($command); + $this->arguments = array('MAILBOX' => $mailbox, 'INCLUDE' => array()); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withMessages() + { + $this->arguments['INCLUDE'][] = 'MESSAGES'; + return $this; + + } + + /** + * {@inheritDoc} + */ + public function withRecent() + { + $this->arguments['INCLUDE'][] = 'RECENT'; + return $this; + + } + + /** + * {@inheritDoc} + */ + public function withUidNext() + { + $this->arguments['INCLUDE'][] = 'UIDNEXT'; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withUidValidity() + { + $this->arguments['INCLUDE'][] = 'UIDVALIDITY'; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withUnseen() + { + $this->arguments['INCLUDE'][] = 'UNSEEN'; + return $this; + } + + + /** + * {@inheritDoc} + */ + public function store() + { + $command = 'STORE'; + $this->asString .= $this->prefixWhitespace($command); + $arguments = array( + 'FLAGS' => array(), + '+FLAGS' => array(), + '-FLAGS' => array() + + ); + + if ($this->command === 'UID') { + $this->arguments['STORE'] = $arguments; + } else { + $this->command = $command; + } + + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function replaceFlag($flag) + { + if ($this->command === 'UID') { + $this->arguments['STORE']['FLAGS'][] = '\\' . $flag; + } else { + $this->arguments['FLAGS'][] = '\\' . $flag; + } + return $this; + } + + /** + * {@inheritDoc} + */ + public function addFlag($flag) + { + if ($this->command === 'UID') { + $this->arguments['STORE']['+FLAGS'][] = '\\' . $flag; + } else { + $this->arguments['+FLAGS'][] = '\\' . $flag; + } + return $this; + } + + /** + * {@inheritDoc} + */ + public function removeFlag($flag) + { + if ($this->command === 'UID') { + $this->arguments['STORE']['-FLAGS'][] = '\\' . $flag; + } else { + $this->arguments['-FLAGS'][] = '\\' . $flag; + } + return $this; + } + + + /** + * {@inheritDoc} + */ + public function flags() + { + $command = 'FETCH'; + $argument = 'FLAGS'; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments[$command]['FIELDS'][] = $argument; + } else { + // fetch is a command + $this->arguments['FIELDS'][] = $argument; + } + + $this->asString .= $this->prefixWhitespace($argument); + + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function addCommandValidator(CommandValidationInterface $commandValidation) + { + $this->validators[$commandValidation->command()] = $commandValidation; + } + + /** + * {@inheritDoc} + */ + public function uid() + { + $this->command = 'UID'; + $this->arguments = array(); + $this->isValidated = false; + $this->asString = $this->prefixWhitespace($this->command); + return $this; + } + + /** + * {@inheritDoc} + */ + public function fetch($message) + { + $command = 'FETCH'; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments = array($command => array('MESSAGE' => $message)); + } else { + // fetch is a command + $this->command = $command; + $this->arguments = array('MESSAGE' => $message); + } + + $this->isValidated = false; + $this->asString .= $this->prefixWhitespace($command); + $this->asString .= $this->prefixWhitespace($message); + return $this; + } + + /** + * {@inheritDoc} + */ + public function fetchRange($messageFrom, $messageTo) + { + $command = 'FETCH'; + $message = $messageFrom . ':' . $messageTo; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments = array($command => array('MESSAGE' => $message)); + } else { + // fetch is a command + $this->command = $command; + $this->arguments = array('MESSAGE' => $message); + } + + $this->isValidated = false; + $this->asString .= $this->prefixWhitespace($command); + $this->asString .= $this->prefixWhitespace($message); + return $this; + } + + /** + * {@inheritDoc} + */ + public function fetchSet(MessageList $messages) + { + $this->buildSet($messages, 'FETCH'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function header() + { + $command = 'FETCH'; + $argument = 'BODY[HEADER] BODYSTRUCTURE'; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments[$command]['FIELDS'][] = $argument; + } else { + // fetch is a command + $this->arguments['FIELDS'][] = $argument; + } + $this->asString .= $this->prefixWhitespace($argument); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function body() + { + $command = 'FETCH'; + $argument = 'BODY[TEXT]'; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments[$command]['FIELDS'][] = $argument; + } else { + // fetch is a command + $this->arguments['FIELDS'][] = $argument; + } + + $this->asString .= $this->prefixWhitespace($argument); + + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function uids() + { + $command = 'FETCH'; + $argument = 'UID'; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments[$command]['FIELDS'][] = $argument; + } else { + // fetch is a command + $this->arguments['FIELDS'][] = $argument; + } + + $this->asString .= $this->prefixWhitespace($argument); + + $this->isValidated = false; + return $this; + } + + /** + * add white space to the beginning of string + * @param string $string + * @return string + */ + private function prefixWhitespace($string) + { + return "\x20" . $string; + } + + private function buildSet(MessageList $messages, $command) + { + $messageArray = array(); + if ($this->command === 'UID') { + // fetch is an argument + + foreach ($messages as $message) { + $messageArray[] = $message->uid(); + } + $messageNumbers = implode(',', $messageArray); + $this->arguments = array($command => array('MESSAGE' => $messageNumbers)); + } else { + // fetch is a command + foreach ($messages as $message) { + $messageArray[] = $message->number(); + } + $messageNumbers = implode(',', $messageArray); + $this->command = $command; + $this->arguments = array('MESSAGE' => $messageNumbers); + } + + $this->isValidated = false; + $this->asString .= $this->prefixWhitespace($command); + $this->asString .= $this->prefixWhitespace($messageNumbers); + } + + private function buildSequence(MessageList $messages, $command) + { + $messageArray = array(); + foreach ($messages as $message) { + if ($this->command === 'UID') { + $messageArray[] = $message->uid(); + } else { + $messageArray[] = $message->number(); + } + } + return implode(' ', $messageArray); + } + + /** + * Used to keep this class DRY + * @param string $command + * @param string $argument + * @param null|string $comparator + */ + private function buildSearchArgument($command, $argument, $comparator = null) + { + $this->asString .= $this->prefixWhitespace($argument); + if ($comparator !== null) { + $this->asString .= $this->prefixWhitespace($comparator); + } + + if ($this->command === 'UID') { + $this->arguments[$command][] = $argument; + if ($comparator !== null) { + $this->arguments[$command][] = $comparator; + } + } else { + $this->arguments[] = $argument; + if ($comparator !== null) { + $this->arguments[] = $comparator; + } + } + } + + private function buildGroup(array $haystack) + { + return '(' . implode("\x20", $haystack) . ')'; + } + + private function buildStore() + { + $command = 'STORE'; + $this->asString = $this->prefixWhitespace($this->command); + + if ($this->command === 'UID') { + $arguments = $this->arguments[$command]; + $this->asString .= $this->prefixWhitespace($command); + } else { + $arguments = $this->arguments; + $this->command = $command; + } + + if (array_key_exists('MESSAGE', $arguments)) { + $this->asString .= $this->prefixWhitespace($arguments['MESSAGE']); + } + + if (!empty($arguments['+FLAGS'])) { + $this->asString .= $this->prefixWhitespace('+FLAGS.SILENT'); + $this->asString .= $this->prefixWhitespace($this->buildGroup($arguments['+FLAGS'])); + } + + if (!empty($arguments['-FLAGS'])) { + $this->asString .= $this->prefixWhitespace('-FLAGS.SILENT'); + $this->asString .= $this->prefixWhitespace($this->buildGroup($arguments['-FLAGS'])); + } + if (!empty($arguments['FLAGS'])) { + $this->asString .= $this->prefixWhitespace('FLAGS.SILENT'); + $this->asString .= $this->prefixWhitespace($this->buildGroup($arguments['FLAGS'])); + } + } + + private function buildFetch() + { + $command = 'FETCH'; + $this->asString = $this->prefixWhitespace($this->command); + + if ($this->command === 'UID') { + $arguments = $this->arguments[$command]; + $this->asString .= $this->prefixWhitespace($command); + } else { + $arguments = $this->arguments; + } + $this->asString .= $this->prefixWhitespace($arguments['MESSAGE']) + . $this->prefixWhitespace($this->buildGroup($arguments['FIELDS'])); + } + + private function buildSearch() + { + $command = 'SEARCH'; + $this->asString = $this->prefixWhitespace($this->command); + + if ($this->command === 'UID') { + $arguments = $this->arguments[$command]; + $this->asString .= $this->prefixWhitespace($command); + } else { + $arguments = $this->arguments; + } + + foreach ($arguments as $argumentKey => $argument) { + $this->asString .= $this->prefixWhitespace($argument); + } + } + + + private function buildCopy() + { + $command = 'COPY'; + $this->asString = $this->prefixWhitespace($this->command); + + if ($this->command === 'UID') { + $arguments = $this->arguments[$command]; + $this->asString .= $this->prefixWhitespace($command); + $this->asString = $this->prefixWhitespace($this->command) + . $this->prefixWhitespace($command) + . $this->prefixWhitespace($arguments['MESSAGE']) + . $this->prefixWhitespace($arguments['MAILBOX']); + } else { + $arguments = $this->arguments; + $this->asString = $this->prefixWhitespace($command) + . $this->prefixWhitespace($arguments['MESSAGE']) + . $this->prefixWhitespace($arguments['MAILBOX']); + } + } +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/PhpImapExtensionSupportedCommandsInterface.php b/src/Imap/CommandBuilder/PhpImapExtensionSupportedCommandsInterface.php new file mode 100755 index 0000000..4c32a2a --- /dev/null +++ b/src/Imap/CommandBuilder/PhpImapExtensionSupportedCommandsInterface.php @@ -0,0 +1,37 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder; + + +/** + * Interface PhpImapExtensionSupportedCommandsInterface + * @package SalesAgility\Imap\CommandBuilder + */ +interface PhpImapExtensionSupportedCommandsInterface extends + Commands\FetchCommandInterface, + Commands\FetchCommandArgumentsInterface, + Commands\LoginCommandInterface, + Commands\LoginCommandPasswordArgumentInterface, + Commands\SelectCommandInterface, + Commands\UidCommandInterface, + Commands\NoopCommandInterface +{ +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/PhpImapExtensionSupportedTopLevelCommandsInterface.php b/src/Imap/CommandBuilder/PhpImapExtensionSupportedTopLevelCommandsInterface.php new file mode 100755 index 0000000..ef6252a --- /dev/null +++ b/src/Imap/CommandBuilder/PhpImapExtensionSupportedTopLevelCommandsInterface.php @@ -0,0 +1,54 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder; + + +/** + * Interface PhpImapExtensionSupportedCommandsInterface + * @package SalesAgility\Imap\CommandBuilder + * To help the IDE to hint the correct commands + */ +interface PhpImapExtensionSupportedTopLevelCommandsInterface extends + CommandRawInterface, + Commands\AppendCommandInterface, + Commands\CheckCommandInterface, + Commands\CloseCommandInterface, + Commands\CopyCommandInterface, + Commands\CreateCommandInterface, + Commands\DeleteCommandInterface, + Commands\ExamineCommandInterface, + Commands\ExpungeCommandInterface, + Commands\FetchCommandInterface, + Commands\ListCommandInterface, + Commands\LoginCommandInterface, + Commands\LogoutCommandInterface, + Commands\LSubCommandInterface, + Commands\NoopCommandInterface, + Commands\RenameCommandInterface, + Commands\SearchCommandInterface, + Commands\SelectCommandInterface, + Commands\StatusCommandInterface, + Commands\StoreCommandInterface, + Commands\SubscribeCommandInterface, + Commands\UidCommandInterface, + Commands\UnsubscribeCommandInterface +{ +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/PimapCommandBuilder.php b/src/Imap/CommandBuilder/PimapCommandBuilder.php new file mode 100755 index 0000000..70deee1 --- /dev/null +++ b/src/Imap/CommandBuilder/PimapCommandBuilder.php @@ -0,0 +1,1382 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder; + +use SalesAgility\Imap\CommandBuilder\CommandValidator\CommandValidationInterface; +use SalesAgility\Imap\CommandBuilder\CommandValidator\CommandValidatorAwareInterface; +use SalesAgility\Imap\Enumerator\Format; +use SalesAgility\Imap\Response\MessageList; +use SalesAgility\Pattern\Singleton; + +/** + * Class PimapCommandBuilder + * @package SalesAgility\Imap\CommandBuilder + */ +class PimapCommandBuilder implements Singleton, PimapSupportedTopLevelCommandsInterface, PimapSupportedCommandsInterface, CommandBuildInterface, CommandBuildArgumentsInterface, CommandValidatorAwareInterface +{ + private $commandPrefix = ''; + private $command = ''; + private $arguments = array(); + private $asString = ''; + private $isValidated = false; + /** @var CommandValidationInterface[] */ + private $validators = array(); + private $isRaw = false; + + /** + * @return PimapSupportedTopLevelCommandsInterface + */ + public static function instance() + { + return new self(); + } + + // Builder calls for Imap library use only + + /** + * {@inheritDoc} + */ + public function build() + { + switch ($this->command) { + case 'UID': + if (array_key_exists('FETCH', $this->arguments)) { + $this->buildFetch(); + } elseif (array_key_exists('SEARCH', $this->arguments)) { + $this->buildSearch(); + } elseif (array_key_exists('STORE', $this->arguments)) { + $this->buildStore(); + } elseif (array_key_exists('COPY', $this->arguments)) { + $this->buildCopy(); + } + break; + case 'FETCH': + $this->buildFetch(); + break; + case 'SEARCH': + $this->buildSearch(); + break; + case 'STORE': + $this->buildStore(); + break; + case 'COPY': + $this->buildCopy(); + break; + case 'STATUS': + $this->asString = $this->prefixWhitespace($this->command) + . $this->prefixWhitespace($this->arguments['MAILBOX']) + . $this->prefixWhitespace($this->buildGroup($this->arguments['INCLUDE'])); + break; + case 'APPEND': + $this->asString = $this->prefixWhitespace($this->command); + + if (!empty($this->arguments['FLAGS'])) { + $this->asString .= $this->prefixWhitespace($this->buildGroup($this->arguments['FLAGS'])); + } + + if (!empty($this->arguments['DATE'])) { + $this->asString .= $this->prefixWhitespace($this->arguments['DATE']); + } + + $this->asString .= $this->prefixWhitespace($this->arguments['MESSAGE']); + break; + case 'LIST': + $this->asString = $this->prefixWhitespace($this->command) + . $this->prefixWhitespace('"' . $this->arguments['REFERENCE_NAME'] . '"') + . $this->prefixWhitespace('"' . $this->arguments['MAILBOX'] . '"'); + break; + case 'LSUB': + $this->asString = $this->prefixWhitespace($this->command) + . $this->prefixWhitespace('"' . $this->arguments['REFERENCE_NAME'] . '"') + . $this->prefixWhitespace('"' . $this->arguments['MAILBOX'] . '"'); + break; + case 'SELECT': + $this->asString = $this->prefixWhitespace($this->command) + . $this->prefixWhitespace('"' . $this->arguments['MAILBOX'] . '"'); + break; + default: + break; + } + + if (array_key_exists($this->command, $this->validators)) { + $this->isValidated = $this->validators[$this->command]->validate($this); + } + + return $this; + } + + /** + * {@inheritDoc} + */ + public function command() + { + return $this->command; + } + + /** + * {@inheritDoc} + */ + public function commandPrefix() + { + return $this->commandPrefix; + } + + /** + * {@inheritDoc} + */ + public function commandArguments() + { + return $this->arguments; + } + + /** + * {@inheritDoc} + */ + public function tagged($tag) + { + $this->commandPrefix = $tag; + return $this; + } + + /** + * {@inheritDoc} + */ + public function untagged() + { + $this->commandPrefix = '*'; + return $this; + } + + /** + * {@inheritDoc} + */ + public function asString() + { + return $this->commandPrefix . $this->asString; + } + + /** + * {@inheritDoc} + */ + public function asArray() + { + return array( + 'command' => $this->command, + 'argument' => $this->arguments, + 'validated' => $this->isValidated, + 'raw' => $this->isRaw, + ); + } + + // Commands For Library Consumer + + /** + * {@inheritDoc} + */ + public function raw($command) + { + $this->arguments = array(); + $this->command = $command; + $this->asString = $this->prefixWhitespace($this->command); + $this->isRaw = true; + return $this; + } + + /** + * {@inheritDoc} + */ + public function logout() + { + $this->command = 'LOGOUT'; + $this->arguments = array(); + $this->isValidated = false; + $this->asString = $this->prefixWhitespace($this->command); + return $this; + } + + /** + * {@inheritDoc} + */ + public function noop() + { + $this->command = 'NOOP'; + $this->arguments = array(); + $this->isValidated = false; + $this->asString = $this->prefixWhitespace($this->command); + return $this; + } + + /** + * {@inheritDoc} + */ + public function login() + { + $this->command = 'LOGIN'; + $this->arguments = array( + 'USER' => '', + 'PASSWORD' => '', + ); + $this->isValidated = false; + $this->asString .= $this->prefixWhitespace($this->command); + return $this; + } + + /** + * {@inheritDoc} + */ + public function user($user) + { + $this->arguments['USER'] = $user; + $this->asString .= $this->prefixWhitespace($user); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function password($password) + { + $this->arguments['PASSWORD'] = $password; + $this->asString .= $this->prefixWhitespace($password); + return $this; + } + + /** + * {@inheritDoc} + */ + public function select($mailbox) + { + $this->command = 'SELECT'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function examine($mailbox) + { + $this->command = 'EXAMINE'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function create($mailbox) + { + $this->command = 'CREATE'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function delete($mailbox) + { + $this->command = 'DELETE'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function subscribe($mailbox) + { + $this->command = 'SUBSCRIBE'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function unsubscribe($mailbox) + { + $this->command = 'UNSUBSCRIBE'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function listMailbox($reference = "", $mailbox = "*") + { + $this->command = 'LIST'; + $this->arguments = array( + 'REFERENCE_NAME' => $reference, + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($reference); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function listSubsetMailbox($reference = "", $mailbox = "*") + { + $this->command = 'LSUB'; + $this->arguments = array( + 'REFERENCE_NAME' => $reference, + 'MAILBOX' => $mailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($reference); + $this->asString .= $this->prefixWhitespace($mailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function rename($mailbox, $newMailbox) + { + $this->command = 'RENAME'; + $this->arguments = array( + 'MAILBOX' => $mailbox, + 'NEW_MAILBOX' => $newMailbox, + ); + $this->asString .= $this->prefixWhitespace($this->command); + $this->asString .= $this->prefixWhitespace($mailbox); + $this->asString .= $this->prefixWhitespace($newMailbox); + return $this; + } + + /** + * {@inheritDoc} + */ + public function capability() + { + $this->command = 'CAPABILITY'; + $this->asString .= $this->prefixWhitespace($this->command); + $this->arguments = array(); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function close() + { + $command = 'CLOSE'; + $this->command = $command; + $this->asString .= $this->prefixWhitespace($command); + $this->arguments = array(); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function expunge() + { + $command = 'EXPUNGE'; + $this->command = $command; + $this->asString .= $this->prefixWhitespace($command); + $this->arguments = array(); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function append($mailbox) + { + $command = 'APPEND'; + $this->command = $command; + $this->asString .= $this->prefixWhitespace($command); + $this->arguments = array( + 'MAILBOX' => $mailbox, + 'FLAGS' => array(), + 'DATE' => '', + 'MESSAGE' => '{0}' . "\r\n\r\n" + ); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withFlag($flag) + { + $this->arguments['FLAGS'][] = $flag; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withDateTime(\DateTimeImmutable $datetime) + { + $this->arguments['DATE'] = $datetime->format(Format::RFC2822_DATE); + return $this; + } + + /** + * {@inheritDoc} + */ + public function withMessage($message) + { + $this->arguments['MESSAGE'] = $message; + return $this; + } + + + /** + * {@inheritDoc} + */ + public function check() + { + $this->command = 'CHECK'; + $this->asString .= $this->prefixWhitespace($this->command); + $this->arguments = array(); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function copy() + { + $command = 'COPY'; + $arguments = array( + 'MESSAGE' => '', + 'MAILBOX' => '' + ); + + if ($this->command === 'UID') { + $this->arguments[$command] = $arguments; + } else { + $this->command = 'COPY'; + $this->arguments = $arguments; + } + $this->asString .= $this->prefixWhitespace($command); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withRange($messageFrom, $messageTo) + { + $arguments = $messageFrom . ':' . $messageTo; + if ($this->command === 'UID') { + reset($this->arguments); + $command = key($this->arguments); + if ($command === 'SEARCH') { + $this->arguments[$command][] = $arguments; + } else { + $this->arguments[$command]['MESSAGE'] = $arguments; + } + } else { + if ($this->command === 'SEARCH') { + $this->arguments[] = $arguments; + } else { + $this->arguments['MESSAGE'] = $arguments; + } + } + + $this->asString .= $this->prefixWhitespace($this->command); + return $this; + } + + /** + * {@inheritDoc} + */ + public function toMailbox($mailbox) + { + $command = 'COPY'; + $arguments = $mailbox; + + if ($this->command === 'UID') { + $this->arguments[$command]['MAILBOX'] = $arguments; + } else { + $this->arguments['MAILBOX'] = $arguments; + } + + $this->asString .= $this->prefixWhitespace($this->command); + return $this; + } + + /** + * {@inheritDoc} + */ + public function search() + { + $command = 'SEARCH'; + + $this->asString .= $this->prefixWhitespace($command); + $this->arguments = array(); + $this->isValidated = false; + + if ($this->command === 'UID') { + $this->arguments[$command] = array(); + } else { + $this->command = $command; + } + + return $this; + } + + + /** + * {@inheritDoc} + */ + public function withSequence(MessageList $messages) + { + $this->buildSearchArgument('SEARCH', $this->buildSequence($messages, 'SEARCH')); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchAll() + { + $this->buildSearchArgument('SEARCH', 'ALL'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchAnswered() + { + $this->buildSearchArgument('SEARCH', 'ANSWERED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchBcc($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'BCC', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchBefore(\DateTimeImmutable $dateTime) + { + $comparator = $dateTime->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'BEFORE', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchBody($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'BODY', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchCc($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'CC', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchDeleted() + { + $this->buildSearchArgument('SEARCH', 'DELETED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchDraft() + { + $this->buildSearchArgument('SEARCH', 'DRAFT'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchFlagged() + { + $this->buildSearchArgument('SEARCH', 'FLAGGED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchFrom($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'FROM', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchHeader($name, $string) + { + $comparator = $name . ':' . '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'HEADER', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchKeyword($keyword) + { + $comparator = '"' . $keyword . '"'; + $this->buildSearchArgument('SEARCH', 'KEYWORD', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchLarger($n) + { + $this->buildSearchArgument('SEARCH', 'LARGER', $n); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchNew() + { + $this->buildSearchArgument('SEARCH', 'NEW'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchNot() + { + $this->buildSearchArgument('SEARCH', 'NOT'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchOld() + { + $this->buildSearchArgument('SEARCH', 'OLD'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchOn(\DateTimeImmutable $date) + { + $comparator = $date->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'ON', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchOr() + { + $this->buildSearchArgument('SEARCH', 'OR'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchRecent() + { + $this->buildSearchArgument('SEARCH', 'RECENT'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSeen() + { + $this->buildSearchArgument('SEARCH', 'SEEN'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSentBefore(\DateTimeImmutable $date) + { + $comparator = $date->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'SENTBEFORE', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSentOn(\DateTimeImmutable $date) + { + $comparator = $date->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'SENTON', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSentSince(\DateTimeImmutable $date) + { + $comparator = $date->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'SENTSINCE', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSince(\DateTimeImmutable $date) + { + $comparator = $date->format(Format::RFC2822_DATE_DAY_MONTH_YEAR); + $this->buildSearchArgument('SEARCH', 'SINCE', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSmaller($n) + { + $this->buildSearchArgument('SEARCH', 'SMALLER', $n); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchSubject($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'SUBJECT', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchText($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'TEXT', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchTo($string) + { + $comparator = '"' . $string . '"'; + $this->buildSearchArgument('SEARCH', 'TEXT', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUid($string) + { + $comparator = $string; + $this->buildSearchArgument('SEARCH', 'UID', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUnanswered() + { + $this->buildSearchArgument('SEARCH', 'UNANSWERED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUndeleted() + { + $this->buildSearchArgument('SEARCH', 'UNDELETED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUndraft() + { + $this->buildSearchArgument('SEARCH', 'UNDRAFT'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUnflagged() + { + $this->buildSearchArgument('SEARCH', 'UNFLAGGED'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUnkeyword($keyword) + { + $comparator = '"' . $keyword . '"'; + $this->buildSearchArgument('SEARCH', 'UNKEYWORD', $comparator); + return $this; + } + + /** + * {@inheritDoc} + */ + public function searchUnseen() + { + $this->buildSearchArgument('SEARCH', 'UNSEEN'); + return $this; + + } + + /** + * {@inheritDoc} + */ + public function status($mailbox) + { + $command = 'STATUS'; + $this->command = $command; + $this->asString .= $this->prefixWhitespace($command); + $this->arguments = array('MAILBOX' => $mailbox, 'INCLUDE' => array()); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withMessages() + { + $this->arguments['INCLUDE'][] = 'MESSAGES'; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withRecent() + { + $this->arguments['INCLUDE'][] = 'RECENT'; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withUidNext() + { + $this->arguments['INCLUDE'][] = 'UIDNEXT'; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withUidValidity() + { + $this->arguments['INCLUDE'][] = 'UIDVALIDITY'; + return $this; + } + + /** + * {@inheritDoc} + */ + public function withUnseen() + { + $this->arguments['INCLUDE'][] = 'UNSEEN'; + return $this; + } + + + /** + * {@inheritDoc} + */ + public function store() + { + $command = 'STORE'; + $this->asString .= $this->prefixWhitespace($command); + $arguments = array( + 'FLAGS' => array(), + '+FLAGS' => array(), + '-FLAGS' => array() + + ); + if ($this->command === 'UID') { + $this->arguments['STORE'] = $arguments; + } else { + $this->command = $command; + } + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function replaceFlag($flag) + { + if ($this->command === 'UID') { + $this->arguments['STORE']['FLAGS'][] = '\\' . $flag; + } else { + $this->arguments['FLAGS'][] = '\\' . $flag; + } + return $this; + } + + /** + * {@inheritDoc} + */ + public function addFlag($flag) + { + if ($this->command === 'UID') { + $this->arguments['STORE']['+FLAGS'][] = '\\' . $flag; + } else { + $this->arguments['+FLAGS'][] = '\\' . $flag; + } + + return $this; + } + + /** + * {@inheritDoc} + */ + public function removeFlag($flag) + { + if ($this->command === 'UID') { + $this->arguments['STORE']['-FLAGS'][] = '\\' . $flag; + } else { + $this->arguments['-FLAGS'][] = '\\' . $flag; + } + return $this; + } + + + /** + * {@inheritDoc} + */ + public function flags() + { + $command = 'FETCH'; + $argument = 'FLAGS'; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments[$command]['FIELDS'][] = $argument; + } else { + // fetch is a command + $this->arguments['FIELDS'][] = $argument; + } + + $this->asString .= $this->prefixWhitespace($argument); + + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function idle() + { + $command = 'IDLE'; + $this->command = $command; + $this->asString .= $this->prefixWhitespace($command); + $this->arguments = array(); + $this->isValidated = true; + return $this; + } + + /** + * {@inheritDoc} + */ + public function x() + { + return $this; + } + + /** + * {@inheritDoc} + */ + public function addCommandValidator(CommandValidationInterface $commandValidation) + { + $this->validators[$commandValidation->command()] = $commandValidation; + } + + /** + * {@inheritDoc} + */ + public function uid() + { + $this->command = 'UID'; + $this->arguments = array(); + $this->isValidated = false; + $this->asString = $this->prefixWhitespace($this->command); + return $this; + } + + /** + * {@inheritDoc} + */ + public function fetch($message) + { + $command = 'FETCH'; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments = array($command => array('MESSAGE' => $message)); + } else { + // fetch is a command + $this->command = $command; + $this->arguments = array('MESSAGE' => $message); + } + + $this->isValidated = false; + $this->asString .= $this->prefixWhitespace($command); + $this->asString .= $this->prefixWhitespace($message); + return $this; + } + + /** + * {@inheritDoc} + */ + public function fetchRange($messageFrom, $messageTo) + { + $command = 'FETCH'; + $message = $messageFrom . ':' . $messageTo; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments = array($command => array('MESSAGE' => $message)); + } else { + // fetch is a command + $this->command = $command; + $this->arguments = array('MESSAGE' => $message); + } + + $this->isValidated = false; + $this->asString .= $this->prefixWhitespace($command); + $this->asString .= $this->prefixWhitespace($message); + return $this; + } + + /** + * {@inheritDoc} + */ + public function fetchSet(MessageList $messages) + { + $this->buildSet($messages, 'FETCH'); + return $this; + } + + /** + * {@inheritDoc} + */ + public function header() + { + $command = 'FETCH'; + $argument = 'BODY[HEADER] BODYSTRUCTURE'; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments[$command]['FIELDS'][] = $argument; + } else { + // fetch is a command + $this->arguments['FIELDS'][] = $argument; + } + $this->asString .= $this->prefixWhitespace($argument); + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function body() + { + $command = 'FETCH'; + $argument = 'BODY[TEXT]'; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments[$command]['FIELDS'][] = $argument; + } else { + // fetch is a command + $this->arguments['FIELDS'][] = $argument; + } + + $this->asString .= $this->prefixWhitespace($argument); + + $this->isValidated = false; + return $this; + } + + /** + * {@inheritDoc} + */ + public function uids() + { + $command = 'FETCH'; + $argument = 'UID'; + if ($this->command === 'UID') { + // fetch is an argument + $this->arguments[$command]['FIELDS'][] = $argument; + } else { + // fetch is a command + $this->arguments['FIELDS'][] = $argument; + } + + $this->asString .= $this->prefixWhitespace($argument); + + $this->isValidated = false; + return $this; + } + + /** + * add white space to the beginning of string + * @param string $string + * @return string + */ + private function prefixWhitespace($string) + { + return "\x20" . $string; + } + + private function buildSet(MessageList $messages, $command) + { + $messageArray = array(); + if ($this->command === 'UID') { + // fetch is an argument + + foreach ($messages as $message) { + $messageArray[] = $message->uid(); + } + $messageNumbers = implode(',', $messageArray); + $this->arguments = array($command => array('MESSAGE' => $messageNumbers)); + } else { + // fetch is a command + foreach ($messages as $message) { + $messageArray[] = $message->number(); + } + $messageNumbers = implode(',', $messageArray); + $this->command = $command; + $this->arguments = array('MESSAGE' => $messageNumbers); + } + + $this->isValidated = false; + $this->asString .= $this->prefixWhitespace($command); + $this->asString .= $this->prefixWhitespace($messageNumbers); + } + + private function buildSequence(MessageList $messages, $command) + { + $messageArray = array(); + foreach ($messages as $message) { + if ($this->command === 'UID') { + $messageArray[] = $message->uid(); + } else { + $messageArray[] = $message->number(); + } + } + return implode(' ', $messageArray); + } + + /** + * Used to keep this class DRY + * @param string $command + * @param string $argument + * @param null|string $comparator + */ + private function buildSearchArgument($command, $argument, $comparator = null) + { + $this->asString .= $this->prefixWhitespace($argument); + if ($comparator !== null) { + $this->asString .= $this->prefixWhitespace($comparator); + } + + if ($this->command === 'UID') { + $this->arguments[$command][] = $argument; + if ($comparator !== null) { + $this->arguments[$command][] = $comparator; + } + } else { + $this->arguments[] = $argument; + if ($comparator !== null) { + $this->arguments[] = $comparator; + } + } + } + + private function buildGroup(array $haystack) + { + return '(' . implode("\x20", $haystack) . ')'; + } + + private function buildStore() + { + $command = 'STORE'; + $this->asString = $this->prefixWhitespace($this->command); + + if ($this->command === 'UID') { + $arguments = $this->arguments[$command]; + $this->asString .= $this->prefixWhitespace($command); + } else { + $arguments = $this->arguments; + $this->command = $command; + } + + if (array_key_exists('MESSAGE', $arguments)) { + $this->asString .= $this->prefixWhitespace($arguments['MESSAGE']); + } + + if (!empty($arguments['+FLAGS'])) { + $this->asString .= $this->prefixWhitespace('+FLAGS.SILENT'); + $this->asString .= $this->prefixWhitespace($this->buildGroup($arguments['+FLAGS'])); + } + + if (!empty($arguments['-FLAGS'])) { + $this->asString .= $this->prefixWhitespace('-FLAGS.SILENT'); + $this->asString .= $this->prefixWhitespace($this->buildGroup($arguments['-FLAGS'])); + } + + if (!empty($arguments['FLAGS'])) { + $this->asString .= $this->prefixWhitespace('FLAGS.SILENT'); + $this->asString .= $this->prefixWhitespace($this->buildGroup($arguments['FLAGS'])); + } + } + + private function buildFetch() + { + $command = 'FETCH'; + $this->asString = $this->prefixWhitespace($this->command); + + if ($this->command === 'UID') { + $arguments = $this->arguments[$command]; + $this->asString .= $this->prefixWhitespace($command); + } else { + $arguments = $this->arguments; + } + $this->asString .= $this->prefixWhitespace($arguments['MESSAGE']) + . $this->prefixWhitespace($this->buildGroup($arguments['FIELDS'])); + } + + private function buildSearch() + { + $command = 'SEARCH'; + $this->asString = $this->prefixWhitespace($this->command); + + if ($this->command === 'UID') { + $arguments = $this->arguments[$command]; + $this->asString .= $this->prefixWhitespace($command); + } else { + $arguments = $this->arguments; + } + + foreach ($arguments as $argumentKey => $argument) { + $this->asString .= $this->prefixWhitespace($argument); + } + } + + private function buildCopy() + { + $command = 'COPY'; + $this->asString = $this->prefixWhitespace($this->command); + + if ($this->command === 'UID') { + $arguments = $this->arguments[$command]; + $this->asString .= $this->prefixWhitespace($command); + + $this->asString = $this->prefixWhitespace($this->command) + . $this->prefixWhitespace($command) + . $this->prefixWhitespace($arguments['MESSAGE']) + . $this->prefixWhitespace($arguments['MAILBOX']); + } else { + $arguments = $this->arguments; + $this->asString = $this->prefixWhitespace($command) + . $this->prefixWhitespace($arguments['MESSAGE']) + . $this->prefixWhitespace($arguments['MAILBOX']); + } + } +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/PimapSupportedCommandsInterface.php b/src/Imap/CommandBuilder/PimapSupportedCommandsInterface.php new file mode 100755 index 0000000..838d62b --- /dev/null +++ b/src/Imap/CommandBuilder/PimapSupportedCommandsInterface.php @@ -0,0 +1,64 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder; + + +/** + * Interface PimapSupportedCommandsInterface + * @package SalesAgility\Imap\CommandBuilder + */ +interface PimapSupportedCommandsInterface extends + CommandRawInterface, + Commands\AppendCommandArgumentsInterface, + Commands\AppendCommandInterface, + Commands\CapabilityCommandInterface, + Commands\CheckCommandInterface, + Commands\CloseCommandInterface, + Commands\CopyCommandArgumentsInterface, + Commands\CopyCommandInterface, + Commands\CreateCommandInterface, + Commands\DeleteCommandInterface, + Commands\ExamineCommandInterface, + Commands\ExpungeCommandInterface, + Commands\FetchCommandArgumentsInterface, + Commands\FetchCommandInterface, + Commands\IdleCommandInterface, + Commands\ListCommandInterface, + Commands\LoginCommandInterface, + Commands\LoginCommandPasswordArgumentInterface, + Commands\LoginCommandUserArgumentInterface, + Commands\LogoutCommandInterface, + Commands\LSubCommandInterface, + Commands\NoopCommandInterface, + Commands\RenameCommandInterface, + Commands\SearchCommandArgumentsInterface, + Commands\SearchCommandInterface, + Commands\SelectCommandInterface, + Commands\StartTlsCommandInterface, + Commands\StatusCommandArgumentsInterface, + Commands\StatusCommandInterface, + Commands\StoreCommandArgumentsInterface, + Commands\StoreCommandInterface, + Commands\SubscribeCommandInterface, + Commands\UidCommandInterface, + Commands\UnsubscribeCommandInterface +{ +} \ No newline at end of file diff --git a/src/Imap/CommandBuilder/PimapSupportedTopLevelCommandsInterface.php b/src/Imap/CommandBuilder/PimapSupportedTopLevelCommandsInterface.php new file mode 100755 index 0000000..0f7ff5a --- /dev/null +++ b/src/Imap/CommandBuilder/PimapSupportedTopLevelCommandsInterface.php @@ -0,0 +1,56 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\CommandBuilder; + + +/** + * Interface PimapSupportedCommandsInterface + * @package SalesAgility\Imap\CommandBuilder + * To help the IDE to hint the correct commands + */ +interface PimapSupportedTopLevelCommandsInterface extends + CommandRawInterface, + Commands\AppendCommandInterface, + Commands\CapabilityCommandInterface, + Commands\CheckCommandInterface, + Commands\CloseCommandInterface, + Commands\CopyCommandInterface, + Commands\CreateCommandInterface, + Commands\DeleteCommandInterface, + Commands\ExamineCommandInterface, + Commands\ExpungeCommandInterface, + Commands\FetchCommandInterface, + Commands\IdleCommandInterface, + Commands\ListCommandInterface, + Commands\LoginCommandInterface, + Commands\LogoutCommandInterface, + Commands\LSubCommandInterface, + Commands\NoopCommandInterface, + Commands\RenameCommandInterface, + Commands\SearchCommandInterface, + Commands\SelectCommandInterface, + Commands\StatusCommandInterface, + Commands\StoreCommandInterface, + Commands\SubscribeCommandInterface, + Commands\UidCommandInterface, + Commands\UnsubscribeCommandInterface +{ +} \ No newline at end of file diff --git a/src/Imap/Enumerator/Format.php b/src/Imap/Enumerator/Format.php new file mode 100644 index 0000000..e0e51a9 --- /dev/null +++ b/src/Imap/Enumerator/Format.php @@ -0,0 +1,16 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Enumerator; + + +/** + * Class ResponseCode + * @package SalesAgility\Imap\Enumerator + */ +class ResponseCode +{ + const OK_RESPONSE = 0; + const NO_RESPONSE = 5000; + const BAD_RESPONSE = 6000; +} \ No newline at end of file diff --git a/src/Imap/ImapException.php b/src/Imap/ImapException.php new file mode 100755 index 0000000..cfa28c7 --- /dev/null +++ b/src/Imap/ImapException.php @@ -0,0 +1,62 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap; + +use SalesAgility\Stream\ConnectionException; +use SalesAgility\Imap\Enumerator\ResponseCode; + +/** + * Class ImapException + * @package SalesAgility\Imap + */ +class ImapException extends ConnectionException +{ + const CODE_TIMEOUT = 408; + const COMMAND_MISSING_PREFIX = 9000; + + /** + * @param $message + * @param int $code + * @return ImapException + */ + public static function NoResponse($message, $code = ResponseCode::NO_RESPONSE) + { + return new self('No response: ' . $message, $code); + } + + /** + * @param $message + * @param int $code + * @return ImapException + */ + public static function BadResponse($message, $code = ResponseCode::BAD_RESPONSE) + { + return new self('Bad response: ' . $message, $code); + } + + /** + * @param string $message + * @return ImapException + */ + public static function MissingCommandPrefix($message = '') + { + return new self('Missing Command Prefix: ' . $message, self::COMMAND_MISSING_PREFIX); + } +} \ No newline at end of file diff --git a/src/Imap/Interpreter/CapabilityInterpreter.php b/src/Imap/Interpreter/CapabilityInterpreter.php new file mode 100755 index 0000000..4df4e04 --- /dev/null +++ b/src/Imap/Interpreter/CapabilityInterpreter.php @@ -0,0 +1,64 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Interpreter; + + +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Imap\Response\Capability; + +class CapabilityInterpreter implements StringIteratorInterpreter +{ + /** + * @param StringIterator $iterator + * @return Capability + */ + public function parse(StringIterator $iterator) + { + $response = $iterator->toString(); + $posStart = strpos($response, 'CAPABILITY') + 11; + $posEnd = strpos($response, "\r\n", $posStart); + if (empty($posEnd)) { + $length = strlen($response) - $posStart; + } else { + $length = $posEnd - $posStart; + } + $substring = substr($response, $posStart, $length); + $capabilities = explode(" ", $substring); + $object = new Capability(); + foreach ($capabilities as $capability) { + if ($capability === "\20") { + continue; + } + + if ($capability === '[') { + continue; + } + + if ($capability === ']') { + // if parsing response of select or examine + break; + } + + $object->offsetSet($capability, true); + } + + return $object; + } +} \ No newline at end of file diff --git a/src/Imap/Interpreter/EncodingInterpreter.php b/src/Imap/Interpreter/EncodingInterpreter.php new file mode 100755 index 0000000..489ddeb --- /dev/null +++ b/src/Imap/Interpreter/EncodingInterpreter.php @@ -0,0 +1,43 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Interpreter; + + +/** + * Class EncodingInterpreter + * @package SalesAgility\Imap\Interpreter + */ +class EncodingInterpreter +{ + /** + * @param $value + * @return string + */ + public function decode($value) + { + $decoded_string = ''; + $decoded = imap_mime_header_decode($value); + foreach ($decoded as $stdclass) { + $decoded_string .= $stdclass->text; + } + + return trim($decoded_string); + } +} \ No newline at end of file diff --git a/src/Imap/Interpreter/LexemeInterpreter.php b/src/Imap/Interpreter/LexemeInterpreter.php new file mode 100755 index 0000000..a2b5fb6 --- /dev/null +++ b/src/Imap/Interpreter/LexemeInterpreter.php @@ -0,0 +1,144 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Interpreter; + +use SalesAgility\Imap\Lexeme\LexemeList; +use SalesAgility\Imap\Lexeme\LexemeType; + +/** + * Class LexemeInterpreter + * @package SalesAgility\Imap\Interpreter + */ +class LexemeInterpreter +{ + /** + * @param LexemeList $lexemes + * @return bool + */ + public function isNewLine(LexemeList &$lexemes) + { + return $lexemes->current()->hasType(LexemeType::newLine()); + } + + /** + * @param LexemeList $lexemes + * @return bool + */ + public function isKeyword(LexemeList &$lexemes) + { + return $lexemes->current()->hasType(LexemeType::allCapitals()) + || $lexemes->current()->hasType(LexemeType::capitalsNumbers()); + } + + /** + * @param LexemeList $lexemes + * @return bool + */ + public function isNumber(LexemeList &$lexemes) + { + return $lexemes->current()->hasType(LexemeType::allNumbers()); + } + + /** + * @param LexemeList $lexemes + * @return bool + */ + public function isWhitespace(LexemeList &$lexemes) + { + return $lexemes->current()->hasType(LexemeType::whitespace()); + } + + /** + * @param LexemeList $lexemes + * @return bool + */ + public function isGroup(LexemeList &$lexemes) + { + return $lexemes->current()->hasType(LexemeType::group()); + } + + /** + * @param LexemeList $lexemes + * @return bool + */ + public function isOptional(LexemeList &$lexemes) + { + return $lexemes->current()->hasType(LexemeType::optional()); + } + + /** + * @param LexemeList $lexemes + * @return bool + */ + public function isFlag(LexemeList &$lexemes) + { + return $lexemes->current()->toString() === '\\'; + } + + /** + * @param LexemeList $lexemes + * @return bool + */ + public function isAtom(LexemeList &$lexemes) + { + return $lexemes->current()->hasType(LexemeType::atom()); + } + + /** + * @param LexemeList $lexemes + * @return int + */ + public function lastInGroup(LexemeList &$lexemes) + { + $groupKey = $lexemes->key(); + $count = $lexemes->current()->current()->lastKey(); + $lastKey = $groupKey; + + while ($lexemes->valid()) { + $lexemes->next(); + + // handle when no new line is after the outer group + if (!$lexemes->valid()) { + $lastKey = $lexemes->key() - 1; + $lexemes->seek($lastKey); + break; + } + + // Skip empty fields like Subject + if ($lexemes->current()->key() === null) { + continue; + } + + $lexemes->current()->fastForward(); + if ($lexemes->current()->current()->firstKey() >= $count) { + $lastKey = $lexemes->key(); + break; + } + + if ($lexemes->current()->current()->lastKey() >= $count) { + $lastKey = $lexemes->key(); + break; + } + } + + $lexemes->seek($groupKey); + return $lastKey; + } +} \ No newline at end of file diff --git a/src/Imap/Interpreter/MailboxInterpreter.php b/src/Imap/Interpreter/MailboxInterpreter.php new file mode 100644 index 0000000..fcbac3f --- /dev/null +++ b/src/Imap/Interpreter/MailboxInterpreter.php @@ -0,0 +1,188 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Interpreter; + + +use SalesAgility\Imap\Token\Tokenizer; +use SalesAgility\Imap\Token\TokenList; +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Imap\Response\Mailbox; + +class MailboxInterpreter implements StringIteratorInterpreter +{ + /** + * @param StringIterator $iterator + * @return Mailbox + * @throws \SalesAgility\Imap\Token\TokenException + */ + public function parse(StringIterator $iterator) + { + $mailbox = new Mailbox(); + $tokenizer = new Tokenizer(); + $tokens = $tokenizer->parse($iterator); + + while ($tokens->valid()) { + $tokenString = $tokens->current()->toString(); + + if ($tokenString === 'FLAGS') { + $this->parseFlags($tokens, $mailbox); + } elseif ($tokenString === 'EXISTS') { + $this->parseKeywordWithNumberPrefix($tokens, $mailbox); + } elseif ($tokenString === 'RECENT') { + $this->parseKeywordWithNumberPrefix($tokens, $mailbox); + } + + // [KEYWORD ...] + if ($tokens->current()->type()->isNonFoldedLiteral()) { + $this->parseOption($tokens, $mailbox); + } + + // KEYWORD + $this->parseOptional($tokens, $mailbox); + + $tokens->next(); + } + + return $mailbox; + } + + public function parseFlags(TokenList &$tokens, Mailbox &$mailbox) + { + while ($tokens->valid()) { + + // find group + // use string iterator to create a substring + // strip replace \ + // convert to array + // assign to Mailbox + if ($tokens->current()->type()->isGroup()) { + $string = $this->buildStringIterator($tokens, $mailbox)->toString(); + $escaped = str_replace("\\", '', $string); + $flags = explode(' ', $escaped); + foreach ($flags as $flag) { + $mailbox->offsetSet('flags', $flag); + } + + $tokens->next(); + return; + } + + if ($tokens->current()->type()->isEndOfLine()) { + $tokens->next(); + return; + } + + $tokens->next(); + } + + } + + public function parseKeywordWithNumberPrefix(TokenList &$tokens, Mailbox &$mailbox) + { + $keyword = strtolower($tokens->current()->toString()); + $mailbox->offsetSet($keyword, $this->seekNumberBefore($tokens)); + + } + + private function parseOption(TokenList &$tokens, Mailbox &$mailbox) + { + $tokenStringIterator = $this->buildStringIterator($tokens); + $tokenizer = new Tokenizer(); + $subTokens = $tokenizer->parse($tokenStringIterator); + + while ($subTokens->valid()) { + $this->parseOptional($subTokens, $mailbox); + $subTokens->next(); + } + } + + private function seekNumberBefore(TokenList &$tokens) + { + $initialKey = $tokens->key() + 1; + + $tokens->seek($tokens->key() - 1); + while ($tokens->valid()) { + if ($tokens->current()->type()->isNotWhiteSpaceOrControl()) { + $tokenString = $tokens->current()->toString(); + if (is_numeric($tokenString)) { + $tokens->seek($initialKey); + return $tokenString; + } + } + + if ($tokens->current()->type()->isEndOfLine()) { + break; + } + + $tokens->seek($tokens->key() - 1); + } + + $tokens->seek($initialKey); + return 0; + } + + private function seekNumberAfter(TokenList &$tokens) + { + while ($tokens->valid()) { + if ($tokens->current()->type()->isNotWhiteSpaceOrControl()) { + $tokenString = $tokens->current()->toString(); + if (is_numeric($tokenString)) { + return $tokenString; + } + } + + if ($tokens->current()->type()->isEndOfLine()) { + break; + } + + $tokens->next(); + } + + return 0; + } + + private function buildStringIterator(TokenList &$tokens) + { + return StringIterator::withStringIterator( + $tokens->current()->getInnerIterator(), + $tokens->current()->first() + 1, + $tokens->current()->last() - $tokens->current()->first() + 1 + ); + } + + private function parseOptional(TokenList &$tokens, Mailbox &$mailbox) + { + $tokenString = $tokens->current()->toString(); + if ($tokenString === 'UNSEEN') { + $mailbox->offsetSet('unseen', (string)$this->seekNumberAfter($tokens)); + $tokens->next(); + return; + } elseif ($tokenString === 'UIDVALIDITY') { + $mailbox->offsetSet('uidvalidity', (string)$this->seekNumberAfter($tokens)); + $tokens->next(); + return; + } elseif ($tokenString === 'UIDNEXT') { + $mailbox->offsetSet('uidnext', (string)$this->seekNumberAfter($tokens)); + $tokens->next(); + return; + } + + } +} \ No newline at end of file diff --git a/src/Imap/Interpreter/MailboxListInterpreter.php b/src/Imap/Interpreter/MailboxListInterpreter.php new file mode 100755 index 0000000..d24f9b6 --- /dev/null +++ b/src/Imap/Interpreter/MailboxListInterpreter.php @@ -0,0 +1,106 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Interpreter; + +use SalesAgility\Imap\Lexeme\LexemeList; +use SalesAgility\Imap\Lexeme\Lexemizer; +use SalesAgility\Imap\Token\Tokenizer; +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Imap\Response\Mailbox; +use SalesAgility\Imap\Response\MailboxList; + +class MailboxListInterpreter implements StringIteratorInterpreter +{ + /** + * @param StringIterator $iterator + * @return array|MailboxList + * @throws \SalesAgility\Imap\Token\TokenException + */ + public function parse(StringIterator $iterator) + { + $mailboxList = new MailboxList(); + + $tokenizer = new Tokenizer(); + $lexemizer = new Lexemizer(); + $tokens = $tokenizer->parseWithoutLineRestrictions($iterator); + $lexemes = $lexemizer->parse($tokens); + + while ($lexemes->valid()) { + if ($lexemes->current()->toString() === '*') { + $lexemes->next(); + $mailboxList[] = $this->parseMailbox($lexemes, $mailboxList); + } + + $lexemes->next(); + } + + return $mailboxList; + } + + private function parseMailbox(LexemeList &$lexemes) + { + $mailbox = new Mailbox(); + $lexemeInterpreter = new LexemeInterpreter(); + + while ($lexemes->valid()) { + if ($lexemeInterpreter->isWhitespace($lexemes)) { + $lexemes->next(); + continue; + } elseif ($lexemeInterpreter->isNewLine($lexemes)) { + $lexemes->next(); + break; + } elseif ($lexemeInterpreter->isKeyword($lexemes)) { + $lexemes->next(); + continue; + } elseif ($lexemeInterpreter->isGroup($lexemes)) { + $this->parseNameAttributes($lexemes, $mailbox); + $lastLexeme = $lexemeInterpreter->lastInGroup($lexemes); + $lexemes->seek($lastLexeme); + continue; + } elseif ($lexemeInterpreter->isAtom($lexemes)) { + if (empty($mailbox->offsetGet('hierarchy'))) { + $mailbox->offsetSet('hierarchy', trim($lexemes->current()->toString(), '"')); + } elseif (empty($mailbox->offsetGet('name'))) { + $mailbox->offsetSet('name', trim($lexemes->current()->toString(), '"')); + break; + } + $lexemes->next(); + continue; + } + + $lexemes->next(); + } + + return $mailbox; + } + + private function parseNameAttributes(LexemeList &$lexemes, Mailbox &$mailbox) + { + // get substring of group + $string = $lexemes->current()->current()->toString(); + $string = trim($string, '()'); + $string = str_replace('\\', '', $string); + $attributes = explode(' ', $string); + foreach ($attributes as $attribute) { + $mailbox->offsetSet('attributes', $attribute); + + } + } +} \ No newline at end of file diff --git a/src/Imap/Interpreter/MessageInterpreter.php b/src/Imap/Interpreter/MessageInterpreter.php new file mode 100755 index 0000000..7a68ef0 --- /dev/null +++ b/src/Imap/Interpreter/MessageInterpreter.php @@ -0,0 +1,490 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Interpreter; + +use SalesAgility\Imap\Interpreter\LexemeInterpreter; +use SalesAgility\Imap\Interpreter\OctetInterpreter; +use SalesAgility\Imap\Interpreter\Rfc2822Interpreter; +use SalesAgility\Imap\Lexeme\LexemeList; +use SalesAgility\Imap\Lexeme\LexemeType; +use SalesAgility\Imap\Lexeme\Lexemizer; +use SalesAgility\Imap\Response\Message; +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Imap\Token\Tokenizer; +use SalesAgility\Utility\Assert; +use SalesAgility\Imap\Response\MessageList; +use SalesAgility\Imap\Response\MessageFactory; + +/** + * Class MessageInterpreter + * @package SalesAgility\Imap\Interpreter + */ +class MessageInterpreter implements StringIteratorInterpreter +{ + /** @var LexemeInterpreter $lexeme */ + private $lexeme; + + /** @var OctetInterpreter $octet */ + private $octet; + + /** @var Rfc2822Interpreter $rfc2822 */ + private $rfc2822; + + /** @var Tokenizer $tokenizer */ + private $tokenizer; + + /** @var Lexemizer $lexemizer */ + private $lexemizer; + + /** + * MessageInterpreter constructor. + */ + public function __construct() + { + $this->lexeme = new LexemeInterpreter(); + $this->octet = new OctetInterpreter(); + $this->rfc2822 = new Rfc2822Interpreter(); + $this->tokenizer = new Tokenizer(); + $this->lexemizer = new Lexemizer(); + } + + /** + * @param StringIterator $message + * @return \SalesAgility\Imap\Response\MessageList + * @throws \SalesAgility\Imap\Token\TokenException + * @throws \Exception + */ + public function parse(StringIterator $message) + { + $tokens = $this->tokenizer->parse($message); + $lexemes = $this->lexemizer->parse($tokens); + $messages = new MessageList(); + + while ($lexemes->valid()) { + $messages[] = $this->seekMessage($lexemes); + } + + return $messages; + } + + /** + * @param LexemeList $lexemes + * @return bool + */ + private function isMessage(LexemeList &$lexemes) + { + $test = function (LexemeList &$lexemes) { + return $lexemes->current()->offsetGet(0)->toString() === '*'; + }; + + if ($this->lexeme->isNewLine($lexemes)) { + $this->seekNextLexeme($lexemes); + if (!$lexemes->valid()) { + return false; + } + return $test($lexemes); + } elseif ($lexemes->key() === 0) { + return $test($lexemes); + } elseif ($lexemes->offsetGet($lexemes->key() - 1)->hasType(LexemeType::newLine())) { + return $test($lexemes); + } + + return false; + } + + + /** + * @param LexemeList $lexemes + * @param Message $message + * @return bool + * @throws \Exception + */ + private function parseKeyword(LexemeList &$lexemes, Message &$message) + { + $keywords = $lexemes->current()->toString(); + switch ($keywords) { + case "FETCH": + $this->seekNextLexeme($lexemes); + $this->seekMessageNumber($lexemes, $message); + + $this->seekWhitespace($lexemes); + + Assert::is($this->lexeme->isGroup($lexemes), 'Message Interpreter: expected a group but found ' . $lexemes->current()->toString()); + $lastLexemeInGroup = $this->lexeme->lastInGroup($lexemes); + $this->seekNextLexeme($lexemes); + + while ($lexemes->valid() && $lexemes->key() < $lastLexemeInGroup) { + Assert::is($this->lexeme->isKeyword($lexemes), 'Message Interpreter: expected a keyword but found ' . $lexemes->current()->toString()); + $this->parseFetchKeyword($lexemes, $message); + + // skip extra new lines and whitespace + $this->seekNoise($lexemes); + } + return true; + default: + break; + } + + return false; + } + + /** + * @param LexemeList $lexemes + * @param Message $message + * @return bool + * @throws \Exception + */ + private function parseFetchKeyword(LexemeList &$lexemes, Message &$message) + { + $keywords = $lexemes->current()->toString(); + switch ($keywords) { + case "UID": + $this->seekNextLexeme($lexemes); + $this->seekWhitespace($lexemes); + Assert::is($this->lexeme->isNumber($lexemes), 'Message Interpreter: expected a number'); + $message['uid'] = $lexemes->current()->toString(); + $this->seekNextLexeme($lexemes); + $this->seekWhitespace($lexemes); + $this->skipNewLines($lexemes); + + break; + case "FLAGS": + $this->seekNextLexeme($lexemes); + $this->seekWhitespace($lexemes); + + Assert::is($this->lexeme->isGroup($lexemes), 'Message Interpreter: expected a group of flags'); + $lastCharacterOfGroup = $lexemes->current()->offsetGet(0)->lastKey(); + $this->seekNextLexeme($lexemes); + + + while ($lexemes->valid()) { + $this->seekWhitespace($lexemes); + + Assert::is($this->lexeme->isFlag($lexemes), 'Message Interpreter: expected a escape character to mark the flag'); + $this->seekNextLexeme($lexemes); + + Assert::is($this->lexeme->isAtom($lexemes), 'Message Interpreter: expected atom'); + $message['flags'][$lexemes->current()->toString()] = true; + $this->seekNextLexeme($lexemes); + + if ($lexemes->current()->offsetGet(0)->firstKey() >= $lastCharacterOfGroup) { + $this->seekWhitespace($lexemes); + $this->skipNewLines($lexemes); + return true; + } + $this->seekNextLexeme($lexemes); + } + $this->skipNewLines($lexemes); + break; + case "BODY": + /** @see https://tools.ietf.org/html/rfc3501#page-54 */ + $this->seekNextLexeme($lexemes); + $this->seekWhitespace($lexemes); + + if ($this->lexeme->isOptional($lexemes)) { + // BODY[section] + $section = $this->parseFetchBodySection($lexemes, $message); + + if ($section !== null) { + // skip new lines at the end of the fetch + $this->skipNewLines($lexemes); + return $section; + } + } + break; + case "BODYSTRUCTURE": + $bodyStructure = $this->parseFetchBodyStructure($lexemes, $message); + if ($bodyStructure !== null) { + if ($lexemes->current()->toString() === ')') { + $lexemes->next(); + } + $this->seekWhitespace($lexemes); + $this->skipNewLines($lexemes); + return $bodyStructure; + } + break; + default: + throw new \Exception('Message Interpreter: unsupported keyword'); + } + + return null; + } + + /** + * @param LexemeList $lexemes + * @param Message $message + * @return bool|null + * @throws \Exception + */ + private function parseFetchBodyStructure(LexemeList &$lexemes, Message &$message) + { + // Quoted-Printable Content-Transfer-Encoding + /** @see https://tools.ietf.org/html/rfc2045#page-19 */ + $this->seekNextLexeme($lexemes); + $this->seekWhitespace($lexemes); + + if ($this->lexeme->isGroup($lexemes)) { + $lastCharacterOfGroup = $lexemes->current()->offsetGet(0)->lastKey(); + $this->seekNextLexeme($lexemes); + } else { + throw new \Exception('Message Interpreter: expected a number'); + } + + $htmlBodyFound = false; + while ($lexemes->valid()) { + // Detect + // is Plain? + // Is html? + // Has Attachments + // + // MIME Part contains: + // Type + // Charset + // Boundary + // Transfer Encoding + // Length + // Lines + if ($lexemes->current()->offsetGet(0)->type()->isQuoted()) { + if (strtolower($lexemes->current()->toString()) === '"text"') { + $this->seekNextLexeme($lexemes); + $this->seekWhitespace($lexemes); + + if (strtolower($lexemes->current()->toString()) === '"plain"') { + $message['body']['structure']->offsetSet('plain', true); + } elseif (strtolower($lexemes->current()->toString()) === '"html"') { + // scan ahead by up 9 tokens test if html is an attachment + // the "NAME" keyword is a good indicator that it is an attachment + $scanTo = 10; + $isAttachment = false; + while ($lexemes->valid() && $scanTo > 0) { + if ($lexemes->current()->offsetGet(0)->type()->isQuoted()) { + if (strtolower($lexemes->current()->toString()) === '"name"') { + $isAttachment = true; + break; + } + } + + --$scanTo; + $this->seekNextLexeme($lexemes); + } + + if ($isAttachment || $htmlBodyFound) { + $message['body']['structure']->offsetSet('attachments', true); + } else { + $message['body']['structure']->offsetSet('html', true); + $htmlBodyFound = true; + } + } + } elseif (strtolower($lexemes->current()->toString()) === '"image"') { + $message['body']['structure']->offsetSet('attachments', true); + } elseif (strtolower($lexemes->current()->toString()) === '"audio"') { + $message['body']['structure']->offsetSet('attachments', true); + } elseif (strtolower($lexemes->current()->toString()) === '"video"') { + $message['body']['structure']->offsetSet('attachments', true); + } elseif (strtolower($lexemes->current()->toString()) === '"application"') { + $message['body']['structure']->offsetSet('attachments', true); + } elseif (strtolower($lexemes->current()->toString()) === '"attachment"') { + $message['body']['structure']->offsetSet('attachments', true); + } + } + + if ($lexemes->current()->offsetGet(0)->firstKey() >= $lastCharacterOfGroup) { + return true; + } + $this->seekNextLexeme($lexemes); + } + + return null; + } + + /** + * @param LexemeList $lexemes + * @param Message $message + * @return bool|null + * @throws \Exception + */ + private function parseFetchBodySection(LexemeList &$lexemes, Message &$message) + { + $keywords = $lexemes->current()->toString(); + switch ($keywords) { + case "[HEADER]": + return $this->rfc2822->parseHeader($lexemes, $message); + case "[TEXT]": + return $this->rfc2822->parseBody($lexemes, $message); + case "TEXT": + return $this->rfc2822->parseBody($lexemes, $message); + default: + throw new \Exception('Message Interpreter: unsupported keyword'); + } + } + + + /** + * Provides offset protection + * Calculates the elapsed $octetCount + * @param LexemeList $lexemes + */ + private function seekNextLexeme(LexemeList &$lexemes) + { + if ($lexemes->offsetExists($lexemes->key() + 1)) { + if (!$this->lexeme->isGroup($lexemes)) { + $this->octet->add($lexemes->current()->octetCount()); + } + } + + $lexemes->next(); + } + + /** + * @param LexemeList $lexemes + * @return bool|Message + * @throws \Exception + */ + /** + * @param LexemeList $lexemes + * @return bool|Message + * @throws \Exception + */ + private function seekMessage(LexemeList &$lexemes) + { + Assert::is($this->isMessage($lexemes), 'Message Interpreter: unable to determine the start of message'); + // CRLF* 1 KEYWORD + $this->seekNextLexeme($lexemes); + $this->seekWhitespace($lexemes); + $this->seekNumber($lexemes); + $this->seekWhitespace($lexemes); + $message = $this->seekKeyword($lexemes); + + // Ignore CRLF + while ($lexemes->valid()) { + if (!$lexemes->current()->hasType(LexemeType::newLine())) { + break; + } + $lexemes->next(); + } + + return $message; + } + + /** + * @param LexemeList $lexemes + * @return bool|Message + * @throws \Exception + */ + /** + * @param LexemeList $lexemes + * @return bool|Message + * @throws \Exception + */ + private function seekKeyword(LexemeList &$lexemes) + { + if ($this->lexeme->isKeyword($lexemes)) { + $message = MessageFactory::instance(); + $this->parseKeyword($lexemes, $message); + Assert::is($message !== false, 'Message Interpreter: keyword is unsupported/invalid'); + return $message; + } + + return false; + } + + /** + * @param LexemeList $lexemes + * @param Message $message + */ + /** + * @param LexemeList $lexemes + * @param Message $message + */ + private function seekMessageNumber(LexemeList &$lexemes, Message &$message) + { + $message['number'] = $lexemes->offsetGet($lexemes->key() - 3)->toString(); + } + + /** + * @param LexemeList $lexemes + * @return bool + */ + /** + * @param LexemeList $lexemes + * @return bool + */ + private function seekWhitespace(LexemeList &$lexemes) + { + if ($this->lexeme->isWhitespace($lexemes)) { + $this->seekNextLexeme($lexemes); + } + + return false; + } + + /** + * @param LexemeList $lexemes + * @return bool + */ + /** + * @param LexemeList $lexemes + * @return bool + */ + private function seekNumber(LexemeList &$lexemes) + { + if ($this->lexeme->isNumber($lexemes)) { + $this->seekNextLexeme($lexemes); + } + + return false; + } + + /** + * Skip extra whitespace, newlines folding lines ect. + * @param LexemeList $lexemes + */ + private function seekNoise(LexemeList &$lexemes) + { + while ($lexemes->valid()) { + + $isNoise = $lexemes->current()->hasType(LexemeType::newLine()) + || $lexemes->current()->hasType(LexemeType::whitespace()); + + if ($isNoise) { + $lexemes->next(); + } else { + break; + } + } + } + + /** + * @param LexemeList $lexemes + */ + /** + * @param LexemeList $lexemes + */ + public function skipNewLines(LexemeList & $lexemes) + { + // skip new lines at the end of the fetch + while ($lexemes->valid()) { + if (!$lexemes->current()->hasType(LexemeType::newLine())) { + break; + } + $this->seekNextLexeme($lexemes); + } + } +} diff --git a/src/Imap/Interpreter/OctetInterpreter.php b/src/Imap/Interpreter/OctetInterpreter.php new file mode 100755 index 0000000..9448009 --- /dev/null +++ b/src/Imap/Interpreter/OctetInterpreter.php @@ -0,0 +1,101 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Interpreter; + +use SalesAgility\Imap\Lexeme\LexemeList; +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Utility\Assert; + +/** + * Class OctetInterpreter + * @package SalesAgility\Imap\Interpreter + */ +class OctetInterpreter +{ + /** + * @var int used to validate the size of headers and bodies + * @see https://tools.ietf.org/html/rfc3501#page-16 + */ + private $count = 0; + + /** + * @param LexemeList $lexemes + * @return int + * @throws \Exception + */ + public function parse(LexemeList &$lexemes) + { + $iterator = StringIterator::withLiteral($lexemes->current()->toString()); + $validFirstChar = $iterator->current() === '{'; + $iterator->fastForward(); + $validLastChar = $iterator->current() === '}'; + $number = (int)trim($iterator->getInnerString(), '{}'); + Assert::is($validFirstChar && $validLastChar && is_numeric($number), 'Message Interpreter: expected number of octets'); + + return $number; + } + + + /** + * @param $integer + */ + public function add($integer) + { + $this->count += $integer; + } + + /** + * @return int + */ + public function count() + { + return $this->count; + } + + public function reset() + { + $this->count = 0; + } + + /** + * @param LexemeList $lexemes + * @param $offset + * @param $limit + * @return bool + */ + public function isEndOfOctetBoundary(LexemeList & $lexemes, $offset, $limit) + { + $lexeme = $lexemes->current(); + $lastCharacterPosition = $offset + $limit; + /** @var StringIterator $token */ + foreach ($lexeme as $token) { + if ($token->first() >= $lastCharacterPosition) { + return true; + }; + + if ($token->last() >= $lastCharacterPosition) { + return true; + } + } + + return false; + + } +} \ No newline at end of file diff --git a/src/Imap/Interpreter/ResponseInterpreter.php b/src/Imap/Interpreter/ResponseInterpreter.php new file mode 100755 index 0000000..9c64156 --- /dev/null +++ b/src/Imap/Interpreter/ResponseInterpreter.php @@ -0,0 +1,82 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Interpreter; + + +use SalesAgility\Imap\Lexeme\LexemeList; +use SalesAgility\Imap\Lexeme\LexemeType; +use SalesAgility\Imap\Lexeme\Lexemizer; +use SalesAgility\Imap\Token\Tokenizer; +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Imap\Response\Response; + +/** + * Class ImapResponseInterpreter + * @package SalesAgility\Imap\Interpreter + */ +class ResponseInterpreter +{ + /** + * @param string $tag + * @param StringIterator $iterator + * @return Response + * @throws \SalesAgility\Imap\Token\TokenException + */ + public function parse($tag, StringIterator $iterator) + { + + $tokenizer = new Tokenizer(); + $tokenList = $tokenizer->parse($iterator); + $lexemizer = new Lexemizer(); + $lexemeList = $lexemizer->parse($tokenList); + + while ($lexemeList->valid()) { + // Find end of file + $lexemeList->current()->rewind(); + if ($lexemeList->current()->hasType(LexemeType::capitalsNumbers())) { + if ($lexemeList->current()->current()->toString() === $tag) { + // create sub string for the included non tagged responses + $endOfIncluded = $lexemeList->current()->current()->first(); + if ($endOfIncluded === 0) { + $includedInMessages = StringIterator::withLiteral('', 0, 0); + } else { + $includedInMessages = StringIterator::withStringIterator($iterator, 0, $endOfIncluded); + } + + // Get the tagged response + $key = $lexemeList->key(); + $lexemeList->fastForward(); + $lexemeList->current()->fastForward(); + $lastCharacter = $lexemeList->current()->current()->last(); + $count = ($lastCharacter - $endOfIncluded) + 1; + $responseMessage = StringIterator::withStringIterator($iterator, $endOfIncluded, $count); + $lexemeList->seek($key); + + // get status message from tagged response + $lexemeList->next(); // tag + $lexemeList->next(); // whitespace + $status = $lexemeList->current()->toString(); // Response Status Code + return new Response($status, $responseMessage, $includedInMessages); + } + } + $lexemeList->next(); + } + } +} \ No newline at end of file diff --git a/src/Imap/Interpreter/Rfc2822Interpreter.php b/src/Imap/Interpreter/Rfc2822Interpreter.php new file mode 100755 index 0000000..6bc03d2 --- /dev/null +++ b/src/Imap/Interpreter/Rfc2822Interpreter.php @@ -0,0 +1,737 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Interpreter; + + +use SalesAgility\Imap\Lexeme\LexemeList; +use SalesAgility\Imap\Lexeme\LexemeType; +use SalesAgility\Imap\Response\Message; +use SalesAgility\Imap\Response\MessageAttachment; +use SalesAgility\Imap\Response\MessageAttachmentStructure; +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Utility\Assert; + +/** + * Class Rfc2822Interpreter + * @package SalesAgility\Imap\Interpreter + */ +class Rfc2822Interpreter +{ + + /** @var LexemeInterpreter $lexeme */ + private $lexeme; + + /** @var EncodingInterpreter */ + private $encoding; + + /** @var OctetInterpreter $octetInterpreter */ + private $octetInterpreter; + + /** + * Rfc2822Interpreter constructor. + */ + public function __construct() + { + $this->lexeme = new LexemeInterpreter(); + $this->encoding = new EncodingInterpreter(); + $this->octetInterpreter = new OctetInterpreter(); + } + + /** + * @param LexemeList $lexemes + * @param Message $message + * @return bool|null + * @throws \Exception + */ + public function parseHeader(LexemeList &$lexemes, Message &$message) + { + $octet = new OctetInterpreter(); + while ($lexemes->valid()) { + $this->seekNextLexeme($lexemes); + $this->seekWhitespace($lexemes); + + $limit = $octet->parse($lexemes); + $this->seekNextLexeme($lexemes); + + if ($lexemes->current()->hasType(LexemeType::newLine())) { + $this->seekNextLexeme($lexemes, $octet); + } + $lexemes->current()->rewind(); + $offset = $lexemes->current()->current()->first(); + + while ($lexemes->valid()) { + if ($lexemes->current()->hasType(LexemeType::fieldHeader())) { + $fieldName = $lexemes->current()->toString(); + $this->seekNextLexeme($lexemes); + Assert::is($lexemes->current()->hasType(LexemeType::fieldBody()), 'Message Interpreter: expected a field value'); + + $fieldValue = $lexemes->current()->toString(); + $this->seekNextLexeme($lexemes); + + $this->parseHeaderField($fieldName, $fieldValue, $message); + } elseif ($octet->isEndOfOctetBoundary($lexemes, $offset, $limit)) { + // End of header + return true; + } + + $this->seekNextLexeme($lexemes); + } + + // use this to parse only the section + $this->seekNextLexeme($lexemes); + } + + return null; + } + + /** + * @param LexemeList $lexemes + * @param Message $message + * @return bool|null|Message + * @throws \Exception + */ + public function parseBody(LexemeList &$lexemes, Message &$message) + { + while ($lexemes->valid()) { + $this->seekNextLexeme($lexemes); + $this->seekWhitespace($lexemes); + + $limit = $this->octetInterpreter->parse($lexemes); + $this->seekNextLexeme($lexemes); + + if ($lexemes->current()->hasType(LexemeType::newLine())) { + $this->seekNextLexeme($lexemes); + } + + $startPosition = $lexemes->key(); + $startOffset = $lexemes->current()->current()->first(); + + $boundaries = $this->seekMimeBoundaries($lexemes, $startOffset, $limit); + + // is plain text email with no attachments? (is there a boundary marker? NO?) + if (empty($boundaries)) { + // just plain text email with no attachments + //// use a new iterator to create a sub string + $lexemes->offsetGet($startPosition)->rewind(); + $substring = $lexemes->offsetGet($startPosition)->current()->getInnerIterator(); + $plainTextBody = StringIterator::withStringIterator($substring, $startOffset, $limit); + $message->body()->offsetSet('text', $plainTextBody->toString()); + $message->body()->structure()->offsetSet('html', false); + $message->body()->structure()->offsetSet('attachments', false); + return $message; + } else { + // parse multiResponse email + $this->parseMultiResponseMime($lexemes, $message, $boundaries); + $lexemes->seek(end($boundaries)['lexemeLastKey'] + 1); + return $message; + } + + // use this to parse only the section + $this->seekNextLexeme($lexemes); + } + return null; + } + + /** + * @param $fieldName + * @param $fieldValue + * @param Message $message + * @throws \Exception + */ + public function parseHeaderField(&$fieldName, &$fieldValue, Message &$message) + { + switch ($fieldName) { + case "Date": + $this->parseDate('date', $fieldValue, $message); + break; + case "Subject": + $this->parseSubject($fieldValue, $message); + break; + case "To": + $this->parseEmailAddresses('to', $fieldValue, $message); + break; + case "From": + $this->parseEmailAddresses('from', $fieldValue, $message); + break; + case "Reply-To": + $this->parseEmailAddresses('replyTo', $fieldValue, $message); + break; + case "Cc": + $this->parseEmailAddresses('cc', $fieldValue, $message); + break; + case "Bcc": + $this->parseEmailAddresses('bcc', $fieldValue, $message); + break; + case "Message-Id": + $this->parseEmailAddresses('messageId', $fieldValue, $message); + break; + case "MIME-Version": + $message->body()->structure()->offsetSet('mimeVersion', $fieldValue); + break; + case "Content-Type": + $message->body()->structure()->offsetSet('contentType', $fieldValue); + break; + case "Content-Transfer-Encoding": + $message->body()->structure()->offsetSet('contentTransferEncoding', $fieldValue); + break; + default: + // not yet supported but it's ok + // there is no need to throw exception + break; + } + } + + /** + * @param $field + * @param $value + * @param Message $message + * @throws \Exception + */ + private function parseDate($field, &$value, Message &$message) + { + $value = trim($value); + $possibleImaps = [ + \DateTime::RFC2822 . '+', + str_replace(['D, '], '', \DateTime::RFC2822), // day-of-week is optional + str_replace([':s'], '', \DateTime::RFC2822), // seconds are optional + str_replace(['D, ', ':s'], '', \DateTime::RFC2822), // day-of-week is optional, seconds are optional + \DateTime::RFC822, + str_replace(['D, '], '', \DateTime::RFC822), // day is optional + str_replace([':s'], '', \DateTime::RFC822), // seconds are optional + str_replace(['D, ', ':s'], '', \DateTime::RFC822), // day is optional, seconds are optional + ]; + + $dateTime = false; + // All IMAP servers respond with different data Imaps. + // The iteration attempt to use each possible Imap to decode the detail. + // The if ($dateTime !== false) means that when the DateTime class successfully + // decodes the date field it will exit the loop. + // As we no longer need to continue trying to decode the datetime Imap. + foreach ($possibleImaps as $possibleImap) { + $dateTime = \DateTimeImmutable::createFromFormat($possibleImap, $value); + if ($dateTime !== false) { + break; + } + } + + Assert::is($dateTime !== false, 'Expected header Date to comply with RFC2882'); + $message->header()->offsetSet($field, $dateTime); + } + + /** + * @param $field + * @param $value + * @param Message $message + * @throws \Exception + */ + private function parseEmailAddresses($field, &$value, Message &$message) + { + Assert::is(!empty($value), "expected email address to no be empty"); + // Test if there are more than one address + if (strpos($value, ',') !== false) { + $addresses = explode(',', $value); + foreach ($addresses as $address) { + $message->header()->offsetSet($field, $this->encoding->decode($address)); + } + } else { + $message->header()->offsetSet($field, $this->encoding->decode($value)); + } + } + + /** + * @param $value + * @param Message $message + */ + private function parseSubject(&$value, Message &$message) + { + $message->header()->offsetSet('subject', $this->encoding->decode($value)); + } + + /** + * Provides offset protection + * Calculates the elapsed $octetCount + * @param LexemeList $lexemes + */ + private function seekNextLexeme(LexemeList &$lexemes) + { + $lexemes->next(); + } + + /** + * skip next whitespace character + * @param LexemeList $lexemes + * @return bool + */ + private function seekWhitespace(LexemeList &$lexemes) + { + if ($this->lexeme->isWhitespace($lexemes)) { + $this->seekNextLexeme($lexemes); + } + + return false; + } + + /** + * skip next whitespace character + * @param LexemeList $lexemes + * @return bool + */ + private function seekToNewline(LexemeList &$lexemes) + { + $key = $lexemes->key(); + while ($lexemes->valid()) { + if ($this->lexeme->isNewLine($lexemes)) { + return true; + } + $this->seekNextLexeme($lexemes); + } + + $lexemes->seek($key); + return false; + } + + + /** + * @param LexemeList $lexemes + * @param string|integer $offset + * @param string|integer $limit + * @return array + * @throws \Exception + */ + private function seekMimeBoundaries(LexemeList &$lexemes, $offset, $limit) + { + $boundaries = array(); + while ($lexemes->valid()) { + if ($this->isBoundary($lexemes)) { + $boundary = $this->decodeBoundary($lexemes, $boundaries); + $boundaries[] = $boundary; + } + + if ($this->octetInterpreter->isEndOfOctetBoundary($lexemes, $offset, $limit)) { + // TODO: detect if boundary to scope is outside? + break; + } + + $lexemes->next(); + } + + if (!empty($boundaries)) { + Assert::is(end($boundaries)['type'] === 'close', 'failed to detect the last mime boundary check'); + } + return $boundaries; + } + + /** + * @param LexemeList $lexemes + * @return bool + */ + /** + * @param LexemeList $lexemes + * @return bool + */ + private function isBoundary(LexemeList &$lexemes) + { + $key = $lexemes->key(); + + $previous1Lexeme = $lexemes->offsetGet($key - 1); + $startWithNewLine = $previous1Lexeme->hasType(LexemeType::newLine()); + + // Detect length, It must include a hash + // This prevent the edge case where a message may contain a -- + // eg -----Original message----- or -- followed by a signature + $isLongEnough = strlen($lexemes->current()->toString()) >= 28; + + // Rewind the token iterator (Just in case the key is not set to 0) + $lexemes->current()->rewind(); + $lexemes->current()->current()->rewind(); + $boundaryString = $lexemes->current()->toString(); + + // Detect starting characters + $startWithDoubleDashes = false; + if ($isLongEnough) { + $firstChar = $boundaryString[0]; + $secondChar = $boundaryString[1]; + $startWithDoubleDashes = $firstChar === '-' && $secondChar === '-'; + } + + + // The next character must be a new line (CRLF) + $followBy = false; + if ($lexemes->offsetExists($key + 1)) { + $nextLexeme = $lexemes->offsetGet($key + 1); + $followBy = $nextLexeme->hasType(LexemeType::newLine()) + || $nextLexeme->hasType(LexemeType::whitespace()) + // Folding space cannot be detected so we need to look for a keyword + || $nextLexeme->hasType(LexemeType::capitalsNumbers()) + || $nextLexeme->hasType(LexemeType::allCapitals()) + || $nextLexeme->toString() === '.' + // + || $nextLexeme->toString() === '*'; + } + + + return $startWithNewLine + && $isLongEnough + && $startWithDoubleDashes + && $followBy; + } + + /** + * @param LexemeList $lexemes + * @param array $boundaries + * @return array + * @throws \Exception + */ + private function decodeBoundary(LexemeList &$lexemes, array $boundaries) + { + // $boundary structure + // lexemeKey is lexeme key where the boundary was found + // lexemeLastKey is lexeme key where the end of the boundary key was found + // type can be invalid | open | separator | close + // "invalid" marks an error has occurred + // "open" marks the start of a boundary + // "separator" marks a new boundary (it is likely to be the html Response) + // "close" marks the end of a boundary + // + // scope can be none | outside | inside + // "none" marks an error has occurred + // "outside" marks the boundary of the entire message + // "inside" marks the boundary of a mime Response eg attachment or html Response + $boundary = array( + 'lexemeKey' => -1, + 'lexemeLastKey' => -1, + 'type' => 'invalid', + 'scope' => 'invalid', + ); + + $key = $lexemes->key(); + + // Set lexemeKey + $boundary['lexemeKey'] = $lexemes->key(); + + // Rewind the token iterator (Just in case the key is not set to 0) + $lexemes->current()->rewind(); + $lexemes->current()->current()->rewind(); + $boundaryString = $lexemes->current()->toString(); + + // When boundary contains characters which typically are used as separators + // eg. "." it means the the boundary will be split across multiple lexemes + // So we need to handle this case + if ($lexemes->offsetExists($key + 1)) { + $nextLexeme = $lexemes->offsetGet($key + 1); + if ($nextLexeme->toString() === '.') { + // seek new line + if ($this->seekToNewline($lexemes)) { + // if new line found then add lexemes to boundaryString + $endkey = $lexemes->key(); + for ($i = $key + 1; $i < $endkey; $i++) { + $boundaryString .= $lexemes->offsetGet($i)->toString(); + $boundary['lexemeLastKey'] = $lexemes->key(); + } + } + } + } + + if ($boundary['lexemeLastKey'] === -1) { + $boundary['lexemeLastKey'] = $lexemes->key(); + } + + // Detect starting characters + $firstChar = $boundaryString[0]; + $secondChar = $boundaryString[1]; + $startWithDoubleDashes = $firstChar === '-' && $secondChar === '-'; + + // Detect ending characters + $last2Char = substr($boundaryString, -2); + $lastChar = $last2Char[1]; + $secondLastChar = $last2Char[0]; + $endsWithDoubleDashes = $lastChar === '-' && $secondLastChar === '-'; + + // Detect boundary type + // not all boundaries end with a -- so we need to check if it is the same as the first boundary + // sometimes the outer boundary is repeated as a separator for html or an attachment + if (!empty($boundaries) && $lexemes->offsetGet(end($boundaries)['lexemeKey'])->toString() === $lexemes->current()->toString()) { + $boundary['type'] = 'separator'; + } elseif ($startWithDoubleDashes && !$endsWithDoubleDashes) { + $boundary['type'] = 'open'; + } elseif ($startWithDoubleDashes && $endsWithDoubleDashes) { + $boundary['type'] = 'close'; + } else { + $boundary['type'] = 'invalid'; + } + + // Detect boundary scope + if (empty($boundaries)) { + // is this the first boundary detected + $boundary['scope'] = 'outside'; + } elseif (trim($lexemes->offsetGet($boundaries[0]['lexemeKey'])->toString(), '-') === trim($lexemes->current()->toString(), '-')) { + // is first boundary === to the current boundary + $boundary['scope'] = 'outside'; + } else { + // Is this second or third etc. boundary detected + $boundary['scope'] = 'inside'; + } + + Assert::is($boundary['scope'] !== 'invalid', 'unable to determine the scope of mime boundary'); + Assert::is($boundary['type'] !== 'invalid', 'unable to determine the type of mime boundary'); + return $boundary; + } + + + /** + * @param LexemeList $lexemes + * @param Message $message + * @param array $boundaries + * @throws \Exception + */ + /** + * @param LexemeList $lexemes + * @param Message $message + * @param array $boundaries + * @throws \Exception + */ + private function parseMultiResponseMime(LexemeList &$lexemes, Message &$message, array $boundaries) + { + // is plain text email with attachments? (is multiResponse? is html section missing? is attachment found?) + // is html/plain email? ( is multiResponse? is html section found?) + // is html/plain email with attachment? (is multiResponse? is html section found? is attachment found?) + // is 2 line ending found? (it signifies the end of a boundary for plain text emails, look for a end of group or keyword after it) + // it should not have any atoms after its. + // is boundary detected? is it an inside or outside boundary? Out side boundary marks the end of the message + + $firstBoundary = $boundaries[0]; + $firstLexemeKey = $firstBoundary['lexemeKey'] + 1; + $lastBoundary = end($boundaries); + $lastBoundaryKey = count($boundaries) - 1; + $lastLexemeKey = $lastBoundary['lexemeKey']; + + // Start from first boundary + $lexemes->seek($firstLexemeKey); + + $boundaryIndex = 0; + while ($boundaryIndex < $lastBoundaryKey && $lexemes->key() < $lastLexemeKey) { + $endOfBoundary = $boundaries[$boundaryIndex + 1]['lexemeKey'] - 1; + $lexemes->seek($boundaries[$boundaryIndex]['lexemeLastKey'] + 1); + while ($lexemes->current()->hasType(LexemeType::newLine())) { + $this->seekNextLexeme($lexemes); + } + + $this->extractMimeResponse($lexemes, $message, $endOfBoundary, $boundaries[$boundaryIndex]); + + + // seek to endOfBoundary + ++$boundaryIndex; + } + + // handle last boundary + $lexemes->next(); + } + + /** + * @param $haystack + * @param $fieldValue + * @param $attributes + */ + /** + * @param $haystack + * @param $fieldValue + * @param $attributes + */ + private function extractFieldAttributes(&$haystack, &$fieldValue, &$attributes) + { + $commentPosition = strpos($haystack, ';'); + if ($commentPosition !== false) { + // has attributes + $fieldValue = trim(substr($haystack, 1, $commentPosition - 1), "\x20\x09\"'"); + $commentString = substr($haystack, $commentPosition); + $comments = explode(' ', trim($commentString, "\x20")); + foreach ($comments as $comment) { + $equalPosition = strpos($comment, '='); + + if ($equalPosition !== false) { + $attribute = explode('=', $comment); + $value = trim($attribute[1], "\x20\x09\"';"); + // fix for '"utf-8";' + $value = trim($value, "\"'<>"); + $attributes[$attribute[0]] = $value; + } + } + } else { + // no attributes available + $fieldValue = trim($haystack, "\x20\x09\"';"); + } + } + + /** + * @param LexemeList $lexemes + * @param Message $message + * @param $endOfBoundary + * @param $boundary + * @return LexemeList|void + * @throws \Exception + */ + private function extractMimeResponse(LexemeList &$lexemes, Message &$message, $endOfBoundary, $boundary) + { + // mime headers inImapion + $contentTransferEncoding = '8bit'; + $contentTransferEncodingAttributes = array(); + $contentDisposition = false; + $contentDispositionAttributes = array(); + $contentType = ''; + $contentTypeAttributes = array(); + $contentId = ''; + $charset = 'ASCII'; + + + while ($lexemes->valid() && $lexemes->key() < $endOfBoundary) { + + if ($lexemes->current()->hasType(LexemeType::fieldHeader())) { + $fieldName = $lexemes->current()->toString(); + $this->seekNextLexeme($lexemes); + Assert::is($lexemes->current()->hasType(LexemeType::fieldBody()), 'Message Interpreter: expected a field value'); + $fieldValue = $lexemes->current()->toString(); + // detect Content-Transfer-Encoding header + // detect Content-Disposition: attachment for attachments and inline attachments + if ($fieldName === 'Content-Type') { + $this->extractFieldAttributes($fieldValue, $contentType, $contentTypeAttributes); + } elseif ($fieldName === 'Content-Disposition') { + $this->extractFieldAttributes($fieldValue, $contentDisposition, $contentDispositionAttributes); + } elseif ($fieldName === 'Content-Transfer-Encoding') { + $this->extractFieldAttributes($fieldValue, $contentTransferEncoding, $contentTransferEncodingAttributes); + } elseif ($fieldName === 'Content-ID') { + $this->extractFieldAttributes($fieldValue, $contentId, $contentTransferEncodingAttributes); + $contentId = trim($contentId, '<>'); + } + + // decode/convert Content-Type charset + if (!empty($contentTypeAttributes) && isset($contentTypeAttributes['charset'])) { + $charset = $contentTypeAttributes['charset']; + + // fix lower case Imaps to be upper case + if ($charset === 'utf-8') { + $charset = $contentTypeAttributes['charset'] = 'UTF-8'; + } + + // fix ascii + if ($charset === 'us-ascii') { + $charset = $contentTypeAttributes['charset'] = 'ASCII'; + } + } elseif (!empty($contentDispositionAttributes) && isset($contentDispositionAttributes['charset'])) { + $charset = $contentTypeAttributes['charset']; + + // fix lower case Imaps to be upper case + if ($charset === 'utf-8') { + $charset = $contentTypeAttributes['charset'] = 'UTF-8'; + } + + // fix ascii + if ($charset === 'us-ascii') { + $charset = $contentTypeAttributes['charset'] = 'ASCII'; + } + } else { + $charset = 'ASCII'; + } + $this->seekNextLexeme($lexemes); + } else { + // select mime Response content + $first = $lexemes->current()->offsetGet(0)->first(); + $last = $lexemes->offsetGet($endOfBoundary)->offsetGet(0)->first(); + $lexemeIterator = $lexemes->current()->offsetGet(0)->getInnerIterator(); + $contentIterator = StringIterator::withStringIterator($lexemeIterator, $first, $last - $first); + $content = $contentIterator->toString(); + + // decode Content-Transfer-Encoding + if (trim($contentTransferEncoding) === '7bit') { + $content = imap_utf7_decode($contentTransferEncoding); + } elseif (trim($contentTransferEncoding) === '8bit') { + // nothing to do + } elseif (trim($contentTransferEncoding) === 'binary') { + // nothing to do + } elseif (trim($contentTransferEncoding) === 'quoted-printable') { + $content = quoted_printable_decode($content); + } elseif (trim($contentTransferEncoding) === 'base64') { + $content = imap_base64($content); + } else { + throw new \Exception('Content-Transfer-Encoding: "' . $contentTransferEncoding . '" is not supported'); + } + + Assert::is(in_array($charset, mb_list_encodings()), 'Charset: "' . $charset . '" is not supported. ' . 'supported: ' . implode(' ', mb_list_encodings())); + $content = mb_convert_encoding($content, 'UTF-8', $charset); + + // assign to body Response of the message class + // decode Content-Disposition: attachment; filename="example.html" + if ($contentDisposition === false) { + // text/html body + if ($boundary['type'] === 'open' && $boundary['scope'] === 'outside' & $contentType === 'text/plain') { + // plain text body + $message->body()->offsetSet('text', $content); + } else { + // html body + $message->body()->offsetSet('html', $content); + } + } elseif ($contentDisposition === 'inline') { + // inline attachment + $attachment = $this->buildAttachment(); + $attachment->offsetSet('content', $content); + $attachment->offsetSet('hasContent', true); + $attachment->offsetSet('isInline', true); + $attachment->offsetSet('contentId', $contentId); + $attachment->structure()->offsetSet('type', $contentType); + $attachment->structure()->offsetSet('size', strlen($content)); + $message->body()->offsetSet('attachments', $attachment); + } elseif ($contentDisposition === 'attachment') { + // attachment + $attachment = $this->buildAttachment(); + $attachment->offsetSet('content', $content); + $attachment->offsetSet('hasContent', true); + $attachment->offsetSet('isInline', false); + + if (array_key_exists('filename', $contentDispositionAttributes)) { + $attachment->structure()->offsetSet('name', $contentDispositionAttributes['filename']); + } elseif (array_key_exists('name', $contentTypeAttributes)) { + $attachment->structure()->offsetSet('name', $contentTypeAttributes['name']); + } + + $attachment->structure()->offsetSet('type', $contentType); + $attachment->structure()->offsetSet('size', strlen($content)); + $attachment->offsetSet('contentId', $contentId); + $message->body()->offsetSet('attachments', $attachment); + } else { + throw new \Exception('Content-Disposition: ' . $contentDisposition . ' is not supported'); + } + return; + + } + + $this->seekNextLexeme($lexemes); + } + } + + /** + * @return MessageAttachment + * @throws \Exception + */ + private function buildAttachment() + { + $attachment = new MessageAttachment(); + $attachment->offsetSet('structure', new MessageAttachmentStructure()); + return $attachment; + } +} diff --git a/src/Imap/Interpreter/SearchInterpreter.php b/src/Imap/Interpreter/SearchInterpreter.php new file mode 100755 index 0000000..df620fa --- /dev/null +++ b/src/Imap/Interpreter/SearchInterpreter.php @@ -0,0 +1,60 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Interpreter; + + +use SalesAgility\Imap\Lexeme\LexemeType; +use SalesAgility\Imap\Lexeme\Lexemizer; +use SalesAgility\Imap\Response\MessageFactory; +use SalesAgility\Imap\Response\Message; +use SalesAgility\Imap\Response\MessageList; +use SalesAgility\Imap\Token\Tokenizer; +use SalesAgility\Iteration\StringIterator; + +class SearchInterpreter implements StringIteratorInterpreter +{ + /** + * @param StringIterator $iterator + * @return array|MessageList + * @throws \SalesAgility\Imap\Token\TokenException + */ + public function parse(StringIterator $iterator) + { + $messageList = new MessageList(); + + $tokenizer = new Tokenizer(); + $lexemizer = new Lexemizer(); + $lexemeInterpreter = new LexemeInterpreter(); + $tokens = $tokenizer->parseWithoutLineRestrictions($iterator); + $lexemes = $lexemizer->parse($tokens); + + while ($lexemes->valid()) { + if ($lexemeInterpreter->isNumber($lexemes)) { + $message = MessageFactory::instance(); + $message->offsetSet('number', $lexemes->current()->toString()); + $messageList[] = $message; + } + + $lexemes->next(); + } + + return $messageList; + } +} \ No newline at end of file diff --git a/src/Imap/Interpreter/StringIteratorInterpreter.php b/src/Imap/Interpreter/StringIteratorInterpreter.php new file mode 100755 index 0000000..39578f6 --- /dev/null +++ b/src/Imap/Interpreter/StringIteratorInterpreter.php @@ -0,0 +1,28 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Interpreter; + + +use SalesAgility\Iteration\StringIterator; + +interface StringIteratorInterpreter +{ + public function parse(StringIterator $interpreter); +} \ No newline at end of file diff --git a/src/Imap/Lexeme/Lexeme.php b/src/Imap/Lexeme/Lexeme.php new file mode 100755 index 0000000..c6cf692 --- /dev/null +++ b/src/Imap/Lexeme/Lexeme.php @@ -0,0 +1,235 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Lexeme; + +use SalesAgility\Imap\Token\Token; +use SalesAgility\Imap\Token\TokenList; + +/** + * Class Lexeme + * @package SalesAgility\Imap\Lexeme + * Higher level tokens and rules + * @see https://www.ietf.org/rfc/rfc2822.txt + */ +class Lexeme implements LexemeIteratorInterface +{ + /** @var TokenList $iterator */ + private $iterator; + + /** @var int $first */ + private $first; + + /** @var int $current */ + private $current; + + /** @var int $last */ + private $last; + + /** @var int $length */ + private $length; + + /** + * @var Token[] $tokenList + */ + private $tokenList = array(); + + /** @var LexemeType[] $type */ + private $types = array(); + + /** + * @var int used to validate the size of headers and bodies + * @see https://tools.ietf.org/html/rfc3501#page-16 + */ + private $octetCount = 0; + + /** + * Token constructor. + */ + public function __construct() + { + $this->iterator = new TokenList(); + } + + /** + * @param LexemeType $type + * @return bool + */ + public function hasType(LexemeType $type) + { + foreach ($this->types as $t) { + if ($t->isType($type)) { + return true; + } + } + + return false; + } + + /** + * @param LexemeType $type + */ + public function addType(LexemeType $type) + { + $this->types[] = $type; + } + + + /** + * @param Token $token + */ + public function addToken(Token $token) + { + $this->tokenList[] = $token; + if ($this->current === null) { + $this->current = 0; + } + + if ($this->first === null) { + $this->first = 0; + } + + if ($this->length === null) { + $this->length = 0; + } + + ++$this->length; + + if ($this->last === null) { + $this->last = -1; + } + + ++$this->last; + } + + /** + * @return string + */ + public function toString() + { + $string = ''; + /** @var Token $token */ + foreach ($this->tokenList as $token) { + $string .= $token->toString(); + } + + return $string; + } + + /** + * Return the current element + * @return Token + */ + public function current() + { + return $this->tokenList[$this->current]; + } + + /** + * Move forward to next element + */ + public function next() + { + $this->current += 1; + } + + /** + * Return the key of the current element + * @return int + */ + public function key() + { + return $this->current; + } + + /** + * Checks if current position is valid + * @return bool + */ + public function valid() + { + return $this->length > 0 + && $this->current <= $this->last + && ($this->current - $this->first) < $this->length; + } + + /** + * Rewind the Iterator to the first element + */ + public function rewind() + { + $this->current = $this->first; + } + + /** + * Fast forward the iterator to the last element + */ + public function fastForward() + { + $this->current = $this->last; + } + + /** + * @param int pos + * @return bool|int + */ + public function seek($pos) + { + $cpos = $this->current; + $this->current = $pos; + if (!$this->valid()) { + $this->current = $cpos; + return false; + } + + return $pos; + } + + /** + * @param int $offset + * @return Token + */ + public function offsetGet($offset) + { + return $this->tokenList[$offset]; + } + + + /** + * @return int + */ + public function octetCount() + { + return $this->octetCount; + } + + /** + * @param int $octets + */ + public function addOctets($octets) + { + if ($this->hasType(LexemeType::group())) { + $this->octetCount = 2; + } elseif ($this->hasType(LexemeType::newLine())) { + $this->octetCount = 2; + } else { + $this->octetCount += $octets; + } + } +} diff --git a/src/Imap/Lexeme/LexemeIteratorInterface.php b/src/Imap/Lexeme/LexemeIteratorInterface.php new file mode 100755 index 0000000..e336b5a --- /dev/null +++ b/src/Imap/Lexeme/LexemeIteratorInterface.php @@ -0,0 +1,30 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Lexeme; + +/** + * Interface LexemeIterator + * @package SalesAgility\Imap\Lexeme + */ +interface LexemeIteratorInterface extends \Iterator +{ + +} \ No newline at end of file diff --git a/src/Imap/Lexeme/LexemeList.php b/src/Imap/Lexeme/LexemeList.php new file mode 100755 index 0000000..be8d7bc --- /dev/null +++ b/src/Imap/Lexeme/LexemeList.php @@ -0,0 +1,142 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Lexeme; + +/** + * Class LexemeList + * @package SalesAgility\Imap\Lexeme + * A list of \SalesAgility\Imap\Lexeme\Lexeme objects + */ +class LexemeList implements LexemeIteratorInterface, \ArrayAccess +{ + private $lexemeList = array(); + private $currentKey = 0; + + /** + * @return Lexeme + */ + public function current() + { + return $this->lexemeList[$this->currentKey]; + } + + /** + * Move key to next postion + */ + public function next() + { + ++$this->currentKey; + } + + /** + * @return int key position in list + */ + public function key() + { + return $this->currentKey; + } + + /** + * @return bool if key is within range + */ + public function valid() + { + return $this->currentKey < count($this->lexemeList); + } + + /** + * set the key to the first position + */ + public function rewind() + { + $this->currentKey = 0; + } + + /** + * @param int $offset + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->lexemeList); + } + + /** + * @param int $offset + * @return Lexeme + */ + public function offsetGet($offset) + { + return $this->lexemeList[$offset]; + } + + /** + * @param int $offset + * @param Lexeme $value + * @throws \InvalidArgumentException + */ + public function offsetSet($offset, $value) + { + + $newOffset = false; + if ($offset === null) { + $newOffset = true; + } elseif (gettype($offset) !== "integer") { + throw new \InvalidArgumentException('Lexeme List can only store integer key values'); + } + + if ($value instanceof Lexeme) { + if (!$newOffset) { + $this->lexemeList[$offset] = $value; + } else { + $count = count($this->lexemeList); + if ($count === 0) { + $this->lexemeList[0] = $value; + } else { + $this->lexemeList[$count] = $value; + } + } + } else { + throw new \InvalidArgumentException('Lexeme List can only store values which derive from a Lexeme'); + } + } + + /** + * @param int $offset + */ + public function offsetUnset($offset) + { + unset($this->lexemeList[$offset]); + } + + /** + * @param $offset + */ + public function seek($offset) + { + $this->currentKey = $offset; + } + + public function fastForward() + { + $this->currentKey = count($this->lexemeList) - 1; + } +} diff --git a/src/Imap/Lexeme/LexemeType.php b/src/Imap/Lexeme/LexemeType.php new file mode 100755 index 0000000..6afcf5b --- /dev/null +++ b/src/Imap/Lexeme/LexemeType.php @@ -0,0 +1,1127 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Lexeme; + + +/** + * Class LexemeType + * @package SalesAgility\Imap\Lexeme + * Higher level tokens + */ +class LexemeType +{ + const ALL_CAPITAL_LETTERS = 1001; + const ALL_NUMBERS = 1002; + const CAPITAL_LETTERS_NUMBERS = 1003; + const CTEXT = 1004; + const CCONTENT = 1005; + const COMMENT = 1006; + const CFWS = 1007; + const ATEXT = 1008; + const ATOM = 1009; + const DOT_ATOM_TEXT = 10010; + const DOT_ATOM = 10011; + const QTEXT = 10012; + const QCONTENT = 10013; + const QUOTED_STRING = 10014; + const WORD = 10015; + const PHRASE = 10016; + const UTEXT = 10017; + const DATE_TIME = 10018; + const DAY_OF_WEEK = 10019; + const DAY_NAME = 10020; + const DATE = 10021; + const YEAR = 10022; + const MONTH = 10023; + const MONTH_NAME = 10024; + const DAY = 10025; + const TIME = 10026; + const TIME_OF_DAY = 10027; + const HOUR = 10028; + const MINUTE = 10029; + const SECOND = 10030; + const ZONE = 10031; + const ADDRESS = 10032; + const MAILBOX = 10033; + const NAME_ADDR = 10034; + const ANGLE_ADDR = 10035; + const GROUP_ADDR = 10036; + const DISPLAY_NAME = 10037; + const MAILBOX_LIST = 10038; + const ADDRESS_LIST = 10040; + const ADDR_SPEC = 10041; + const LOCAL_PART = 10042; + const DOMAIN = 10043; + const DOMAIN_LITERAL = 10044; + const DCONTENT = 10045; + const DTEXT = 10046; + const MESSAGE = 10047; + const BODY = 10048; + const FIELD_HEADER = 10049; + const FIELD_BODY = 10050; + const SYSTEM_FLAG = 10051; + const FLAG_CLASS = 10052; + const FLAG_TYPE = 10053; + const SYSTEM_RESPONSE = 10054; + const COMMAND = 10055; + const SYSTEM_CODE = 10056; + const RESPONSE = 10057; + const ATTRIBUTE = 10058; + const MAILBOX_NAME_ATTRIBUTE = 10059; + const SPECIFICATION_REQUIREMENT_TERM = 10060; + const MESSAGE_FIELD = 10061; + const PART_SPECIFIER = 10062; + const STATUS_ITEM = 10063; + const OPTIONAL = 10064; + const GROUP = 10065; + const NEWLINE = 10066; + const WHITESPACE = 10067; + + private $type; + + /** + * LexemeType Constructor + * @param int type + * + */ + private function __construct($type) + { + $this->type = $type; + } + + /** + * @param LexemeType $type + * @return bool + */ + public function isType(LexemeType $type) + { + return $type->type === $this->type; + } + + /** + * @return LexemeType + */ + public static function allCapitals() + { + return new self(self::ALL_CAPITAL_LETTERS); + } + + /** + * @return bool + */ + public function isAllCapitals() + { + return $this->type === self::ALL_CAPITAL_LETTERS; + } + + /** + * @return LexemeType + */ + public static function allNumbers() + { + return new self(self::ALL_NUMBERS); + } + + /** + * @return bool + */ + public function isAllNumbers() + { + return $this->type === self::ALL_NUMBERS; + } + + /** + * @return LexemeType + */ + public static function capitalsNumbers() + { + return new self(self::CAPITAL_LETTERS_NUMBERS); + } + + /** + * @return bool + */ + public function isCapitalsNumbers() + { + return $this->type === self::CAPITAL_LETTERS_NUMBERS; + } + + /** + * @return LexemeType + */ + public static function ctext() + { + return new self(self::CTEXT); + } + + /** + * @return bool + */ + public function isCtext() + { + return $this->type === self::CTEXT; + } + + /** + * @return LexemeType + */ + public static function ccontent() + { + return new self(self::CCONTENT); + } + + /** + * @return bool + */ + public function isCcontent() + { + return $this->type === self::CCONTENT; + } + + /** + * @return LexemeType + */ + public static function comment() + { + return new self(self::COMMENT); + } + + /** + * @return bool + */ + public function isComment() + { + return $this->type === self::COMMENT; + } + + /** + * @return LexemeType + */ + public static function cfws() + { + return new self(self::CFWS); + } + + /** + * @return bool + */ + public function isCfws() + { + return $this->type === self::CFWS; + } + + /** + * @return LexemeType + */ + public static function atext() + { + return new self(self::ATEXT); + } + + /** + * @return bool + */ + public function isAtext() + { + return $this->type === self::ATEXT; + } + + /** + * @return LexemeType + */ + public static function atom() + { + return new self(self::ATOM); + } + + /** + * @return bool + */ + public function isAtom() + { + return $this->type === self::ATOM; + } + + /** + * @return LexemeType + */ + public static function dotAtomText() + { + return new self(self::DOT_ATOM_TEXT); + } + + /** + * @return bool + */ + public function isDotAtomText() + { + return $this->type === self::DOT_ATOM_TEXT; + } + + /** + * @return LexemeType + */ + public static function qtext() + { + return new self(self::QTEXT); + } + + /** + * @return bool + */ + public function isQtext() + { + return $this->type === self::QTEXT; + } + + /** + * @return LexemeType + */ + public static function qcontent() + { + return new self(self::QCONTENT); + } + + /** + * @return bool + */ + public function isQcontent() + { + return $this->type === self::QCONTENT; + } + + /** + * @return LexemeType + */ + public static function quotedString() + { + return new self(self::QUOTED_STRING); + } + + /** + * @return bool + */ + public function isQuotedString() + { + return $this->type === self::QUOTED_STRING; + } + + /** + * @return LexemeType + */ + public static function word() + { + return new self(self::WORD); + } + + /** + * @return bool + */ + public function isWord() + { + return $this->type === self::WORD; + } + + /** + * @return LexemeType + */ + public static function phrase() + { + return new self(self::PHRASE); + } + + /** + * @return bool + */ + public function isPhrase() + { + return $this->type === self::PHRASE; + } + + /** + * @return LexemeType + */ + public static function utext() + { + return new self(self::UTEXT); + } + + /** + * @return bool + */ + public function isUtext() + { + return $this->type === self::UTEXT; + } + + /** + * @return LexemeType + */ + public static function dateTime() + { + return new self(self::DATE_TIME); + } + + /** + * @return bool + */ + public function isDateTime() + { + return $this->type === self::DATE_TIME; + } + + /** + * @return LexemeType + */ + public static function dayOfWeek() + { + return new self(self::DAY_OF_WEEK); + } + + /** + * @return bool + */ + public function isDayOfWeek() + { + return $this->type === self::DAY_OF_WEEK; + } + + /** + * @return LexemeType + */ + public static function dayName() + { + return new self(self::DAY_NAME); + } + + /** + * @return bool + */ + public function isDayName() + { + return $this->type === self::DAY_NAME; + } + + /** + * @return LexemeType + */ + public static function date() + { + return new self(self::DATE); + } + + /** + * @return bool + */ + public function isDate() + { + return $this->type === self::DATE; + } + + /** + * @return LexemeType + */ + public static function year() + { + return new self(self::YEAR); + } + + /** + * @return bool + */ + public function isYear() + { + return $this->type === self::YEAR; + } + + /** + * @return LexemeType + */ + public static function month() + { + return new self(self::MONTH); + } + + /** + * @return bool + */ + public function isMonth() + { + return $this->type === self::MONTH; + } + + /** + * @return LexemeType + */ + public static function monthName() + { + return new self(self::MONTH_NAME); + } + + /** + * @return bool + */ + public function isMonthName() + { + return $this->type === self::MONTH_NAME; + } + + /** + * @return LexemeType + */ + public static function day() + { + return new self(self::DAY); + } + + /** + * @return bool + */ + public function isDay() + { + return $this->type === self::DAY; + } + + /** + * @return LexemeType + */ + public static function time() + { + return new self(self::TIME); + } + + /** + * @return bool + */ + public function isTime() + { + return $this->type === self::TIME; + } + + /** + * @return LexemeType + */ + public static function timeOfDay() + { + return new self(self::TIME_OF_DAY); + } + + /** + * @return bool + */ + public function isTimeOfDay() + { + return $this->type === self::TIME_OF_DAY; + } + + /** + * @return LexemeType + */ + public static function hour() + { + return new self(self::HOUR); + } + + /** + * @return bool + */ + public function isHour() + { + return $this->type === self::HOUR; + } + + /** + * @return LexemeType + */ + public static function minute() + { + return new self(self::MINUTE); + } + + /** + * @return bool + */ + public function isMinute() + { + return $this->type === self::MINUTE; + } + + /** + * @return LexemeType + */ + public static function second() + { + return new self(self::SECOND); + } + + /** + * @return bool + */ + public function isSecond() + { + return $this->type === self::SECOND; + } + + /** + * @return LexemeType + */ + public static function zone() + { + return new self(self::ZONE); + } + + /** + * @return bool + */ + public function isZone() + { + return $this->type === self::ZONE; + } + + /** + * @return LexemeType + */ + public static function address() + { + return new self(self::ADDRESS); + } + + /** + * @return bool + */ + public function isAddress() + { + return $this->type === self::ADDRESS; + } + + /** + * @return LexemeType + */ + public static function mailbox() + { + return new self(self::MAILBOX); + } + + /** + * @return bool + */ + public function isMailbox() + { + return $this->type === self::MAILBOX; + } + + /** + * @return LexemeType + */ + public static function nameAddress() + { + return new self(self::NAME_ADDR); + } + + /** + * @return bool + */ + public function isNameAddress() + { + return $this->type === self::NAME_ADDR; + } + + /** + * @return LexemeType + */ + public static function angleAddressPair() + { + return new self(self::ANGLE_ADDR); + } + + /** + * @return bool + */ + public function isAngleAddressPair() + { + return $this->type === self::ANGLE_ADDR; + } + + /** + * @return LexemeType + */ + public static function groupAddress() + { + return new self(self::GROUP_ADDR); + } + + /** + * @return bool + */ + public function isGroupAddress() + { + return $this->type === self::GROUP_ADDR; + } + + /** + * @return LexemeType + */ + public static function displayName() + { + return new self(self::DISPLAY_NAME); + } + + /** + * @return bool + */ + public function isDisplayName() + { + return $this->type === self::DISPLAY_NAME; + } + + /** + * @return LexemeType + */ + public static function mailboxList() + { + return new self(self::MAILBOX_LIST); + } + + /** + * @return bool + */ + public function isMailboxList() + { + return $this->type === self::MAILBOX_LIST; + } + + /** + * @return LexemeType + */ + public static function addressList() + { + return new self(self::ADDRESS_LIST); + } + + /** + * @return bool + */ + public function isAddressList() + { + return $this->type === self::ADDRESS_LIST; + } + + /** + * @return LexemeType + */ + public static function addressSpec() + { + return new self(self::ADDR_SPEC); + } + + /** + * @return bool + */ + public function isAddressSpec() + { + return $this->type === self::ADDR_SPEC; + } + + /** + * @return LexemeType + */ + public static function localPart() + { + return new self(self::LOCAL_PART); + } + + /** + * @return bool + */ + public function isLocalPart() + { + return $this->type === self::LOCAL_PART; + } + + /** + * @return LexemeType + */ + public static function domain() + { + return new self(self::DOMAIN); + } + + /** + * @return bool + */ + public function isDomain() + { + return $this->type === self::DOMAIN; + } + + /** + * @return LexemeType + */ + public static function domainLiteral() + { + return new self(self::DOMAIN_LITERAL); + } + + /** + * @return bool + */ + public function isDomainLiteral() + { + return $this->type === self::DOMAIN_LITERAL; + } + + /** + * @return LexemeType + */ + public static function dcontent() + { + return new self(self::DCONTENT); + } + + /** + * @return bool + */ + public function isDcontent() + { + return $this->type === self::DCONTENT; + } + + /** + * @return LexemeType + */ + public static function dtext() + { + return new self(self::DTEXT); + } + + /** + * @return bool + */ + public function isDtext() + { + return $this->type === self::DTEXT; + } + + /** + * @return LexemeType + */ + public static function message() + { + return new self(self::MESSAGE); + } + + /** + * @return bool + */ + public function isMessage() + { + return $this->type === self::MESSAGE; + } + + /** + * @return LexemeType + */ + public static function fieldHeader() + { + return new self(self::FIELD_HEADER); + } + + /** + * @return bool + */ + public function isFieldHeader() + { + return $this->type === self::FIELD_HEADER; + } + + /** + * @return LexemeType + */ + public static function fieldBody() + { + return new self(self::FIELD_BODY); + } + + /** + * @return bool + */ + public function isFieldBody() + { + return $this->type === self::FIELD_BODY; + } + + /** + * @return LexemeType + */ + public static function systemFlag() + { + return new self(self::SYSTEM_FLAG); + } + + /** + * @return bool + */ + public function isSystemFlag() + { + return $this->type === self::SYSTEM_FLAG; + } + + /** + * @return LexemeType + */ + public static function flagClass() + { + return new self(self::FLAG_CLASS); + } + + /** + * @return bool + */ + public function isFlagClass() + { + return $this->type === self::FLAG_CLASS; + } + + /** + * @return LexemeType + */ + public static function flagType() + { + return new self(self::FLAG_TYPE); + } + + /** + * @return bool + */ + public function isFlagType() + { + return $this->type === self::FLAG_TYPE; + } + + /** + * @return LexemeType + */ + public static function systemResponse() + { + return new self(self::SYSTEM_RESPONSE); + } + + /** + * @return bool + */ + public function isSystemResponse() + { + return $this->type === self::SYSTEM_RESPONSE; + } + + /** + * @return LexemeType + */ + public static function command() + { + return new self(self::COMMAND); + } + + /** + * @return bool + */ + public function isCommand() + { + return $this->type === self::COMMAND; + } + + /** + * @return LexemeType + */ + public static function systemCode() + { + return new self(self::SYSTEM_CODE); + } + + /** + * @return bool + */ + public function isSystemCode() + { + return $this->type === self::SYSTEM_CODE; + } + + /** + * @return LexemeType + */ + public static function attribute() + { + return new self(self::ATTRIBUTE); + } + + /** + * @return bool + */ + public function isAttribute() + { + return $this->type === self::ATTRIBUTE; + } + + /** + * @return LexemeType + */ + public static function mailboxNameAttribute() + { + return new self(self::MAILBOX_NAME_ATTRIBUTE); + } + + /** + * @return bool + */ + public function isMailboxNameAttribute() + { + return $this->type === self::MAILBOX_NAME_ATTRIBUTE; + } + + /** + * @return LexemeType + */ + public static function specificationRequirementTerm() + { + return new self(self::SPECIFICATION_REQUIREMENT_TERM); + } + + /** + * @return bool + */ + public function isSpecificationRequirementTerm() + { + return $this->type === self::SPECIFICATION_REQUIREMENT_TERM; + } + + /** + * @return LexemeType + */ + public static function messageField() + { + return new self(self::MESSAGE_FIELD); + } + + /** + * @return bool + */ + public function isMessageField() + { + return $this->type === self::MESSAGE_FIELD; + } + + /** + * @return LexemeType + */ + public static function partSpecifier() + { + return new self(self::PART_SPECIFIER); + } + + /** + * @return bool + */ + public function isPartSpecifier() + { + return $this->type === self::PART_SPECIFIER; + } + + /** + * @return LexemeType + */ + public static function statusItem() + { + return new self(self::STATUS_ITEM); + } + + /** + * @return bool + */ + public function isStatusItem() + { + return $this->type === self::STATUS_ITEM; + } + + /** + * @return LexemeType + */ + public static function group() + { + return new self(self::GROUP); + } + + /** + * @return bool + */ + public function isGroup() + { + return $this->type === self::GROUP; + } + + /** + * @return LexemeType + */ + public static function optional() + { + return new self(self::OPTIONAL); + } + + /** + * @return bool + */ + public function isOptional() + { + return $this->type === self::OPTIONAL; + } + + /** + * @return LexemeType + */ + public static function newLine() + { + return new self(self::NEWLINE); + } + + /** + * @return bool + */ + public function isNewLine() + { + return $this->type === self::NEWLINE; + } + + /** + * @return LexemeType + */ + public static function whitespace() + { + return new self(self::WHITESPACE); + } + + /** + * @return bool + */ + public function isWhitespace() + { + return $this->type === self::WHITESPACE; + } + // STATUS ITEM +} diff --git a/src/Imap/Lexeme/Lexemizer.php b/src/Imap/Lexeme/Lexemizer.php new file mode 100755 index 0000000..79f6e76 --- /dev/null +++ b/src/Imap/Lexeme/Lexemizer.php @@ -0,0 +1,347 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Lexeme; + +use SalesAgility\Imap\Token\Token; +use SalesAgility\Imap\Token\Tokenizer; +use SalesAgility\Imap\Token\TokenList; +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Utility\Assert; + +/** + * Class Lexemizer + * @package SalesAgility\Imap\Lexeme + * @see https://www.ietf.org/rfc/rfc2822.txt + * Higher level parser to aid in better interpretation + */ +class Lexemizer +{ + private $skippedOctets = 0; + + /** + * @param TokenList $tokens + * @return LexemeList + * @throws \SalesAgility\Imap\Token\TokenException + */ + public function parse(TokenList $tokens) + { + $lexemeList = new LexemeList(); + + foreach ($tokens as $tokenIndex => $token) { + + // reduce protocol noise + if ($token->type()->isFoldingWhiteSpace()) { + $this->skipOctets($token->count()); + $ref = $lexemeList->current(); + $this->addSkippedOctets($ref); + continue; + } + + if ($token->type()->isControlCharacter()) { + $this->skipOctets($token->count()); + continue; + } + + // Error Detection / Correction + if ($token->type()->isRecommendedLineLength()) { + $this->skipOctets($token->count()); + continue; + } + + if ($token->type()->isRequiredLineLength()) { + $this->skipOctets($token->count()); + continue; + } + + if ($token->type()->isEndOfLine()) { + $lexemeList[] = $this->fromToken($token, [LexemeType::newLine()]); + continue; + } + + if ($token->type()->isWhiteSpace()) { + $lexemeList[] = $this->fromToken($token, [LexemeType::whitespace()]); + continue; + } + + // recursively tokenize paired items + if ($token->type()->isGroup()) { + // mark the beginning and end of each group + // tokenize the insides of that group + $this->seekGroup($tokens, $lexemeList); + continue; + } + + if ($token->type()->isQuoted()) { + $lexemeList[] = $this->fromTokens($tokens, $tokenIndex, 1, [LexemeType::quotedString(), LexemeType::atom()]); + continue; + } + + if ($token->type()->isNonFoldedLiteral()) { + // is optional? + $lexemeList[] = $this->fromTokens($tokens, $tokenIndex, 1, [LexemeType::optional()]); + continue; + } + + $lexemeTypes = array(); + $a = ord('a'); + $z = ord('z'); + $A = ord('A'); + $Z = ord('Z'); + + $hasCapitals = false; + $hasLowerCase = false; + $hasNumeric = false; + $hasSpecial = false; + // Might contain an attribute + foreach ($token as $c => $character) { + $ord = ord($character); + if ($ord >= $A && $character <= $Z) { + $hasCapitals = true; + } + + if (is_numeric($character)) { + $hasNumeric = true; + } + + if ($ord >= $a && $character <= $z) { + $hasLowerCase = true; + } + + if ($token->type()->isSpecial()) { + $hasSpecial = true; + } + } + + if ($hasSpecial) { + if ($token->toString() === ':') { + $field = $this->seekField($lexemeList, $tokens); + if ($field !== false) { + $lexemeList[] = $field; + // New line marks the end of a field + // however the next field needs to detect the previous line + // so we need to split out the new line + $lexemeList[] = $this->fromToken($tokens->current(), [LexemeType::newLine()]); + continue; + } + } + } elseif (!$hasCapitals && $hasNumeric && !$hasLowerCase) { + $lexemeTypes[] = LexemeType::allNumbers(); + } elseif ($hasCapitals && $hasNumeric && !$hasLowerCase) { + $lexemeTypes[] = LexemeType::capitalsNumbers(); + } elseif ($hasCapitals && !$hasNumeric && !$hasLowerCase) { + $lexemeTypes[] = LexemeType::allCapitals(); + } + + $lexemeTypes[] = LexemeType::atom(); + $lexemeList[] = $this->fromTokens($tokens, $tokenIndex, 1, $lexemeTypes); + } + + + return $lexemeList; + } + + /** + * @param TokenList $tokens + * @param LexemeList $lexemeList + * @throws \SalesAgility\Imap\Token\TokenException + */ + private function seekGroup(TokenList &$tokens, LexemeList &$lexemeList) + { + $lexemeList[] = $this->fromTokens($tokens, $tokens->key(), 1, [LexemeType::group(), LexemeType::atom()]); + + $token = $tokens->current(); + $start = $token->firstKey() + 1; + $end = $token->lastKey() - 1; + $innerString = $token->getInnerString(); + $insideGroup = new StringIterator($innerString, $start, $end - $start + 1); + + $tokenParser = new Tokenizer(); + $groupTokens = $tokenParser->parse($insideGroup); + // Recursively find groups and append them + $groupLexemes = $this->parse($groupTokens); + $this->append($lexemeList, $groupLexemes); +// // 1 for opening and closing parenthesis +// $this->skipOctets(2); + } + + /** + * @param LexemeList $lexemeList + * @param TokenList $tokenList + * @return bool|Lexeme + * @throws \Exception + * @see https://tools.ietf.org/html/rfc2822#page-18 + */ + private function seekField(LexemeList &$lexemeList, TokenList &$tokenList) + { + Assert::is($tokenList->current()->toString() === ':', ' TokenList::current() must be a colon'); + // There must be at least 4 tokens to make a field + if (!$tokenList->offsetExists(3)) { + return false; + } + + if ($tokenList->key() < 2) { + return false; + } + + $key = $tokenList->key(); + $newline = $tokenList->offsetGet($key - 2); + if (!$newline->type()->isEndOfLine()) { + return false; + } + + // fast forward the key() to the end + $lexemeList->fastForward(); + // Add field header to lexeme before the ":" + $lexemeList->offsetGet($lexemeList->key())->addType(LexemeType::fieldHeader()); + // ':' was skipped but we still need to record the octet + $this->skipOctets($tokenList->current()->count()); + + // seek body + $lexeme = new Lexeme(); + $tokenList->next(); + $lexeme->addType(LexemeType::fieldBody()); + while ($tokenList->valid()) { + + if ($tokenList->current()->type()->isFoldingWhiteSpace()) { + $this->skipOctets($tokenList->current()->count()); + $tokenList->next(); + continue; + } + + if ($tokenList->current()->type()->isEndOfLine()) { + break; + } + + $token = $tokenList->current(); + $this->addToken($lexeme, $token); + $tokenList->next(); + } + Assert::is($tokenList->valid(), 'lexemizer couldn\'t detect the end of the field'); + $this->addSkippedOctets($lexeme); + Assert::is($tokenList->current()->type()->isEndOfLine(), 'last token in a field must must be the end of line'); + return $lexeme; + } + + /** + * @param TokenList $tokens + * @param $offset + * @param $count + * @param array $types + * @return Lexeme + */ + private function fromTokens(TokenList $tokens, $offset, $count, array $types) + { + $lexeme = new Lexeme(); + + /** @var LexemeType $type */ + foreach ($types as $type) { + $lexeme->addType($type); + } + + $tokens->seek($offset); + + $countDown = $count; + while ($tokens->valid() && $countDown > 0) { + $token = $tokens->current(); + $this->addToken($lexeme, $token); + $tokens->next(); + --$countDown; + } + + // fix offset by 1 error when there is only 1 token + if ($count == 1) { + $tokens->seek($offset); + } + +// $this->addSkippedOctets($lexeme); + return $lexeme; + } + + /** + * @param Token $token + * @param array $types + * @return Lexeme + */ + private function fromToken(Token $token, array $types) + { + $lexeme = new Lexeme(); + + /** @var LexemeType $type */ + foreach ($types as $type) { + $lexeme->addType($type); + } + $this->addToken($lexeme, $token); + $this->addSkippedOctets($lexeme); + return $lexeme; + } + + /** + * @param LexemeList $a + * @param LexemeList $b + */ + /** + * @param LexemeList $a + * @param LexemeList $b + */ + private function append(LexemeList &$a, LexemeList &$b) + { + foreach ($b as $append) { + $a[] = $append; + } + } + + /** + * @param Lexeme $lexeme + * @param Token $token + */ + /** + * @param Lexeme $lexeme + * @param Token $token + */ + private function addToken(Lexeme &$lexeme, Token &$token) + { + $lexeme->addToken($token); + + $lexeme->addOctets($token->count()); + } + + /** + * @param $amount + * Keep a tally of how many octets have been skipped + */ + private function skipOctets($amount) + { + $this->skippedOctets += $amount; + } + + /** + * @param Lexeme $lexeme + */ + /** + * @param Lexeme $lexeme + */ + private function addSkippedOctets(Lexeme &$lexeme) + { + // Octet count must include tokens which have been skipped + $lexeme->addOctets($this->skippedOctets); + $this->skippedOctets = 0; + } +} \ No newline at end of file diff --git a/src/Imap/Lexeme/keywords.yaml b/src/Imap/Lexeme/keywords.yaml new file mode 100755 index 0000000..59ad468 --- /dev/null +++ b/src/Imap/Lexeme/keywords.yaml @@ -0,0 +1,268 @@ +command: + APPEND: + AUTHENTICATE: + responses: + OK: + attrbute: + - "authenticate completed, now in authenticated state" + BAD: + attribute: + - "authenticate failure: unsupported authentication mechanism, credentials rejected" + NO: + attribute: + - "command unknown or arguments invalid" + CAPABILITY: + response: + CAPABILITY: + attribute: + - "capability completed" + CHECK: + CLOSE: + COPY: + CREATE: + DELETE: + EXAMINE: + EXPUNGE: + FETCH: + response: + - FETCH + item: + - ALL + - FAST + - BODY + - BODY[HEADER] + - BODY[HEADER.FIELDS] + - BODY[HEADER.FIELDS.NOT] + - BODY[MIME] + - BODY[TEXT] + - BODY[1] + - BODY[2.1] + - BODY[2.2] + - BODY.PEAK + - BODYSTRUCTURE + - ENVELOPE + - FLAGS + - INTERNALDATE + - RFC822 + - RFC822.HEADER + - RFC822.SIZE + - RFC822.TEXT + - UID + result: + - BODY + - BODY[HEADER] + - BODY[HEADER.FIELDS] + - BODY[HEADER.FIELDS.NOT] + - BODY[MIME] + - BODY[TEXT] + - BODYSTRUCTURE + - ENVELOPE + - FLAGS + - INTERNALDATE + - RFC822 + - RFC822.HEADER + - RFC822.SIZE + - RFC822.TEXT + - UID + LIST: + LOGIN: + response: + OK: + attribute: + - "login completed, now in authenticated state" + NO: + attribute: + - "login failure: user name or password rejected" + BAD: + attribute: + - "command unknown or arguments invalid" + attribute: + - "Logged in" + - "(Success)" + LOGOUT: + response: + OK: + attribute: + "logout completed" + BYE: + BAD: + attribute: + - "command unknown or arguments invalid" + LSUB: + NOOP: + OK: + attrbute: + - "completed" + BAD: + attribute: + - "command unknown or arguments invalid" + RENAME: + SEARCH: + key: + - ALL + - ANSWERED + - BCC + - BEFORE + - BODY + - CC + - DELETED + - DRAFT + - FLAGGED + - FROM + - HEADER + - KEYWORD + - LARGER + - NEW + - NOT + - OLD + - ON + - OR + - RECENT + - SEEN + - SENTBEFORE + - SENTON + - SENTSINCE + - SINCE + - SMALLER + - SUBJECT + - TEXT + - TO + - UID + - UNANSWERED + - UNDELETED + - UNDRAFT + - UNFLAGGED + - UNKEYWORD + - UNSEEN + SELECT: + STARTTLS: + response: + OK: + attribute: + - "starttls completed, begin TLS negotiation" + BAD: + attribute: + - "command unknown or arguments invalid" + STATUS: + STORE: + item: + - FLAGS + - FLAGS.SILENT + - +FLAGS + - +FLAGS.SILENT + - -FLAGS + - -FLAGS.SILENT + SUBSCRIBE: + UID: +# X-: +systen code: +- ALERT +- BADCHARSET +- CAPABILITY +- PARSE +- PERMANENTFLAGS +- READ-ONLY +- READ-WRITE +- TRYCREATE +- UIDNEXT +- UIDVALIDITY +- UNSEEN +response: +- BAD +- BYE +- CAPABILITY +- EXISTS +- EXPUNGE +- FETCH +- FLAGS +- LIST +- LSUB +- NO +- OK +- PREAUTH +- RECENT +- SEARCH +- STATUS +system response: +- OK +- BAD +- NO +system flag: +- \Answered +- \Deleted +- \Draft +- \Flagged +- \Recent +- \Seen +flag class: +- Permanent Flag +- Session Flag +flag type: +- Keyword +- System Flag +mailbox name attribute: +- \Marked +- \Noinferiors +- \Noselect +- \Unmarked +specification requirement term: +- MAY +- MUST +- MUST NOT +- OPTIONAL +- REQUIRED +- SHOULD +- SHOULD NOT +message: + field: + # + - orig-date + - references + - comments + - keywords + - optional-field + # main fields + - Return-Path + - Received + - From + - Date + - To + - Reply-To + - Subject + - Message-ID + - MIME-Version + # Resent indication + - trace + - resent-date + - resent-from + - resent-sender + - resent-to + - resent-cc + - resent-bcc + - resent-msg-id + + mime field: + - Content-Type + - Content-ID + - Content-Transfer-Encoding + - Content-Disposition + part speficifier: + - HEADER + - HEADER.FIELDS + - HEADER.FIELDS.NOT + - MIME + - TEXT + attribute: + - "Body Structure" + - "Envelope Structure" + - "Flags" + - "Internal Date" + - "Message Sequence Number" + - "Unique Identifier (UID)" + - "[RFC-2822] Size" +status item: +- MESSAGES +- RECENT +- UIDNEXT +- UIDVALIDITY +- UNSEEN \ No newline at end of file diff --git a/src/Imap/Manager/ManagerInterface.php b/src/Imap/Manager/ManagerInterface.php new file mode 100755 index 0000000..21c3f34 --- /dev/null +++ b/src/Imap/Manager/ManagerInterface.php @@ -0,0 +1,55 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Manager; + +use SalesAgility\Imap\CommandBuilder\PimapSupportedTopLevelCommandsInterface; +use SalesAgility\Imap\Response\MessageList; +use SalesAgility\Imap\CommandBuilder\CommandBuildArgumentsInterface; +use SalesAgility\Imap\CommandBuilder\PhpImapExtensionSupportedTopLevelCommandsInterface; +use SalesAgility\Imap\Stream\CommandTransporterInterface; + +/** + * Interface ManagerInterface + * @package SalesAgility\Imap\Manager + */ +interface ManagerInterface +{ + /** + * @param CommandTransporterInterface $connection + */ + public function setTransporter(CommandTransporterInterface $connection); + + /** + * @return CommandTransporterInterface + */ + public function transporter(); + + /** + * @return PimapSupportedTopLevelCommandsInterface|PhpImapExtensionSupportedTopLevelCommandsInterface + */ + public function command(); + + /** + * @param CommandBuildArgumentsInterface $command + * @return MessageList + */ + public function run(CommandBuildArgumentsInterface $command); +} \ No newline at end of file diff --git a/src/Imap/Manager/PhpImapExtensionManager.php b/src/Imap/Manager/PhpImapExtensionManager.php new file mode 100755 index 0000000..d08608e --- /dev/null +++ b/src/Imap/Manager/PhpImapExtensionManager.php @@ -0,0 +1,118 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Manager; + + +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use SalesAgility\Imap\CommandBuilder\CommandBuildArgumentsInterface; +use SalesAgility\Imap\CommandBuilder\PhpImapExtensionCommandBuilder; +use SalesAgility\Imap\Pipeline\Pipeline; +use SalesAgility\Imap\Pipeline\PipeLineAwareInterface; +use SalesAgility\Imap\Pipeline\PipelineInterface; +use SalesAgility\Imap\Stream\CommandTransporterInterface; +use SalesAgility\Imap\Stream\PhpImapExtensionMessageTransporter; +use SalesAgility\Pattern\ContainerAwareInterface; + +/** + * Class PhpImapExtensionManager + * @package SalesAgility\Imap\Manager + */ +class PhpImapExtensionManager implements ManagerInterface, ContainerAwareInterface, PipeLineAwareInterface, LoggerAwareInterface +{ + /** @var LoggerInterface $log */ + private $log; + /** @var ContainerInterface $container */ + private $container; + /** @var PhpImapExtensionMessageTransporter */ + private $transporter; + /** @var Pipeline $pipeline */ + private $pipeline; + + /** + * PhpImapExtensionManager constructor. + * @param ContainerInterface $container + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * @param CommandTransporterInterface $transporter + */ + public function setTransporter(CommandTransporterInterface $transporter) + { + $this->transporter = $transporter; + } + + /** + * @return CommandTransporterInterface|PhpImapExtensionMessageTransporter + */ + public function transporter() + { + return $this->transporter; + } + + /** + * @return \SalesAgility\Imap\CommandBuilder\PhpImapExtensionSupportedTopLevelCommandsInterface + */ + public function command() + { + return PhpImapExtensionCommandBuilder::instance(); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return \SalesAgility\Imap\Response\Response + * @throws \ErrorException + * @throws \SalesAgility\Imap\ImapException + */ + public function run(CommandBuildArgumentsInterface $command) + { + return $this->transporter->transmitCommand($command); + } + + /** + * @return Pipeline + */ + public function pipeline() + { + return $this->pipeline; + } + + /** + * @param PipelineInterface $pipeline + */ + public function setPipeLine(PipelineInterface $pipeline) + { + $this->pipeline = $pipeline; + } + + /** + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->log = $logger; + } +} \ No newline at end of file diff --git a/src/Imap/Manager/PimapManager.php b/src/Imap/Manager/PimapManager.php new file mode 100755 index 0000000..7985657 --- /dev/null +++ b/src/Imap/Manager/PimapManager.php @@ -0,0 +1,257 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Manager; + +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use SalesAgility\Imap\Interpreter\ResponseInterpreter; +use SalesAgility\Imap\Interpreter\MailboxInterpreter; +use SalesAgility\Imap\Interpreter\MailboxListInterpreter; +use SalesAgility\Imap\Interpreter\MessageInterpreter; +use SalesAgility\Imap\Interpreter\SearchInterpreter; +use SalesAgility\Imap\Interpreter\StringIteratorInterpreter; +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Imap\CommandBuilder\CommandBuildArgumentsInterface; +use SalesAgility\Imap\CommandBuilder\PimapCommandBuilder; +use SalesAgility\Imap\CommandBuilder\PimapSupportedTopLevelCommandsInterface; +use SalesAgility\Imap\Stream\CommandTransporterInterface; +use SalesAgility\Imap\Pipeline\PipeLineAwareInterface; +use SalesAgility\Imap\Pipeline\PipelineInterface; +use SalesAgility\Pattern\ContainerAwareInterface; +use SalesAgility\Utility\StringValue; + +/** + * Class PimapManager + * @package SalesAgility\Imap\Manager + */ +class PimapManager implements ManagerInterface, ContainerAwareInterface, PipeLineAwareInterface, LoggerAwareInterface +{ + /** @var ContainerInterface $container */ + public $container; + /** @var CommandTransporterInterface $transporter */ + private $transporter; + /** @var LoggerInterface */ + private $log; + /** @var PipelineInterface */ + private $pipeline; + + /** + * PimapManager constructor. + * @param ContainerInterface $container + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * @param CommandTransporterInterface $transporter + */ + public function setTransporter(CommandTransporterInterface $transporter) + { + $this->transporter =& $transporter; + } + + /** + * @return CommandTransporterInterface + */ + public function transporter() + { + return $this->transporter; + } + + /** + * @return PimapSupportedTopLevelCommandsInterface + */ + public function command() + { + return PimapCommandBuilder::instance(); + } + + /** + * @param CommandBuildArgumentsInterface $commandBuildArguments + * @return mixed + * @throws \SalesAgility\Imap\Token\TokenException + * @throws \Exception + */ + public function run(CommandBuildArgumentsInterface $commandBuildArguments) + { + switch ($commandBuildArguments->command()) { + case 'UID': + $responseMessage = $this->runCommand($commandBuildArguments); + + if (array_key_exists('FETCH', $commandBuildArguments->commandArguments())) { + $interpreter = new MessageInterpreter(); + } elseif (array_key_exists('SEARCH', $commandBuildArguments->commandArguments())) { + $interpreter = new SearchInterpreter(); + } else { + // COPY and STORE commands return a simple OK/BAD/NO response. + $tag = $this->pipeline->getLastPipe()->getTag(); + $interpreter = new ResponseInterpreter(); + return $interpreter->parse($tag, new StringIterator($responseMessage)); + } + + return $this->parseResponse($interpreter, $responseMessage); + case 'FETCH': + $responseMessage = $this->runCommand($commandBuildArguments); + $interpreter = new MessageInterpreter(); + return $this->parseResponse($interpreter, $responseMessage); + case 'SEARCH': + $responseMessage = $this->runCommand($commandBuildArguments); + $interpreter = new SearchInterpreter(); + return $this->parseResponse($interpreter, $responseMessage); + case 'STATUS': + $responseMessage = $this->runCommand($commandBuildArguments); + $interpreter = new MailboxInterpreter(); + return $this->parseResponse($interpreter, $responseMessage); + case 'SELECT': + $responseMessage = $this->runCommand($commandBuildArguments); + $interpreter = new MailboxInterpreter(); + return $this->parseResponse($interpreter, $responseMessage); + case 'EXAMINE': + $responseMessage = $this->runCommand($commandBuildArguments); + $interpreter = new MailboxInterpreter(); + return $this->parseResponse($interpreter, $responseMessage); + case 'LIST': + $responseMessage = $this->runCommand($commandBuildArguments); + $interpreter = new MailboxListInterpreter(); + return $this->parseResponse($interpreter, $responseMessage); + case 'LSUB': + $responseMessage = $this->runCommand($commandBuildArguments); + $interpreter = new MailboxListInterpreter(); + return $this->parseResponse($interpreter, $responseMessage); + case 'IDLE': + $responseMessage = $this->runIDLE(); + $interpreter = new MailboxInterpreter(); + return $interpreter->parse(new StringIterator($responseMessage)); + default: + $responseMessage = $this->runCommand($commandBuildArguments); + $tag = $this->pipeline->getLastPipe()->getTag(); + $interpreter = new ResponseInterpreter(); + return $interpreter->parse($tag, new StringIterator($responseMessage)); + } + } + + /** + * @param PipelineInterface $pipeline + */ + public function setPipeLine(PipelineInterface $pipeline) + { + $this->pipeline = $pipeline; + } + + /** + * @return PipelineInterface + */ + public function pipeline() + { + return $this->pipeline; + } + + /** + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->log = $logger; + } + + /** + * @param CommandBuildArgumentsInterface $commandBuildArguments + * @return bool|string + * @throws \Exception + */ + private function runCommand(CommandBuildArgumentsInterface $commandBuildArguments) + { + $this->transporter->transmitCommand($commandBuildArguments); + $response = $this->transporter->receive(); + $this->pipeline->getLastPipe()->addResponse($response); + return $response; + } + + /** + * Custom handling for IDLE command + * @see https://tools.ietf.org/html/rfc2177 + * @return string + * @throws \Exception + */ + private function runIDLE() + { + $command = $this->command()->idle()->build(); + $this->pipeline->add($command); + $tag = $this->pipeline->getLastPipe()->getTag(); + $strCommand = $command->tagged($tag)->asString(); + $this->transporter->transmit($strCommand . "\x0D\x0A"); + + // wait for idling response + $response = ''; + while (true) { + $response .= $this->transporter->connection()->readMessage(); + if (StringValue::startsWith($response, '+ idling')) { + break; + } + } + + // wait for message response + while (true) { + $responseCurrent = $this->transporter->connection()->readMessage(); + $response .= $responseCurrent; + if (StringValue::startsWith($responseCurrent, '*') + && StringValue::endsWith($responseCurrent, 'EXISTS' . "\r\n") + ) { + break; + } + } + + // Complete IDLE and wait for OK response + $this->transporter->transmit('DONE' . "\x0D\x0A"); + while (!$this->transporter->isEndOfFile('')) { + $message = $this->transporter->connection()->readMessage(); + if ($message === null) { + break; + } + $response .= $message; + } + + $this->transporter->connection()->readMessage(); + + return $response; + } + + /** + * Keeps this class DRY + * @param StringIteratorInterpreter $interpreter + * @param $responseMessage + * @return mixed + * @throws \SalesAgility\Imap\Token\TokenException + */ + private function parseResponse(StringIteratorInterpreter $interpreter, $responseMessage) + { + $tag = $this->pipeline->getLastPipe()->getTag(); + $responseInterpreter = new ResponseInterpreter(); + $response = $responseInterpreter->parse($tag, new StringIterator($responseMessage)); + $parsed = $interpreter->parse($response->included()); + $this->pipeline->getLastPipe()->addParsed($parsed); + return $parsed; + } + +} \ No newline at end of file diff --git a/src/Imap/ManagerFactory.php b/src/Imap/ManagerFactory.php new file mode 100755 index 0000000..952c278 --- /dev/null +++ b/src/Imap/ManagerFactory.php @@ -0,0 +1,128 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap; + +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerAwareInterface; +use SalesAgility\Pattern\Singleton; +use SalesAgility\Imap\Manager\PhpImapExtensionManager; +use SalesAgility\Imap\Manager\PimapManager; +use SalesAgility\Imap\Pipeline\Pipeline; +use SalesAgility\Imap\Stream\MessageTransporter; +use SalesAgility\Imap\Stream\PhpImapExtensionMessageTransporter; +use SalesAgility\Imap\Stream\PhpImpExtensionConnection; +use SalesAgility\Stream\Connection; +use SalesAgility\Utility\PimapLogger; + +/** + * Class ManagerFactory + * @package SalesAgility\Imap\Pipeline + */ +class ManagerFactory implements Singleton, ContainerInterface +{ + private $containers = array(); + + private function __construct() + { + $this->containers = array( + PimapManager::class => new PimapManager($this), + Pipeline::class => new Pipeline($this), + Connection::class => new Connection($this), + MessageTransporter::class => new MessageTransporter($this), + PhpImapExtensionManager::class => new PhpImapExtensionManager($this), + PhpImpExtensionConnection::class => new PhpImpExtensionConnection($this), + PhpImapExtensionMessageTransporter::class => new PhpImapExtensionMessageTransporter($this), + PimapLogger::class => new PimapLogger(), + 'Logger' => new PimapLogger() + ); + } + + /** + * @return ManagerFactory + */ + public static function instance() + { + return new self(); + } + + /** + * @return PimapManager + */ + public function PimapManager() + { + /** @var MessageTransporter $transporter */ + $transporter = $this->get(MessageTransporter::class); + /** @var Pipeline $pipeline */ + $pipeline = $this->get(Pipeline::class); + /** @var PimapManager $manager */ + $manager = $this->get(PimapManager::class); + /** @var PimapManager $manager */ + $connection = $this->get(Connection::class); + + $transporter->setConnection($connection); + $transporter->setPipeLine($pipeline); + $manager->setTransporter($transporter); + $manager->setPipeLine($pipeline); + + return $manager; + } + + /** + * @return PhpImapExtensionManager + * @throws \Exception + */ + public function PhpImapExtensionManager() + { + /** @var PhpImapExtensionMessageTransporter $transporter */ + $transporter = $this->get(PhpImapExtensionMessageTransporter::class); + /** @var Pipeline $pipeline */ + $pipeline = $this->get(Pipeline::class); + /** @var PhpImapExtensionManager $manager */ + $manager = $this->get(PhpImapExtensionManager::class); + + $connection = $this->get(PhpImpExtensionConnection::class); + $transporter->setConnection($connection); + $transporter->setPipeLine($pipeline); + $manager->setTransporter($transporter); + $manager->setPipeLine($pipeline); + $manager->setLogger($this->get(PimapLogger::class)); + + return $manager; + } + + /** + * @param string $id + * @return mixed + */ + public function get($id) + { + return $this->containers[$id]; + } + + /** + * @param string $id + * @return bool + */ + public function has($id) + { + return array_key_exists($id, $this->containers); + } +} \ No newline at end of file diff --git a/src/Imap/Pipeline/Pipe.php b/src/Imap/Pipeline/Pipe.php new file mode 100755 index 0000000..4d11baf --- /dev/null +++ b/src/Imap/Pipeline/Pipe.php @@ -0,0 +1,188 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Pipeline; + +use SalesAgility\Imap\Lexeme\LexemeList; +use SalesAgility\Imap\Response\MessageList; +use SalesAgility\Imap\Token\TokenList; +use SalesAgility\Imap\CommandBuilder\CommandBuildArgumentsInterface; +use SalesAgility\Utility\Assert; + +/** + * Class Pipe + * @package SalesAgility\Imap + */ +class Pipe implements PipeInterface +{ + /** + * @var string tag + * should only be assigned once + */ + private $tag; + + /** + * @var CommandBuildArgumentsInterface command + * should only be assigned once + */ + private $command; + + /** + * @var string[] $response + */ + private $response; + + /** + * @var array + */ + private $parsed; + + /** @var TokenList $tokenList */ + private $tokenList; + + /** @var LexemeList $lexemeList */ + private $lexemeList; + + /** @var MessageList $messageList */ + private $messageList; + + /** + * Pipe constructor. + * @param string $tag + * @param CommandBuildArgumentsInterface $command + * @throws \Exception + */ + public function __construct($tag, CommandBuildArgumentsInterface $command) + { + Assert::is(gettype($tag) === 'string', '$tag must be a string eg A1'); + Assert::is(!empty($tag), '$tag must not be empty'); + + $this->tag = $tag; + $command->tagged($tag); + $this->command = $command; + $this->response = array(); + $this->parsed = array(); + } + + /** + * @param $response + * @throws \Exception + */ + public function addResponse($response) + { + Assert::is(gettype($response) === 'string', '$response must be a string eg A1 OK'); + $this->response[] = $response; + } + + /** + * @return string + */ + public function getResponse() + { + return implode('', $this->response); + } + + /** + * @return string + */ + public function getTag() + { + return $this->tag; + } + + /** + * @return CommandBuildArgumentsInterface + */ + public function getCommand() + { + return $this->command; + } + + /** + * @return string + */ + public function buildCommand() + { + return $this->tag . "\x20" . $this->command->command(); + } + + /** + * @param TokenList $tokenList + */ + public function addTokenList(TokenList $tokenList) + { + $this->tokenList = $tokenList; + } + + /** + * @return bool + */ + public function isTokenized() + { + return $this->tokenList !== null; + } + + /** + * @return mixed|TokenList + */ + public function tokenList() + { + return $this->tokenList; + } + + /** + * @param LexemeList $lexemeList + */ + public function addLexemeList(LexemeList $lexemeList) + { + $this->lexemeList = $lexemeList; + } + + /** + * @return bool + */ + public function isLexemized() + { + return $this->lexemeList !== null; + } + + /** + * @return mixed|LexemeList + */ + public function lexemeList() + { + return $this->lexemeList; + } + + /** + * @param $parsedContent + */ + public function addParsed($parsedContent) + { + $this->parsed = $parsedContent; + } + + /** + * @return array + */ + public function parsed() + { + return $this->parsed; + } +} \ No newline at end of file diff --git a/src/Imap/Pipeline/PipeInterface.php b/src/Imap/Pipeline/PipeInterface.php new file mode 100755 index 0000000..bc0c68e --- /dev/null +++ b/src/Imap/Pipeline/PipeInterface.php @@ -0,0 +1,108 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Pipeline; + +use SalesAgility\Imap\Lexeme\LexemeList; +use SalesAgility\Imap\Token\TokenList; +use SalesAgility\Imap\Response\MessageList; +use SalesAgility\Imap\CommandBuilder\CommandBuildArgumentsInterface; + +/** + * Interface PipeInterface + * @package SalesAgility\Imap\Pipeline + */ +interface PipeInterface +{ + /** + * Pipe constructor. + * @param string $tag + * @param CommandBuildArgumentsInterface $command + */ + public function __construct($tag, CommandBuildArgumentsInterface $command); + + /** + * @param string $response + * @throws \Exception + */ + public function addResponse($response); + + /** + * @return string + */ + public function getTag(); + + /** + * @return CommandBuildArgumentsInterface + */ + public function getCommand(); + + /** + * @return string[] + */ + public function getResponse(); + + /** + * @return string full command including tag + */ + public function buildCommand(); + + /** + * @param TokenList $tokenList + * @return void + */ + public function addTokenList(TokenList $tokenList); + + /** + * @return bool + */ + public function isTokenized(); + + /** + * @return mixed + */ + public function tokenList(); + + /** + * @param LexemeList $lexemeList + * @return void + */ + public function addLexemeList(LexemeList $lexemeList); + + /** + * @return bool + */ + public function isLexemized(); + + /** + * @return mixed + */ + public function lexemeList(); + + /** + * @param $parsedContent + */ + public function addParsed($parsedContent); + + /** + * @return mixed + */ + public function parsed(); + +} \ No newline at end of file diff --git a/src/Imap/Pipeline/PipeLineAwareInterface.php b/src/Imap/Pipeline/PipeLineAwareInterface.php new file mode 100755 index 0000000..6ec55b0 --- /dev/null +++ b/src/Imap/Pipeline/PipeLineAwareInterface.php @@ -0,0 +1,35 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Pipeline; + + +/** + * Interface PipeLineAwareInterface + * @package SalesAgility\Imap\Pipeline + */ +interface PipeLineAwareInterface +{ + /** + * @param PipelineInterface $pipeline + * @return mixed + */ + public function setPipeLine(PipelineInterface $pipeline); +} \ No newline at end of file diff --git a/src/Imap/Pipeline/Pipeline.php b/src/Imap/Pipeline/Pipeline.php new file mode 100755 index 0000000..b6d8804 --- /dev/null +++ b/src/Imap/Pipeline/Pipeline.php @@ -0,0 +1,172 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Pipeline; + +use Psr\Container\ContainerInterface; +use SalesAgility\Imap\CommandBuilder\CommandBuildArgumentsInterface; +use SalesAgility\Imap\CommandBuilder\CommandBuildInterface; +use SalesAgility\Pattern\ContainerAwareInterface; + +/** + * Class Pipeline + * @package SalesAgility\Imap + * Pipeline used to queue Imap Pipes and store responses + */ +class Pipeline implements PipelineInterface, ContainerAwareInterface +{ + /** @var ContainerInterface $container */ + private $container; + + /** @var int $current */ + private $current = 0; + + /** @var int $last */ + private $last; + + /** @var int $length */ + private $length; + + /** + * @var PipeInterface[] $pipes + */ + private $pipes; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Return the current element + * @return PipeInterface + */ + public function current() + { + return $this->pipes[$this->current]; + } + + /** + * Move forward to next element + */ + public function next() + { + ++$this->current; + } + + /** + * Return the key of the current element + * @return int + */ + public function key() + { + return $this->current; + } + + /** + * Checks if current position is valid + * @return bool + */ + public function valid() + { + return $this->length !== 0 && $this->current <= $this->last; + } + + /** + * Rewind the Iterator to the first element + */ + public function rewind() + { + $this->current = 0; + } + + /** + * @param CommandBuildInterface $command + * @throws \Exception + */ + public function add(CommandBuildInterface $command) + { + ++$this->length; + $tag = 'A' . ($this->length); + $pipe = new Pipe($tag, $command); + $this->pipes[] = $pipe; + } + + /** + * @return PipeInterface[] + * Recommended: use a reference eg $commands = &$pipeline->getPipes(); + */ + public function pipes() + { + return $this->pipes; + } + + /** + * @return PipeInterface + */ + public function getLastPipe() + { + $key = count($this->pipes) - 1; + return $this->pipes[$key]; + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return PipeInterface|null + */ + public function pipeByCommand($command) + { + /** @var Pipe $pipe */ + foreach ($this->pipes as $pipe) { + $comparator = substr($command->asString(), strlen($pipe->getTag())); + $pipeComparator = substr($pipe->getCommand()->asString(), strlen($pipe->getTag())); + if ($pipeComparator === $comparator) { + return $pipe; + } + } + return null; + } + + /** + * @param PipelineInterface $pipeline + * @return null|void + */ + public function mergePipeline($pipeline) + { + + $skipCommands = array( + 'LOGIN', + 'LOGOUT', + 'NOOP', + 'IDLE', + 'SELECT', + 'EXAMINE', + 'CHECK', + ); + + /** @var PipeInterface $pipe */ + foreach ($pipeline->pipes as $pipe) { + $command = $pipe->getCommand()->command(); + if (in_array($command, $skipCommands) == false) { + ++$this->length; + $this->pipes[] = $pipe; + } + } + } +} \ No newline at end of file diff --git a/src/Imap/Pipeline/PipelineInterface.php b/src/Imap/Pipeline/PipelineInterface.php new file mode 100755 index 0000000..4d54423 --- /dev/null +++ b/src/Imap/Pipeline/PipelineInterface.php @@ -0,0 +1,50 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Pipeline; + +use SalesAgility\Imap\CommandBuilder\CommandBuildInterface; + +/** + * Interface PipelineInterface + * @package SalesAgility\Imap\Pipeline + */ +interface PipelineInterface extends \Iterator +{ + /** + * @param CommandBuildInterface $command + */ + public function add(CommandBuildInterface $command); + + /** + * @return PipeInterface[] + */ + public function pipes(); + + /** + * @return PipeInterface + */ + public function getLastPipe(); + + /** + * @param PipelineInterface $pipeline + * @return null + */ + public function mergePipeline($pipeline); +} \ No newline at end of file diff --git a/src/Imap/Pipeline/PipelineIteratorInterface.php b/src/Imap/Pipeline/PipelineIteratorInterface.php new file mode 100755 index 0000000..b36c183 --- /dev/null +++ b/src/Imap/Pipeline/PipelineIteratorInterface.php @@ -0,0 +1,67 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Pipeline; + + +/** + * Interface PipelineIteratorInterface + * @package SalesAgility\Imap\Pipeline + */ +interface PipelineIteratorInterface extends \OuterIterator +{ + /** + * Return the current element + * @return PipeInterface + */ + public function current(); + + /** + * Move forward to next element + */ + public function next(); + + /** + * Return the key of the current element + * @return int + */ + public function key(); + + /** + * Checks if current position is valid + * @return bool + */ + public function valid(); + + /** + * Rewind the Iterator to the first element + */ + public function rewind(); + + /** + * Fast forward the iterator to the last element + */ + public function fastForward(); + + /** + * @return &PipeIterator innerString + */ + public function getInnerIterator(); +} \ No newline at end of file diff --git a/src/Imap/Response/Capability.php b/src/Imap/Response/Capability.php new file mode 100755 index 0000000..839db0b --- /dev/null +++ b/src/Imap/Response/Capability.php @@ -0,0 +1,47 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Response; + + +class Capability implements \ArrayAccess +{ + /** @var array */ + private $capability = array(); + + public function offsetExists($offset) + { + return isset($this->capability[$offset]); + } + + public function offsetGet($offset) + { + return $this->capability[$offset]; + } + + public function offsetSet($offset, $value) + { + $this->capability[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->capability[$offset]); + } +} \ No newline at end of file diff --git a/src/Imap/Response/Mailbox.php b/src/Imap/Response/Mailbox.php new file mode 100755 index 0000000..89bcb9c --- /dev/null +++ b/src/Imap/Response/Mailbox.php @@ -0,0 +1,222 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Response; + + +use SalesAgility\Utility\Assert; + +/** + * Class Mailbox + * @package SalesAgility\Imap\ImapResponse + */ +class Mailbox implements \ArrayAccess +{ + /** @var array $flags */ + private $flags = array(); + + /** @var array $attributes */ + private $attributes = array(); + + /** @var string $hierarchy */ + private $hierarchy = ''; + + /** @var string $name */ + private $name = ''; + + /** @var string $exists */ + private $exists = ''; + + /** @var string $recent */ + private $recent = ''; + + /** @var string $unseen */ + private $unseen = ''; + + /** @var string $uidValidity */ + private $uidvalidity = ''; + + /** @var string $uidNext */ + private $uidnext = ''; + + /** + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + if (in_array($offset, array( + "flags", + "attributes", + "hierarchy", + "name", + "exists", + "recent", + "unseen", + "uidvalidity", + "uidnext" + ))) { + return true; + } + + return false; + } + + /** + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->{$offset}; + } + + /** + * @param mixed $offset + * @param mixed $value + * @throws \Exception + */ + public function offsetSet($offset, $value) + { + Assert::is(gettype($value) === 'string', '$value must be a string'); + + if ($offset === 'flags') { + $this->flags[] = $value; + } elseif ($offset === 'attributes') { + $this->attributes[] = $value; + } else { + $this->{$offset} = $value; + } + } + + /** + * @param mixed $offset + */ + public function offsetUnset($offset) + { + if ($offset === 'flags') { + $this->flags = array(); + } elseif ($offset === 'attributes') { + $this->attributes = array(); + } elseif ($offset === 'hierarchy') { + $this->hierarchy = ''; + } elseif ($offset === 'name') { + $this->name = ''; + } elseif ($offset === 'exists') { + $this->exists = ''; + } elseif ($offset === 'recent') { + $this->recent = ''; + } elseif ($offset === 'unseen') { + $this->unseen = ''; + } elseif ($offset === 'uidvalidity') { + $this->uidvalidity = ''; + } elseif ($offset === 'uidnext') { + $this->uidnext = ''; + } + } + + /** + * The hierarchy delimiter is a character used to delimit levels of hierarchy in a mailbox name. + * A client can use it to create child mailboxes, and to search higher or lower levels of naming hierarchy. + * All children of a top-level hierarchy node MUST use the same separator character. A NIL hierarchy delimiter + * means that no hierarchy exists; the name is a "flat" name. + * @return string + */ + public function hierarchy() + { + return $this->hierarchy; + } + + /** + * The name of the mailbox + * @return string + */ + public function name() + { + return $this->name; + } + + /** + * - Noinferiors - It is not possible for any child levels of hierarchy to exist under this name; no child + * levels exist now and none can be created in the future. + * - Noselect - It is not possible to use this name as a selectable mailbox. + * - Marked - The mailbox has been marked "interesting" by the server; the mailbox probably contains + * messages that have been added since the last time the mailbox was selected. + * - Unmarked - The mailbox does not contain any additional messages since the last time the mailbox was selected. + * @return array + */ + public function attributes() + { + return $this->attributes; + } + + /** + * Defined flags in the mailbox. + * @return array + */ + public function flags() + { + return $this->flags; + } + + /** + * The number of messages in the mailbox. + * @return string + */ + public function exists() + { + return $this->exists; + } + + /** + * The number of messages with the Recent flag set. + * @return string + */ + public function recent() + { + return $this->recent; + } + + /** + * The message sequence number of the first unseen message in the mailbox. + * @return string + */ + public function unseen() + { + return $this->unseen; + } + + /** + * The unique identifier validity value. + * @return string + */ + public function uidValidity() + { + return $this->uidvalidity; + } + + /** + * The next unique identifier value. + * @return string + */ + public function uidNext() + { + return $this->uidnext; + } +} \ No newline at end of file diff --git a/src/Imap/Response/MailboxList.php b/src/Imap/Response/MailboxList.php new file mode 100755 index 0000000..f01d876 --- /dev/null +++ b/src/Imap/Response/MailboxList.php @@ -0,0 +1,121 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Response; + +/** + * Class MailboxList + * @package SalesAgility\Imap + * A list of @see \SalesAgility\Imap\Response\Mailbox + */ +class MailboxList implements \Iterator, \ArrayAccess +{ + private $mailboxList = array(); + private $currentKey = 0; + + /** + * @return Mailbox + */ + public function current() + { + return $this->mailboxList[$this->currentKey]; + } + + public function next() + { + ++$this->currentKey; + } + + /** + * @return int|mixed + */ + public function key() + { + return $this->currentKey; + } + + /** + * @return bool + */ + public function valid() + { + return $this->currentKey < count($this->mailboxList); + } + + public function rewind() + { + $this->currentKey = 0; + } + + /** + * @param $offset + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->mailboxList); + } + + /** + * @param mixed $offset + * @return Mailbox + */ + public function offsetGet($offset) + { + return $this->mailboxList[$offset]; + } + + /** + * @param int $offset + * @param Mailbox $value + */ + public function offsetSet($offset, $value) + { + $newOffset = false; + if ($offset === null) { + $newOffset = true; + } elseif (gettype($offset) !== "integer") { + throw new \InvalidArgumentException('Mailbox List can only store integer key values'); + } + + if ($value instanceof Mailbox) { + if (!$newOffset) { + $this->mailboxList[$offset] = $value; + } else { + $count = count($this->mailboxList); + if ($count === 0) { + $this->mailboxList[0] = $value; + } else { + $this->mailboxList[$count] = $value; + } + } + } else { + throw new \InvalidArgumentException('Mailbox List can only store values which derive from a Mailbox'); + } + } + + /** + * @param int $offset + */ + public function offsetUnset($offset) + { + array_splice($this->mailboxList, $offset, 1); + } +} \ No newline at end of file diff --git a/src/Imap/Response/Message.php b/src/Imap/Response/Message.php new file mode 100755 index 0000000..407282e --- /dev/null +++ b/src/Imap/Response/Message.php @@ -0,0 +1,211 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Response; + + +/** + * Class Message + * @package SalesAgility\Imap\Response + */ +class Message implements \ArrayAccess +{ + /** @var MessageHeaderInterface|null */ + private $header; + + /** @var MessageBodyInterface|null */ + private $body; + + /** + * @var string + */ + private $number; + + /** + * @var string + */ + private $uid; + + /** + * @var MessageFlagsInterface + */ + private $flags; + + /** + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + if ($offset === 'header') { + return true; + } elseif ($offset === 'body') { + return true; + } elseif ($offset === 'number') { + return true; + } elseif ($offset === 'uid') { + return true; + } elseif ($offset === 'flags') { + return true; + } + + return false; + } + + /** + * @param string $offset header|body + * @return null|MessageBodyInterface|MessageHeaderInterface|string + */ + public function offsetGet($offset) + { + if ($offset === 'header') { + return $this->header; + } elseif ($offset === 'body') { + return $this->body; + } elseif ($offset === 'number') { + return $this->number; + } elseif ($offset === 'uid') { + return $this->uid; + } elseif ($offset === 'flags') { + return $this->flags; + } else { + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @param string $offset header|body + * @param MessageHeaderInterface|MessageBodyInterface|mixed $value + */ + public function offsetSet($offset, $value) + { + if ($offset === 'header') { + if ($value instanceof MessageHeaderInterface) { + $this->header = $value; + } else { + throw new \InvalidArgumentException('header $value must implement: MessageHeaderInterface'); + } + } elseif ($offset === 'body') { + if ($value instanceof MessageBodyInterface) { + $this->body = $value; + } else { + throw new \InvalidArgumentException('body $value must implement: MessageBodyInterface'); + } + } elseif ($offset === 'number') { + if (is_string($value)) { + $this->number = $value; + } else { + throw new \InvalidArgumentException('number $value must be a string'); + } + } elseif ($offset === 'uid') { + if (is_string($value)) { + $this->uid = $value; + } else { + throw new \InvalidArgumentException('uid $value must be a string'); + } + } elseif ($offset === 'flags') { + if ($value instanceof MessageFlagsInterface) { + $this->flags = $value; + } else { + throw new \InvalidArgumentException('flags $value must implement: MessageFlagsInterface'); + } + } else { + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @param string $offset header|body + */ + public function offsetUnset($offset) + { + if ($offset === 'header') { + $this->header = null; + } elseif ($offset === 'body') { + $this->body = null; + } elseif ($offset === 'number') { + $this->number = null; + } elseif ($offset === 'uid') { + $this->uid = null; + } elseif ($offset === 'flags') { + $this->flags = null; + } else { + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * has the header been included in the message + * @return bool + */ + public function hasHeader() + { + return $this->header !== null; + } + + /** + * @return MessageHeaderInterface + */ + public function header() + { + return $this->header; + } + + /** + * has body been included in the message + * @return bool + */ + public function hasBody() + { + return $this->body !== null; + } + + /** + * @return MessageBodyInterface + */ + public function body() + { + return $this->body; + } + + /** + * @return string + */ + public function number() + { + return $this->number; + } + + /** + * @return string + */ + public function uid() + { + return $this->uid; + } + + /** + * @return array|MessageFlagsInterface + */ + public function flags() + { + return $this->flags; + } +} diff --git a/src/Imap/Response/MessageAttachment.php b/src/Imap/Response/MessageAttachment.php new file mode 100755 index 0000000..7bf9b78 --- /dev/null +++ b/src/Imap/Response/MessageAttachment.php @@ -0,0 +1,190 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Response; + + +use SalesAgility\Utility\Assert; + +/** + * Class MessageAttachment + * @package SalesAgility\Imap\Response + */ +class MessageAttachment implements MessageAttachmentInterface +{ + /** @var MessageAttachmentStructureInterface $structure */ + private $structure; + + /** @var bool $isInline */ + private $isInline = false; + + /** @var bool $hasContent */ + private $hasContent = false; + + /** @var string $content */ + private $content = ''; + + /** @var string $contentId */ + private $contentId = ''; + + /** + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + switch ($offset) { + case 'structure': + return true; + case 'hasContent': + return true; + case 'content': + return true; + case 'isInline': + return true; + case 'contentId': + return true; + default: + return false; + } + } + + /** + * @param string $offset + * @return mixed + * @throws \InvalidArgumentException + */ + public function offsetGet($offset) + { + switch ($offset) { + case 'structure': + return $this->structure; + case 'hasContent': + return $this->hasContent; + case 'content': + return $this->content; + case 'isInline': + return $this->isInline; + case 'contentId': + return $this->contentId; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @param string $offset + * @param mixed $value + * @return mixed + * @throws \InvalidArgumentException + * @throws \Exception + */ + public function offsetSet($offset, $value) + { + switch ($offset) { + case 'structure': + $this->structure = $value; + break; + case 'hasContent': + Assert::is(gettype($value) === 'boolean', 'hasContent must be a boolean'); + $this->hasContent = $value; + break; + case 'content': + $this->content = $value; + break; + case 'isInline': + Assert::is(gettype($value) === 'boolean', 'isInline must be a boolean'); + return $this->isInline = $value; + case 'contentId': + $this->contentId = $value; + break; + default: + throw new \InvalidArgumentException('$offset does not exist: test'); + } + } + + /** + * @param string $offset + * @return bool|null|string + * @throws \InvalidArgumentException + */ + public function offsetUnset($offset) + { + switch ($offset) { + case 'structure': + return $this->structure = null; + case 'hasContent': + return $this->hasContent = false; + case 'content': + return $this->content = ''; + case 'isInline': + return $this->isInline = false; + case 'contentId': + return $this->contentId = ''; + default: + throw new \InvalidArgumentException('$offset does not exist: test'); + } + } + + /** + * get the information about the attachment. + * @return MessageAttachmentStructureInterface + */ + public function structure() + { + return $this->structure; + } + + /** + * has the attachments content been included? + * @return bool + */ + public function hasContent() + { + return $this->hasContent; + } + + /** + * get attachments content been included. + * @return string|null + */ + public function content() + { + return $this->content; + } + + /** + * is the attachment embedded content? + * @return bool + */ + public function isInline() + { + return $this->isInline; + } + + /** + * get the content id (cid:) which is referenced in the text or html of the body + * @return string used to recognize inline attachments + */ + public function contentId() + { + return $this->contentId; + } +} diff --git a/src/Imap/Response/MessageAttachmentInterface.php b/src/Imap/Response/MessageAttachmentInterface.php new file mode 100755 index 0000000..6a05e9a --- /dev/null +++ b/src/Imap/Response/MessageAttachmentInterface.php @@ -0,0 +1,49 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Response; + + +/** + * Interface MessageAttachmentInterface + * @package SalesAgility\Imap\Response + */ +interface MessageAttachmentInterface extends \ArrayAccess +{ + /** + * @return MessageAttachmentStructureInterface + */ + public function structure(); + + /** + * @return bool + */ + public function hasContent(); + + /** + * @return string + */ + public function content(); + + /** + * return if content is embedded in the email html + * @return bool + */ + public function isInline(); +} diff --git a/src/Imap/Response/MessageAttachmentStructure.php b/src/Imap/Response/MessageAttachmentStructure.php new file mode 100755 index 0000000..b501a60 --- /dev/null +++ b/src/Imap/Response/MessageAttachmentStructure.php @@ -0,0 +1,149 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Response; + +use SalesAgility\Utility\Assert; + +/** + * Class MessageAttachmentStructure + * @package SalesAgility\Imap\Response + */ +class MessageAttachmentStructure implements MessageAttachmentStructureInterface +{ + /** @var string $type */ + private $type = ''; + + /** @var string $type */ + private $name = ''; + + /** @var string $size */ + private $size = ''; + + /** + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + switch ($offset) { + case 'type': + return true; + case 'name': + return true; + case 'size': + return true; + default: + return false; + } + } + + /** + * @param string $offset + * @return string $mixed + */ + public function offsetGet($offset) + { + switch ($offset) { + case 'type': + return $this->type; + case 'name': + return $this->name; + case 'size': + return $this->size; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @param string $offset + * @param mixed $value + * @throws \InvalidArgumentException + * @throws \Exception + */ + public function offsetSet($offset, $value) + { + switch ($offset) { + case 'type': + Assert::is(is_string($value), 'type must be a string'); + $this->type = $value; + break; + case 'name': + Assert::is(is_string($value), 'name must be a string'); + $this->name = $value; + break; + case 'size': + Assert::is(is_int($value), 'size must be a integer in bytes'); + $this->size = $value; + break; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @param string $offset + * @throws \InvalidArgumentException + */ + public function offsetUnset($offset) + { + switch ($offset) { + case 'type': + $this->type = null; + break; + case 'name': + $this->name = ''; + break; + case 'size': + $this->size = ''; + break; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * get the mime type of the content. Eg text/plain, text/html, image/jpeg etc... + * @return string + */ + public function type() + { + return $this->type; + } + + /** + * get the file name of the content + * @return string + */ + public function name() + { + return $this->name; + } + + /** + * get the size (in bytes) of the content + * @return string + */ + public function size() + { + return $this->size; + } +} diff --git a/src/Imap/Response/MessageAttachmentStructureInterface.php b/src/Imap/Response/MessageAttachmentStructureInterface.php new file mode 100755 index 0000000..d273cb7 --- /dev/null +++ b/src/Imap/Response/MessageAttachmentStructureInterface.php @@ -0,0 +1,43 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Response; + + +/** + * Interface MessageAttachmentStructureInterface + * @package SalesAgility\Imap\Response + */ +interface MessageAttachmentStructureInterface extends \ArrayAccess +{ + /** + * @return string + */ + public function type(); + + /** + * @return string + */ + public function name(); + + /** + * @return string + */ + public function size(); +} \ No newline at end of file diff --git a/src/Imap/Response/MessageBody.php b/src/Imap/Response/MessageBody.php new file mode 100755 index 0000000..d72cdf5 --- /dev/null +++ b/src/Imap/Response/MessageBody.php @@ -0,0 +1,164 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Response; + + +use SalesAgility\Utility\Assert; + +/** + * Class MessageBody + * @package SalesAgility\Imap\Response + */ +class MessageBody implements MessageBodyInterface +{ + /** @var MessageBodyStructureInterface */ + private $structure; + + /** @var string $html */ + private $html = ''; + + /** @var string $text */ + private $text = ''; + + /** @var MessageAttachmentInterface[] */ + private $attachments = array(); + + /** + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + switch ($offset) { + case 'structure': + return true; + case 'html': + return true; + case 'text': + return true; + case 'attachments': + return true; + default: + return false; + } + } + + /** + * @param string $offset + * @return MessageAttachmentInterface[]|MessageBodyStructureInterface|string + * @throws \InvalidArgumentException + */ + public function offsetGet($offset) + { + switch ($offset) { + case 'structure': + return $this->structure; + case 'html': + return $this->html; + case 'text': + return $this->text; + case 'attachments': + return $this->attachments; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @param string $offset + * @param mixed $value + * @return mixed + * @throws \InvalidArgumentException + * @throws \Exception + */ + public function offsetSet($offset, $value) + { + switch ($offset) { + case 'structure': + Assert::is($value instanceof MessageBodyStructureInterface, 'attachment must implement MessageAttachmentInterface'); + return $this->structure = $value; + case 'html': + Assert::is(is_string($value), 'html must be a string'); + return $this->html = $value; + case 'text': + Assert::is(is_string($value), 'text must be a string'); + return $this->text = $value; + case 'attachments': + Assert::is($value instanceof MessageAttachmentInterface, 'attachment must implement MessageAttachmentInterface'); + return $this->attachments[] = $value; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @param string $offset + * @return array|null|string + * @throws \InvalidArgumentException + */ + public function offsetUnset($offset) + { + switch ($offset) { + case 'structure': + return $this->structure = null; + case 'html': + return $this->html = ''; + case 'text': + return $this->text = ''; + case 'attachments': + return $this->attachments = array(); + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @return MessageBodyStructureInterface|null + */ + public function structure() + { + return $this->structure; + } + + /** + * @return string|null + */ + public function html() + { + return $this->html; + } + + /** + * @return string|null + */ + public function text() + { + return $this->text; + } + + /** + * @return MessageAttachment[]|MessageAttachmentInterface[] + */ + public function attachments() + { + return $this->attachments; + } +} diff --git a/src/Imap/Response/MessageBodyInterface.php b/src/Imap/Response/MessageBodyInterface.php new file mode 100755 index 0000000..6e60986 --- /dev/null +++ b/src/Imap/Response/MessageBodyInterface.php @@ -0,0 +1,49 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Response; + + +/** + * Interface MessageBodyInterface + * @package SalesAgility\Imap\Response + */ +interface MessageBodyInterface extends \ArrayAccess +{ + /** + * @return MessageBodyStructureInterface + */ + public function structure(); + + /** + * @return string + */ + public function html(); + + /** + * @return string + */ + public function text(); + + /** + * @return MessageAttachmentInterface[] + */ + public function attachments(); +} \ No newline at end of file diff --git a/src/Imap/Response/MessageBodyStructure.php b/src/Imap/Response/MessageBodyStructure.php new file mode 100755 index 0000000..7fbbb7a --- /dev/null +++ b/src/Imap/Response/MessageBodyStructure.php @@ -0,0 +1,185 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Response; + + +use SalesAgility\Utility\Assert; + +/** + * Class MessageBodyStructure + * @package SalesAgility\Imap\Response + */ +class MessageBodyStructure implements MessageBodyStructureInterface +{ + /** @var bool $text */ + public $plain = false; + + /** @var bool $html */ + public $html = false; + + /** @var bool */ + public $attachments = false; + + // + // Hidden implementation details + // + /** @var string $contentTransferEncoding */ + private $contentTransferEncoding; + /** @var string $contentType */ + private $contentType; + /** @var string $mimeVersion */ + private $mimeVersion; + + /** + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + switch ($offset) { + case 'html': + return true; + case 'plain': + return true; + case 'attachments': + return true; + case 'mimeVersion': + return true; + case 'contentType': + return true; + case 'contentTransferEncoding': + return true; + default: + return false; + } + } + + /** + * @param string $offset + * @return bool|\InvalidArgumentException + */ + public function offsetGet($offset) + { + switch ($offset) { + case 'html': + return $this->html; + case 'plain': + return $this->plain; + case 'attachments': + return $this->attachments; + case 'mimeVersion': + return $this->mimeVersion; + case 'contentType': + return $this->contentType; + case 'contentTransferEncoding': + return $this->contentTransferEncoding; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @param string $offset + * @param mixed $value + * @return mixed + * @throws \InvalidArgumentException + * @throws \Exception + */ + public function offsetSet($offset, $value) + { + switch ($offset) { + case 'html': + Assert::is(is_bool($value), '"html" must be a bool'); + return $this->html = $value; + case 'plain': + Assert::is(is_bool($value), '"plain" must be a bool'); + return $this->plain = $value; + case 'attachments': + Assert::is(is_bool($value), '"attachments" must be a bool'); + return $this->attachments = $value; + case 'mimeVersion': + return $this->mimeVersion = $value; + case 'contentType': + return $this->contentType = $value; + case 'contentTransferEncoding': + return $this->contentTransferEncoding = $value; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @param string $offset + * @throws \InvalidArgumentException + */ + public function offsetUnset($offset) + { + switch ($offset) { + case 'html': + $this->html = false; + break; + case 'plain': + $this->plain = false; + break; + case 'attachments': + $this->attachments = false; + break; + case 'mimeVersion': + $this->mimeVersion = ''; + break; + case 'contentType': + $this->contentType = ''; + break; + case 'contentTransferEncoding': + $this->contentTransferEncoding = ''; + break; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * does message contains a html part? + * @return bool + */ + public function htmlBodyExists() + { + return $this->html; + } + + /** + * does message contain a plain part? + * @return bool + */ + public function plainTextBodyExists() + { + return $this->plain; + } + + /** + * does message contain attachments or embedded content? + * @return bool + */ + public function attachmentsExists() + { + return $this->attachments; + } +} diff --git a/src/Imap/Response/MessageBodyStructureInterface.php b/src/Imap/Response/MessageBodyStructureInterface.php new file mode 100755 index 0000000..ae0592b --- /dev/null +++ b/src/Imap/Response/MessageBodyStructureInterface.php @@ -0,0 +1,44 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Response; + + +/** + * Interface MessageBodyStructureInterface + * @package SalesAgility\Imap\Response + */ +interface MessageBodyStructureInterface extends \ArrayAccess +{ + /** + * @return bool + */ + public function htmlBodyExists(); + + /** + * @return bool + */ + public function plainTextBodyExists(); + + /** + * @return bool + */ + public function attachmentsExists(); +} \ No newline at end of file diff --git a/src/Imap/Response/MessageFactory.php b/src/Imap/Response/MessageFactory.php new file mode 100755 index 0000000..dc52821 --- /dev/null +++ b/src/Imap/Response/MessageFactory.php @@ -0,0 +1,47 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Response; + +use SalesAgility\Imap\Response\Message; +use SalesAgility\Imap\Response\MessageBody; +use SalesAgility\Imap\Response\MessageBodyStructure; +use SalesAgility\Imap\Response\MessageFlags; +use SalesAgility\Imap\Response\MessageHeader; + +/** + * Class MessageFactory + * @package SalesAgility\Imap + */ +class MessageFactory +{ + /** + * @return Message + */ + public static function instance() + { + $message = new Message(); + $message['header'] = new MessageHeader(); + $message['body'] = new MessageBody(); + $message['flags'] = new MessageFlags(); + $message['body']['structure'] = new MessageBodyStructure(); + + return $message; + } +} \ No newline at end of file diff --git a/src/Imap/Response/MessageFlags.php b/src/Imap/Response/MessageFlags.php new file mode 100755 index 0000000..55167ed --- /dev/null +++ b/src/Imap/Response/MessageFlags.php @@ -0,0 +1,141 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Response; + + +use SalesAgility\Utility\Assert; + +/** + * Class MessageFlags + * @package SalesAgility\Imap\Response + */ +class MessageFlags implements MessageFlagsInterface +{ + private $flags = array(); + + /** + * MessageFlags Constructor + */ + public function __construct() + { + $this->flags = array( + 'Answered' => false, + 'Deleted' => false, + 'Draft' => false, + 'Flagged' => false, + 'Recent' => false, + 'Seen' => false, + ); + } + + /** + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->flags); + } + + /** + * @param string $offset + * @return bool + */ + public function offsetGet($offset) + { + return $this->flags[$offset]; + } + + /** + * @param string $offset + * @param bool $value + * @throws \Exception + */ + public function offsetSet($offset, $value) + { + Assert::is(is_string($offset), '$offset must be a string'); + Assert::is(is_bool($value), '$value must be a boolean'); + $this->flags[$offset] = $value; + } + + /** + * @param $offset + * @throws \Exception + */ + public function offsetUnset($offset) + { + throw new \Exception('Unset is not supported'); + } + + /** + * has been answered? + * @return bool + */ + public function isAnswered() + { + return $this->flags['Answered']; + } + + /** + * is "deleted" for removal by later EXPUNGE? + * @return bool + */ + public function isDeleted() + { + return $this->flags['Deleted']; + } + + /** + * + * has not completed composition (marked as a draft) + * @return bool + */ + public function isDraft() + { + return $this->flags['Draft']; + } + + /** + * is "flagged" for urgent/special attention? + * @return bool + */ + public function isFlagged() + { + return $this->flags['Flagged']; + } + + /** + * is "recently" arrived in this mailbox? + * @return bool + */ + public function isRecent() + { + return $this->flags['Recent']; + } + + /** + * has Message been read? + * @return bool + */ + public function isSeen() + { + return $this->flags['Seen']; + } +} diff --git a/src/Imap/Response/MessageFlagsInterface.php b/src/Imap/Response/MessageFlagsInterface.php new file mode 100755 index 0000000..f96adc7 --- /dev/null +++ b/src/Imap/Response/MessageFlagsInterface.php @@ -0,0 +1,58 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Response; + + +/** + * Interface MessageFlagsInterface + * @package SalesAgility\Imap\Response + */ +interface MessageFlagsInterface extends \ArrayAccess +{ + /** + * @return bool + */ + public function isAnswered(); + + /** + * @return bool + */ + public function isDeleted(); + + /** + * @return bool + */ + public function isDraft(); + + /** + * @return bool + */ + public function isFlagged(); + + /** + * @return bool + */ + public function isRecent(); + + /** + * @return bool + */ + public function isSeen(); +} diff --git a/src/Imap/Response/MessageHeader.php b/src/Imap/Response/MessageHeader.php new file mode 100755 index 0000000..b637998 --- /dev/null +++ b/src/Imap/Response/MessageHeader.php @@ -0,0 +1,270 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Response; + + +use SalesAgility\Utility\Assert; + +/** + * Class MessageHeader + * @package SalesAgility\Imap\Response + */ +class MessageHeader implements MessageHeaderInterface +{ + /** @var \DateTimeImmutable $date */ + private $date = null; + + /** @var string[] $to */ + private $to = array(); + + /** @var string[] $from */ + private $from = array(); + + /** @var string[] $replyTo */ + private $replyTo = array(); + + /** @var string[] $cc */ + private $cc = array(); + + /** @var string[] bcc */ + private $bcc = array(); + + /** @var string $subject */ + private $subject = ''; + + /** @var string $messageId */ + private $messageId = ''; + + /** + * @param string $offset to|from|replyTo|cc|bcc|subject + * @return bool + */ + public function offsetExists($offset) + { + switch ($offset) { + case "date": + return true; + case "to": + return true; + case "from": + return true; + case "replyTo": + return true; + case "cc": + return true; + case "bcc": + return true; + case "subject": + return true; + case "messageId": + return true; + default: + return false; + } + } + + /** + * @param string $offset to|from|replyTo|cc|bcc|subject + * @return string|string[] + */ + public function offsetGet($offset) + { + switch ($offset) { + case "date": + return $this->date; + case "to": + return $this->to; + case "from": + return $this->from; + case "replyTo": + return $this->replyTo; + case "cc": + return $this->cc; + case "bcc": + return $this->bcc; + case "subject": + return $this->subject; + case "messageId": + return $this->messageId; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @param string $offset to|from|replyTo|cc|bcc|subject + * @param string $value Note: setting the value will append the array + * @return void + * @throws \Exception + */ + public function offsetSet($offset, $value) + { + switch ($offset) { + case "date": + Assert::is($value instanceof \DateTimeImmutable, '"date" must be a \DateTimeImmutable'); + $this->date = $value; + break; + case "to": + Assert::is(is_string($value), '"to" must be a string'); + $this->to[] = $value; + break; + case "from": + Assert::is(is_string($value), '"from" must be a string'); + $this->from[] = $value; + break; + case "replyTo": + Assert::is(is_string($value), '"replyTo" must be a string'); + $this->replyTo[] = $value; + break; + case "cc": + Assert::is(is_string($value), '"cc" must be a string'); + $this->cc[] = $value; + break; + case "bcc": + Assert::is(is_string($value), '"bcc" must be a string'); + $this->bcc[] = $value; + break; + case "subject": + Assert::is(is_string($value), '"subject" must be a string'); + $this->subject = $value; + break; + case "messageId": + Assert::is(is_string($value), '"messageId" must be a string'); + $this->messageId = trim($value); + break; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * @param string $offset to|from|replyTo|cc|bcc|subject + * @return void + */ + public function offsetUnset($offset) + { + switch ($offset) { + case "date": + $this->date = null; + break; + case "to": + $this->to = array(); + break; + case "from": + $this->from = array(); + break; + case "replyTo": + $this->replyTo = array(); + break; + case "cc": + $this->cc = array(); + break; + case "bcc": + $this->bcc = array(); + break; + case "subject": + $this->subject = ''; + break; + case "messageId": + $this->messageId = ''; + break; + default: + throw new \InvalidArgumentException('$offset does not exist: ' . $offset); + } + } + + /** + * The origination date specifies the date and time at which the creator of the message indicated + * that the message was complete and ready to enter the mail delivery system. + * @return \DateTimeImmutable + */ + public function date() + { + return $this->date; + } + + /** + * contains the address(es) of the primary recipient(s) of the message. + * @return string[] + */ + public function to() + { + return $this->to; + } + + /** + * where the email originates from. + * @return string[] + */ + public function from() + { + return $this->from; + } + + /** + * where the email originates from / an email to reply to. + * @return string[] + */ + public function replyTo() + { + if (empty($this->replyTo)) { + return $this->from(); + } + + return $this->replyTo; + } + + /** + * "carbon copy" - contains the addresses of the primary recipient(s) of the message. + * @return string[] + */ + public function cc() + { + return $this->cc; + } + + /** + * "blind carbon copy" - contains addresses of recipients of the message whose addresses are not to be revealed to other recipients of the message. + * @return string[] + */ + public function bcc() + { + return $this->bcc; + } + + /** + * brief description of what the email is about. + * @return string + */ + public function subject() + { + return $this->subject; + } + + /** + * contains a single unique message identifier. + * @return string + */ + public function messageId() + { + return $this->messageId; + } +} \ No newline at end of file diff --git a/src/Imap/Response/MessageHeaderInterface.php b/src/Imap/Response/MessageHeaderInterface.php new file mode 100755 index 0000000..3b839ff --- /dev/null +++ b/src/Imap/Response/MessageHeaderInterface.php @@ -0,0 +1,68 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Response; + + +/** + * Interface MessageHeaderInterface + * @package SalesAgility\Imap\Response + */ +interface MessageHeaderInterface extends \ArrayAccess +{ + /** + * @return \DateTimeImmutable|null + */ + public function date(); + + /** + * @return string[] + */ + public function to(); + + /** + * @return string[] + */ + public function from(); + + /** + * @return string[] + */ + public function replyTo(); + + /** + * @return string[] + */ + public function cc(); + + /** + * @return string[] + */ + public function bcc(); + + /** + * @return string + */ + public function subject(); + + /** + * @return string + */ + public function messageId(); +} \ No newline at end of file diff --git a/src/Imap/Response/MessageInterface.php b/src/Imap/Response/MessageInterface.php new file mode 100755 index 0000000..1d5a1be --- /dev/null +++ b/src/Imap/Response/MessageInterface.php @@ -0,0 +1,49 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Response; + + +/** + * Interface MessageInterface + * @package SalesAgility\Imap\Response + */ +interface MessageInterface extends \ArrayAccess +{ + + /** + * @return bool + */ + public function hasHeader(); + + /** + * @return MessageHeaderInterface|null + */ + public function header(); + + /** + * @return bool + */ + public function hasBody(); + + /** + * @return MessageBodyInterface|null + */ + public function body(); +} \ No newline at end of file diff --git a/src/Imap/Response/MessageList.php b/src/Imap/Response/MessageList.php new file mode 100755 index 0000000..1c039e6 --- /dev/null +++ b/src/Imap/Response/MessageList.php @@ -0,0 +1,169 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Response; + +/** + * Class MessageList + * @package SalesAgility\Imap + * A list of @see \SalesAgility\Imap\Response\Message + */ +class MessageList implements \Iterator, \ArrayAccess +{ + private $messageList = array(); + private $currentKey = 0; + private $direction = 1; + + /** + * @return Message + */ + public function current() + { + return $this->messageList[$this->currentKey]; + } + + public function next() + { + ++$this->currentKey; + } + + /** + * @return int|mixed + */ + public function key() + { + return $this->currentKey; + } + + /** + * @return bool + */ + public function valid() + { + return $this->currentKey < count($this->messageList); + } + + public function rewind() + { + $this->currentKey = 0; + } + + /** + * @param $offset + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->messageList); + } + + /** + * @param mixed $offset + * @return message + */ + public function offsetGet($offset) + { + return $this->messageList[$offset]; + } + + /** + * @param int $offset + * @param Message $value + */ + public function offsetSet($offset, $value) + { + $newOffset = false; + if ($offset === null) { + $newOffset = true; + } elseif (gettype($offset) !== "integer") { + throw new \InvalidArgumentException('Message List can only store integer key values'); + } + + if ($value instanceof Message) { + if (!$newOffset) { + $this->messageList[$offset] = $value; + } else { + $count = count($this->messageList); + if ($count === 0) { + $this->messageList[0] = $value; + } else { + $this->messageList[$count] = $value; + } + } + } else { + throw new \InvalidArgumentException('Message List can only store values which derive from a Message'); + } + } + + /** + * @param int $offset + */ + public function offsetUnset($offset) + { + unset($this->messageList[$offset]); + } + + /** + * Reverses the order of the Message List + * @see MessageList::direction() + */ + public function reverseOrder() + { + $this->direction *= -1; + $this->messageList = array_reverse($this->messageList); + } + + /** + * @return int positive integer is ascending order, negative integer is descending order + * @see MessageList::reverseOrder() + */ + public function direction() + { + return $this->direction; + } + + + /** + * @param integer $offset which page to display + * @param integer $size total Messages per page + * @return MessageList + */ + public function page($offset, $size) + { + $messageList = new MessageList(); + if ($size < 1) { + return $messageList; + } + + if ($this->valid()) { + $paginated = array_chunk($this->messageList, $size); + if ($paginated !== null) { + $messageList->messageList = $paginated[$offset]; + } + } + + return $messageList; + } + + public function count() + { + return count($this->messageList); + } +} \ No newline at end of file diff --git a/src/Imap/Response/Response.php b/src/Imap/Response/Response.php new file mode 100755 index 0000000..ff43831 --- /dev/null +++ b/src/Imap/Response/Response.php @@ -0,0 +1,76 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Response; + +use SalesAgility\Iteration\StringIterator; + + +/** + * Class Response + * @package SalesAgility\Imap\ImapResponse + */ +class Response +{ + /** @var string $status */ + private $status; + + /** @var StringIterator $responseMessage */ + private $responseMessage; + + /** @var StringIterator $includedInResponse */ + private $includedInResponse; + + /** + * Response constructor. + * @param string $status + * @param StringIterator $responseMessage + * @param StringIterator $includedInResponse + */ + public function __construct($status, StringIterator $responseMessage, StringIterator $includedInResponse) + { + $this->status = $status; + $this->responseMessage = $responseMessage; + $this->includedInResponse = $includedInResponse; + } + + /** + * @return string + */ + public function status() + { + return $this->status; + } + + /** + * @return StringIterator + */ + public function message() + { + return $this->responseMessage; + } + + /** + * @return StringIterator + */ + public function included() + { + return $this->includedInResponse; + } +} \ No newline at end of file diff --git a/src/Imap/Stream/CommandTransporterInterface.php b/src/Imap/Stream/CommandTransporterInterface.php new file mode 100755 index 0000000..23d498a --- /dev/null +++ b/src/Imap/Stream/CommandTransporterInterface.php @@ -0,0 +1,38 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Stream; + +use SalesAgility\Stream\MessageTransporterInterface; + +use SalesAgility\Imap\CommandBuilder\CommandBuildArgumentsInterface; + +/** + * Interface CommandTransporterInterface + * @package SalesAgility\Imap\Stream + */ +interface CommandTransporterInterface extends MessageTransporterInterface +{ + /** + * @param CommandBuildArgumentsInterface $command + * @return mixed + */ + public function transmitCommand(CommandBuildArgumentsInterface $command); +} \ No newline at end of file diff --git a/src/Imap/Stream/MessageTransporter.php b/src/Imap/Stream/MessageTransporter.php new file mode 100755 index 0000000..f451f2a --- /dev/null +++ b/src/Imap/Stream/MessageTransporter.php @@ -0,0 +1,175 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Stream; + + +use SalesAgility\Imap\ImapException; +use SalesAgility\Imap\Pipeline\Pipeline; +use SalesAgility\Imap\Pipeline\PipeLineAwareInterface; +use SalesAgility\Imap\Pipeline\PipelineInterface; +use SalesAgility\Imap\CommandBuilder\CommandBuildArgumentsInterface; +use SalesAgility\Stream\StreamConnectionInterface; +use SalesAgility\Utility\Assert; +use SalesAgility\Utility\StringValue; + +/** + * Class MessageTransporter for imap protocol + * @package SalesAgility\Imap + * + */ +class MessageTransporter implements CommandTransporterInterface, PipeLineAwareInterface +{ + /** @var StreamConnectionInterface $connection */ + private $connection; + + /** @var Pipeline $pipeline */ + protected $pipeline; + + /** + * @var CommandBuildArgumentsInterface + */ + private $command = ''; + + public function __construct($container) + { + } + + /** + * @param StreamConnectionInterface $connection + */ + public function setConnection(StreamConnectionInterface $connection) + { + $this->connection = $connection; + } + + /** + * @return StreamConnectionInterface + */ + public function connection() + { + return $this->connection; + } + + /** + * @param string $string + * @throws \Exception + */ + public function transmit($string) + { + $this->isConfigured(); + $this->command = null; + $this->connection->transmitMessage($string); + } + + /** + * @return string + * @throws \Exception + */ + public function receive() + { + $this->isConfigured(); + + $response = ''; + $message = ''; + while (!$this->isEndOfFile($message)) { + $message = $this->connection->readMessage(); + if ($message === null) { + break; + } + $response .= $message; + } + return $response; + } + + /** + * @param string $string + * @return bool + * @throws ImapException + */ + public function isEndOfFile($string) + { + if ($string === null) { + return true; + } + + if ($this->hasTag($string)) { + $response = str_replace($this->pipeline->getLastPipe()->getTag() . ' ', '', $string); + if (StringValue::startsWith($response, 'OK')) { + return true; + } elseif (StringValue::startsWith($response, 'BAD')) { + throw ImapException::BadResponse($string); + } elseif (StringValue::startsWith($response, 'NO')) { + throw ImapException::NoResponse($string); + } + } + + return false; + } + + /** + * @param PipelineInterface $pipeline + */ + public function setPipeLine(PipelineInterface $pipeline) + { + $this->pipeline = $pipeline; + } + + /** + * @throws \Exception + */ + private function isConfigured() + { + Assert::is($this->connection !== null, 'Connection must be set'); + Assert::is($this->pipeline !== null, 'Pipeline must be set'); + return true; + } + + /** + * @param $message + * @return bool + */ + private function hasTag($message) + { + if (empty($this->command)) { + $pattern = '/^[A\-]\d{1,}/'; + + preg_match($pattern, $message, $matches); + if (empty($matches) || count($matches) !== 1) { + return false; + } + } + + return true; + } + + /** + * @param CommandBuildArgumentsInterface $command + * @throws \Exception + */ + public function transmitCommand(CommandBuildArgumentsInterface $command) + { + $this->isConfigured(); + $this->pipeline->add($command); + $tag = $this->pipeline->getLastPipe()->getTag(); + $strCommand = $command->tagged($tag)->asString(); + $this->transmit($strCommand . "\x0D\x0A"); + } +} \ No newline at end of file diff --git a/src/Imap/Stream/PhpImapExtensionMessageTransporter.php b/src/Imap/Stream/PhpImapExtensionMessageTransporter.php new file mode 100644 index 0000000..ecd07bd --- /dev/null +++ b/src/Imap/Stream/PhpImapExtensionMessageTransporter.php @@ -0,0 +1,849 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Stream; + + +use Psr\Log\LoggerInterface; +use SalesAgility\Imap\CommandBuilder\CommandBuildInterface; +use SalesAgility\Imap\Interpreter\Rfc2822Interpreter; +use SalesAgility\Imap\Pipeline\PipelineInterface; +use SalesAgility\Imap\Response\Mailbox; +use SalesAgility\Imap\Response\MailboxList; +use SalesAgility\Imap\Response\Message; +use SalesAgility\Imap\Response\MessageAttachment; +use SalesAgility\Imap\Response\MessageAttachmentStructure; +use SalesAgility\Imap\Response\MessageFactory; +use SalesAgility\Imap\Response\MessageFlags; +use SalesAgility\Imap\Response\MessageList; +use SalesAgility\Imap\Response\Response; +use SalesAgility\Imap\CommandBuilder\CommandBuildArgumentsInterface; +use SalesAgility\Imap\ImapException; +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Stream\StreamConnectionInterface; +use SalesAgility\Utility\StringValue; + +/** + * Class PhpImapExtensionMessageTransporter + * @package SalesAgility\Imap\Stream + */ +class PhpImapExtensionMessageTransporter implements CommandTransporterInterface +{ + /** @var PhpImpExtensionConnection */ + private $connection; + + /** @var PipelineInterface */ + private $pipeline; + + public function __construct($container) + { + } + + /** + * @param PipelineInterface $pipeline + */ + public function setPipeLine(PipelineInterface $pipeline) + { + $this->pipeline = $pipeline; + } + + /** + * @return PipelineInterface + */ + public function pipeline() + { + return $this->pipeline; + } + + /** + * Not Implemented + * @param StreamConnectionInterface $connection + * @throws \Exception + */ + public function setConnection(StreamConnectionInterface $connection) + { + if ($connection instanceof PhpImpExtensionConnection) { + $this->connection = $connection; + } else { + throw new \Exception('Native client is only compatible with PhpImpExtensionConnection'); + } + } + + /** + * @return StreamConnectionInterface + */ + public function connection() + { + return $this->connection; + } + + /** + * Not Implemented + * @param string $string + * @throws \Exception + */ + public function transmit($string) + { + throw new \Exception('Native Php Client handles it\'s own transmission'); + } + + /** + * Not Implemented + * @return string|void + * @throws \Exception + */ + public function receive() + { + throw new \Exception('Native Php Client handles it\'s own transmission'); + } + + /** + * Not Implemented + * @param string $string + * @return bool|void + * @throws \Exception + */ + public function isEndOfFile($string) + { + throw new \Exception('Native Php Client handles it\'s own transmission'); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return mixed + * @throws ImapException + * @throws \Exception + */ + public function transmitCommand(CommandBuildArgumentsInterface $command) + { + // cases are ordered by most likely to be used + switch ($command->command()) { + case 'LOGIN': + return $this->login($command); + case 'SELECT': + return $this->select($command); + case 'FETCH': + return $this->fetch($command); + case 'SEARCH': + return $this->search($command); + case 'UID': + return $this->uid($command); + case 'STATUS': + return $this->select($command); + case 'LIST': + return $this->listMailboxes($command); + case 'LSUB': + return $this->lsub($command); + case 'LOGOUT': + return $this->logout($command); + case 'EXPUNGE': + return $this->expunge($command); + case 'CHECK': + return $this->check($command); + case 'EXAMINE': + return $this->select($command); + case 'STORE': + return $this->store($command); + case 'NOOP': + return $this->noop($command); + case 'CREATE': + return $this->create($command); + case 'RENAME': + return $this->rename($command); + case 'DELETE': + return $this->delete($command); + case 'SUBSCRIBE': + return $this->subscribe($command); + case 'UNSUBSCRIBE': + return $this->unsubscribe($command); + case 'CLOSE': + return $this->close($command); + case 'COPY': + return $this->copy($command); + case 'APPEND': + return $this->append($command); + default: + throw new \Exception('Command Not Supported: ' . $command->command()); + } + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return mixed + * @throws \Exception + */ + private function uid(CommandBuildArgumentsInterface $command) + { + $this->addCommand($command); + if (array_key_exists('FETCH', $command->commandArguments())) { + return $this->fetch($command, true); + } elseif (array_key_exists('SEARCH', $command->commandArguments())) { + return $this->search($command, true); + } elseif (array_key_exists('STORE', $command->commandArguments())) { + return $this->store($command, true); + } elseif (array_key_exists('COPY', $command->commandArguments())) { + return $this->copy($command, true); + } else { + throw new \Exception('UID Command Not Supported: ' . $command->command()); + } + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + * @throws \Exception + */ + private function noop(CommandBuildArgumentsInterface $command) + { + $this->addCommand($command); + imap_ping($this->connection->connection); + $this->checkErrors(); + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK Nothing Happened.' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + * @throws \Exception + */ + private function login(CommandBuildArgumentsInterface $command) + { + $this->addCommand($command); + $arg = $command->commandArguments(); + $this->connection->username = $arg['USER']; + $this->connection->password = $arg['PASSWORD']; + $this->connection->connect(); + $this->checkErrors(); + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK login success.' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + */ + private function logout(CommandBuildArgumentsInterface $command) + { + $this->addCommand($command); + $this->connection->disconnect(); + $includedMessage = StringIterator::withLiteral('* BYE Logging out' . "\r\n"); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK Logout completed.' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Mailbox + * @throws \Exception + */ + private function select(CommandBuildArgumentsInterface $command) + { + $this->addCommand($command); + $arg = $command->commandArguments(); + $selected = '{' . $this->connection->server . ':' . $this->connection->port . $this->connection->security . '}' . $arg['MAILBOX']; + imap_reopen($this->connection->connection, $selected); + $this->checkErrors(); + $mailbox = new Mailbox(); + $this->connection->mailbox = $arg['MAILBOX']; + $mailbox->offsetSet('exists', (string)imap_num_msg($this->connection->connection)); + $this->checkErrors(); + $mailbox->offsetSet('recent', (string)imap_num_recent($this->connection->connection)); + $this->checkErrors(); + return $this->addResponse($mailbox); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @param bool $isUid + * @return array|MessageList + * @throws \Exception + */ + private function fetch(CommandBuildArgumentsInterface $command, $isUid = false) + { + $this->addCommand($command); + if ($isUid) { + $arguments = $command->commandArguments()['FETCH']; + } else { + $arguments = $command->commandArguments(); + } + + $messageList = new MessageList(); + $rfc2822 = new Rfc2822Interpreter(); + + // populate header fields + // fetch()->header()->uid()->flags + $headers = $this->imap_fetch_overview($this->connection->connection, $arguments, $isUid); + $this->checkErrors(); + foreach ($headers as $header) { + $message = MessageFactory::instance(); + $messageFlags = new MessageFlags(); + foreach ($header as $fieldName => $fieldValue) { + if ($message->header()->offsetExists($fieldName)) { + // header field + $mapped = $this->mapHeaderFieldName($fieldName); + $rfc2822->parseHeaderField($mapped, $fieldValue, $message); + } elseif ($messageFlags->offsetExists($this->mapHeaderFieldName($fieldName))) { + // flags + if ($fieldValue === 1) { + $messageFlags->offsetSet($this->mapHeaderFieldName($fieldName), (bool)$fieldValue); + } + } elseif ($fieldName === 'message_id') { + // map to header + $fn = 'messageId'; + $rfc2822->parseHeaderField($fn, $fieldValue, $message); + } elseif ($fieldName === 'msgno') { + $message->offsetSet('number', (string)$fieldValue); + } elseif ($fieldName === 'uid') { + $message->offsetSet('uid', (string)$fieldValue); + } + } + + $message->offsetSet('flags', $messageFlags); + + $bodystructure = $this->imap_fetchstructure($this->connection->connection, $message, $isUid); + $this->checkErrors(); + $message->body()->structure()->offsetSet('attachments', (bool)$bodystructure->ifdisposition); + // fetch()->body() + if (array_search('BODY[TEXT]', $arguments['FIELDS']) !== false) { + $this->fetchBody($this->connection->connection, $message, $bodystructure, $isUid); + } + + $messageList[] = $message; + } + + return $this->addResponse($messageList); + } + + /** + * @param resource $connection + * @param Message $message + * @param \stdClass|array $bodystructure + * @param bool $isUid + * @throws \Exception + * @see http://php.net/manual/en/function.imap-fetchstructure.php#85486 + */ + private function fetchBody($connection, &$message, $bodystructure, $isUid = false) + { + if (!isset($bodystructure->parts)) { + // plain + $this->fetchBodyParts($connection, $message, $bodystructure, 0, $isUid); + } else { + // multipart + foreach ($bodystructure->parts as $part => $parameters) { + $this->fetchBodyParts($connection, $message, $parameters, $part + 1, $isUid); + } + } + } + + /** + * @param resource $connection + * @param Message $message + * @param array $parameters + * @param integer|string $part + * @param bool $isUid + * @throws \Exception + * @see http://php.net/manual/en/function.imap-fetchstructure.php#85486 + */ + private function fetchBodyParts($connection, &$message, $parameters, $part, $isUid = false) + { + // DECODE DATA + $data = ($part) ? + // multipart + $this->imap_fetchbody($connection, $message, $part, $isUid) : + // plain text + $this->imap_body($connection, $message, $isUid); + $this->checkErrors(); + // Any part may be encoded, even plain text messages, so check everything. + if ($parameters->encoding == 4) { + $data = quoted_printable_decode($data); + } elseif ($parameters->encoding == 3) { + $data = base64_decode($data); + } + + // PARAMETERS + // get all parameters, like charset, filenames of attachments, etc. + $params = array(); + if (isset($parameters->parameters)) { + foreach ($parameters->parameters as $parameter) { + $params[strtolower($parameter->attribute)] = $parameter->value; + } + } + + if (isset($parameters->dparameters)) { + foreach ($parameters->dparameters as $dparameter) { + $params[strtolower($parameter->attribute)] = $dparameter->value; + } + } + + // ATTACHMENT + if (array_key_exists('filename', $params) || array_key_exists('name', $params)) { + $filename = (array_key_exists('filename', $params)) ? $params['filename'] : $params['name']; + $attachment = $this->buildAttachment(); + // if inline + if (isset($parameters->disposition) && strtolower($parameters->disposition) === 'inline') { + $attachment->offsetSet('isInline', true); + $attachment->offsetSet('contentId', trim($parameters->id, '<>')); + } else { + $attachment->offsetSet('isInline', false); + } + + $attachment->offsetSet('content', $data); + $attachment->offsetSet('hasContent', true); + $finfo = new \finfo(FILEINFO_MIME); + $attachment->structure()->offsetSet('type', $finfo->buffer($data)); + $attachment->structure()->offsetSet('size', $parameters->bytes); + $attachment->structure()->offsetSet('name', $filename); + $message->body()->offsetSet('attachments', $attachment); + } + + // TEXT + if ($parameters->type == 0 && $data) { + if (strtolower($parameters->subtype) == 'plain') { + $message['body']['text'] .= $data; + } else { + $message['body']['html'] .= $data; + } + } elseif ($parameters->type == 2 && $data) { + $message['body']['text'] .= $data; + } + + // SUBPART RECURSION + if (isset($parameters->parts)) { + foreach ($parameters->parts as $subpart => $subpartParameters) { + // 1.2, 1.2.1, etc. + $this->fetchBodyParts($connection, $message, $subpartParameters, $part . '.' . ($subpart + 1)); + } + } + } + + /** + * @param $connection + * @param $arguments + * @param $isUid + * @return array|mixed + */ + private function imap_fetch_overview($connection, $arguments, $isUid) + { + return ($isUid) ? + imap_fetch_overview($connection, $arguments['MESSAGE'], FT_UID) : + imap_fetch_overview($connection, $arguments['MESSAGE'], 0); + } + + /** + * @param $connection + * @param $message + * @param $isUid + * @return array|mixed|object + */ + private function imap_fetchstructure($connection, $message, $isUid) + { + return ($isUid) ? + imap_fetchstructure($connection, $message->number(), FT_UID) : + imap_fetchstructure($connection, $message->number()); + } + + /** + * @param $connection + * @param $message + * @param $part + * @param $isUid + * @return array|mixed|string + */ + private function imap_fetchbody($connection, $message, $part, $isUid) + { + return ($isUid) ? + imap_fetchbody($connection, $message->number(), $part, FT_UID) : + imap_fetchbody($connection, $message->number(), $part); + } + + /** + * @param $connection + * @param $message + * @param $isUid + * @return array|mixed|string + */ + private function imap_body($connection, $message, $isUid) + { + return ($isUid) ? + imap_body($connection, $message->number(), FT_UID) : imap_body($connection, $message->number()); + } + + /** + * @param $fieldName + * @return string + */ + private function mapHeaderFieldName($fieldName) + { + return ucwords($fieldName, '-'); + } + + /** + * @return MessageAttachment + * @throws \Exception + */ + private function buildAttachment() + { + $attachment = new MessageAttachment(); + $attachment->offsetSet('structure', new MessageAttachmentStructure()); + return $attachment; + } + + /** + * @param CommandBuildArgumentsInterface $command + * @param bool $isUid + * @return array|MessageList + * @throws \Exception + */ + private function search(CommandBuildArgumentsInterface $command, $isUid = false) + { + $this->addCommand($command); + if ($isUid) { + $arguments = $command->commandArguments()['SEARCH']; + $messages = imap_search($this->connection->connection, implode(' ', $arguments), FT_UID); + } else { + $arguments = $command->commandArguments(); + $messages = imap_search($this->connection->connection, implode(' ', $arguments)); + } + + $this->checkErrors(); + + $messageList = new MessageList(); + foreach ($messages as $messageNumber) { + $message = new Message(); + $message->offsetSet('number', $messageNumber->number()); + $messageList[] = $message; + } + + return $this->addResponse($messageList); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @param bool $isUid + * @return mixed + * @throws \Exception + */ + private function store(CommandBuildArgumentsInterface $command, $isUid = false) + { + $this->addCommand($command); + $arguments = $command->commandArguments(); + $flags = array(); + + if (isset($arguments['-FLAGS'])) { + foreach ($arguments['-FLAGS'] as $flag) { + $flags[] = '\\' . $flag; + } + + if ($isUid) { + imap_clearflag_full($this->connection->connection, $command->commandArguments()['MESSAGE'], implode(' ', $flags), FT_UID); + } else { + imap_clearflag_full($this->connection->connection, $command->commandArguments()['MESSAGE'], implode(' ', $flags)); + } + } else { + if (isset($arguments['FLAGS'])) { + foreach ($arguments['FLAGS'] as $flag) { + $flags[] = '\\' . $flag; + } + } + + if (isset($arguments['+FLAGS'])) { + foreach ($arguments['+FLAGS'] as $flag) { + $flags[] = '\\' . $flag; + } + } + + if ($isUid) { + imap_setflag_full($this->connection->connection, $command->commandArguments()['MESSAGE'], implode(' ', $flags), FT_UID); + } else { + imap_setflag_full($this->connection->connection, $command->commandArguments()['MESSAGE'], implode(" ", $arguments['FLAGS'])); + } + } + + $this->checkErrors(); + + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK ' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + private function copy(CommandBuildArgumentsInterface $command, $isUid = false) + { + $this->addCommand($command); + $arguments = $command->commandArguments(); + if ($isUid) { + $arguments = $command->commandArguments()['COPY']; + $mailbox = $arguments['MAILBOX']; + $message = $arguments['MESSAGE']; + + imap_mail_copy($this->connection->connection, $message, $mailbox, FT_UID); + } else { + $arguments = $command->commandArguments(); + $mailbox = $arguments['MAILBOX']; + $message = $arguments['MESSAGE']; + imap_mail_copy($this->connection->connection, $message, $mailbox); + } + + $this->checkErrors(); + + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK ' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + + /** + * @param CommandBuildArgumentsInterface $command + * @return array|MailboxList + */ + private function listMailboxes(CommandBuildArgumentsInterface $command) + { + $ref = $command->commandArguments()['REFERENCE_NAME']; + $mailbox = $command->commandArguments()['MAILBOX']; + + $mailboxes = imap_list($this->connection->connection, $this->buildRef($ref), $mailbox); + $this->checkErrors(); + + return $this->mapList($mailboxes, $this->buildRef($ref), $command); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return array|MailboxList + */ + private function lsub(CommandBuildArgumentsInterface $command) + { + // imap_lsub + $ref = $command->commandArguments()['REFERENCE_NAME']; + $mailbox = $command->commandArguments()['MAILBOX']; + + $mailboxes = imap_lsub($this->connection->connection, $this->buildRef($ref), $mailbox); + $this->checkErrors(); + + return $this->mapList($mailboxes, $this->buildRef($ref), $command); + } + + private function buildRef($ref) + { + if (empty($ref)) { + $ref = '{' . $this->connection->server . ':' . $this->connection->port . $this->connection->security . '}'; + } + + return $ref; + } + + private function mapList($mailboxes, $ref, CommandBuildArgumentsInterface $command) + { + $mailboxList = new MailboxList(); + foreach ($mailboxes as $mailbox) { + $rmailbox = new Mailbox(); + if (StringValue::startsWith($mailbox, $ref)) { + $rmailbox->offsetSet('name', substr($mailbox, strlen($ref))); + $rmailbox->offsetSet('hierarchy', $command->commandArguments()['REFERENCE_NAME']); + } else { + $rmailbox->offsetSet('name', $mailbox); + $rmailbox->offsetSet('hierarchy', $command->commandArguments()['REFERENCE_NAME']); + + } + + $mailboxList[] = $rmailbox; + } + return $mailboxList; + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + * @throws \Exception + */ + private function check(CommandBuildArgumentsInterface $command) + { + + $object = imap_check($this->connection->connection); + $this->checkErrors(); + + $this->addCommand($command); + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK ' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + * @throws \Exception + */ + private function create(CommandBuildArgumentsInterface $command) + { + imap_createmailbox($this->connection->connection, $command->commandArguments()['MAILBOX']); + $this->checkErrors(); + + $this->addCommand($command); + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK ' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + * @throws \Exception + */ + private function delete(CommandBuildArgumentsInterface $command) + { + //imap_delete + imap_delete($this->connection->connection, $command->commandArguments()['MAILBOX']); + $this->checkErrors(); + + $this->addCommand($command); + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK ' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + * @throws \Exception + */ + private function rename(CommandBuildArgumentsInterface $command) + { + imap_renamemailbox($this->connection->connection, $command->commandArguments()['MAILBOX'], $command->commandArguments()['NEW_MAILBOX']); + $this->checkErrors(); + + $this->addCommand($command); + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK ' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + * @throws \Exception + */ + private function subscribe(CommandBuildArgumentsInterface $command) + { + imap_subscribe($this->connection->connection, $command->commandArguments()['MAILBOX']); + $this->checkErrors(); + + $this->addCommand($command); + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK ' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + * @throws \Exception + */ + private function unsubscribe(CommandBuildArgumentsInterface $command) + { + imap_unsubscribe($this->connection->connection, $command->commandArguments()['MAILBOX']); + $this->checkErrors(); + + $this->addCommand($command); + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK ' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + * @throws \Exception + */ + private function close(CommandBuildArgumentsInterface $command) + { + imap_expunge($this->connection->connection); + $this->checkErrors(); + + $this->addCommand($command); + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK ' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + * @throws \Exception + */ + private function expunge(CommandBuildArgumentsInterface $command) + { + imap_expunge($this->connection->connection); + $this->checkErrors(); + + $this->addCommand($command); + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK ' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + /** + * @param CommandBuildArgumentsInterface $command + * @return Response + * @throws \Exception + */ + private function append(CommandBuildArgumentsInterface $command) + { + imap_append($this->connection->connection, $command->commandArguments()['MAILBOX'], $command->commandArguments()['MESSAGE']); + $this->checkErrors(); + + $this->addCommand($command); + $includedMessage = StringIterator::withLiteral('', 0, 0); + $responseMessage = StringIterator::withLiteral($this->pipeline->getLastPipe()->getTag() . ' OK ' . "\r\n"); + return $this->addResponse(new Response('OK', $responseMessage, $includedMessage)); + } + + /** + * @throws \Exception + */ + private function checkErrors() + { + if (imap_last_error()) { + throw new \Exception(imap_last_error()); + } + } + + /** + * Add command to pipeline + * @param CommandBuildInterface $command + */ + private function addCommand(CommandBuildInterface $command) + { + $this->pipeline->add($command); + } + + /** + * Add Response to pipeline + * @param mixed $response + * @return mixed + */ + private function addResponse($response) + { + $this->pipeline->getLastPipe()->addParsed($response); + return $response; + } +} \ No newline at end of file diff --git a/src/Imap/Stream/PhpImpExtensionConnection.php b/src/Imap/Stream/PhpImpExtensionConnection.php new file mode 100755 index 0000000..dc4700c --- /dev/null +++ b/src/Imap/Stream/PhpImpExtensionConnection.php @@ -0,0 +1,137 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Stream; + + +use Psr\Log\LoggerInterface; +use SalesAgility\Stream\StreamConnectionInterface; + +/** + * Class PhpImpExtensionConnection + * @package SalesAgility\Imap\Stream + * The native php extension handles it own connection and transport + */ +class PhpImpExtensionConnection implements StreamConnectionInterface +{ + public $connection; + public $server = ''; + public $port = ''; + public $security = ''; + public $username = ''; + public $password = ''; + public $mailbox = 'INBOX'; + /** @var LoggerInterface */ + private $log; + + public function __construct($container) + { + } + + /** + * @param string $string + * @throws \Exception + */ + public function setConnectionString($string) + { + if (strpos($string, '{') !== false) { + throw new \Exception('Expected tcp://hostname:portnumber'); + } + + $string = str_replace('tcp://', '', $string); + $string = str_replace('/', '', $string); + $opt = explode(':', $string); + $this->server = $opt[0]; + $this->port = $opt[1]; + } + + /** + * @param string $security + */ + public function enableEncryption($security = '/imap/ssl/novalidate-cert') + { + $this->security = $security; + } + + public function disableEncryption() + { + $this->security = ''; + } + + /** + * @return bool + */ + public function connect() + { + if ($this->username === '') { + // wait for login command + $this->connection = 1; + return false; + } + $mailbox = '{' . $this->server . ':' . $this->port . $this->security . '}' . $this->mailbox; + $this->connection = imap_open($mailbox, $this->username, $this->password, OP_HALFOPEN); + return true; + } + + public function disconnect() + { + if (!empty($this->connection) and $this->connection !== 1) { + imap_close($this->connection); + } + + $this->connection = null; + } + + /** + * @return bool + */ + public function isConnected() + { + return $this->connection !== null; + } + + /** + * @param $message + * @throws \Exception + */ + public function transmitMessage($message) + { + throw new \Exception('Not supported'); + } + + /** + * @return bool|string|void + * @throws \Exception + */ + public function readMessage() + { + throw new \Exception('Not supported'); + } + + /** + * @param string $string + * @return bool|void + * @throws \Exception + */ + public function isEndOfFile($string = "") + { + throw new \Exception('Not supported'); + } +} \ No newline at end of file diff --git a/src/Imap/Token/Token.php b/src/Imap/Token/Token.php new file mode 100755 index 0000000..b9d1845 --- /dev/null +++ b/src/Imap/Token/Token.php @@ -0,0 +1,185 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Imap\Token; + +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Iteration\StringIteratorInterface; + +/** + * Class Token + * @package SalesAgility\Imap\Token + * @see https://www.ietf.org/rfc/rfc2822.txt + * Primitive Tokens + */ +class Token implements TokenIteratorInterface +{ + /** @var TokenType $type */ + private $type; + + /** + * Token constructor. + * @param StringIteratorInterface $iterator + * @param TokenType $type + */ + public function __construct(StringIteratorInterface $iterator, TokenType $type) + { + $this->iterator = $iterator; + $this->type = $type; + } + + /** + * @return TokenType + */ + public function type() + { + return $this->type; + } + + /** + * @return string + */ + public function toString() + { + $currentKey = $this->iterator->key(); + $this->iterator->rewind(); + $string = ""; + + foreach ($this->iterator as $character) { + $string .= $character; + } + + $this->iterator->seek($currentKey); + return $string; + } + + /** + * @return int + */ + public function firstKey() + { + $currentKey = $this->key(); + $this->rewind(); + $firstKey = $this->key(); + $this->seek($currentKey); + return $firstKey; + } + + /** + * @return int + */ + public function lastKey() + { + $currentKey = $this->key(); + $this->fastForward(); + $lastKey = $this->key(); + $this->seek($currentKey); + return $lastKey; + } + + /** @var StringIteratorInterface $iterator */ + protected $iterator; + + /** + * @return string + */ + public function current() + { + return $this->iterator->current(); + } + + public function next() + { + $this->iterator->next(); + } + + /** + * @return int + */ + public function key() + { + return $this->iterator->key(); + } + + /** + * @return bool + */ + public function valid() + { + return $this->iterator->valid(); + } + + public function rewind() + { + $this->iterator->rewind(); + } + + public function fastForward() + { + $this->iterator->fastForward(); + } + + /** + * @param int $position + */ + public function seek($position) + { + $this->iterator->seek($position); + } + + /** + * @return string + */ + public function getInnerString() + { + return $this->iterator->getInnerString(); + } + + /** + * @return int + */ + public function count() + { + return $this->iterator->count(); + } + + /** + * @return int + */ + public function first() + { + return $this->iterator->first(); + } + + /** + * @return int + */ + public function last() + { + return $this->iterator->last(); + } + + /** + * @return StringIterator + */ + public function getInnerIterator() + { + return $this->iterator; + } +} \ No newline at end of file diff --git a/src/Imap/Token/TokenException.php b/src/Imap/Token/TokenException.php new file mode 100755 index 0000000..a1b0969 --- /dev/null +++ b/src/Imap/Token/TokenException.php @@ -0,0 +1,40 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Token; + + +/** + * Class TokenException + * @package SalesAgility\Imap\Token + */ +class TokenException extends \Exception +{ + const LINE_REQUIREMENT = 1; + + /** + * @return TokenException + */ + public static function requiredLineLengthExceeded() + { + return new self('RFC2822 requires line length is less than or equals to than 998 characters', self::LINE_REQUIREMENT); + } + +} \ No newline at end of file diff --git a/src/Imap/Token/TokenIteratorInterface.php b/src/Imap/Token/TokenIteratorInterface.php new file mode 100755 index 0000000..f949c85 --- /dev/null +++ b/src/Imap/Token/TokenIteratorInterface.php @@ -0,0 +1,41 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Token; + + +use SalesAgility\Iteration\StringIteratorInterface; + +/** + * Interface TokenIterator + * @package SalesAgility\Imap\Token + */ +interface TokenIteratorInterface extends StringIteratorInterface +{ + /** + * @return mixed + */ + public function firstKey(); + + /** + * @return mixed + */ + public function lastKey(); +} \ No newline at end of file diff --git a/src/Imap/Token/TokenList.php b/src/Imap/Token/TokenList.php new file mode 100755 index 0000000..a0ea6d7 --- /dev/null +++ b/src/Imap/Token/TokenList.php @@ -0,0 +1,130 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Token; + +/** + * Class TokenList + * @package SalesAgility\Imap\Token + * A list of \SalesAgility\Imap\Token\Token objects + */ +class TokenList implements \Iterator, \ArrayAccess +{ + private $tokenList = array(); + private $currentKey = 0; + + /** + * @return Token + */ + public function current() + { + return $this->tokenList[$this->currentKey]; + } + + public function next() + { + ++$this->currentKey; + } + + /** + * @return int + */ + public function key() + { + return $this->currentKey; + } + + /** + * @return bool + */ + public function valid() + { + return $this->currentKey >= 0 && $this->currentKey < count($this->tokenList); + } + + public function rewind() + { + $this->currentKey = 0; + } + + /** + * @param $offset + */ + public function seek($offset) + { + $this->currentKey = $offset; + } + + /** + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->tokenList); + } + + /** + * @param int $offset + * @return Token + */ + public function offsetGet($offset) + { + return $this->tokenList[$offset]; + } + + /** + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $newOffset = false; + if ($offset === null) { + $newOffset = true; + } elseif (gettype($offset) !== "integer") { + throw new \InvalidArgumentException('Token List can only store integer key values'); + } + + if ($value instanceof Token) { + if (!$newOffset) { + $this->tokenList[$offset] = $value; + } else { + $count = count($this->tokenList); + if ($count === 0) { + $this->tokenList[0] = $value; + } else { + $this->tokenList[$count] = $value; + } + } + } else { + throw new \InvalidArgumentException('Token List can only store values which derive from a Token'); + } + } + + /** + * @param $offset + */ + public function offsetUnset($offset) + { + unset($this->tokenList[$offset]); + $this->tokenList = array_values($this->tokenList); + } +} \ No newline at end of file diff --git a/src/Imap/Token/TokenType.php b/src/Imap/Token/TokenType.php new file mode 100755 index 0000000..978a4ed --- /dev/null +++ b/src/Imap/Token/TokenType.php @@ -0,0 +1,322 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Token; + +/** + * Class TokenType + * @package SalesAgility\Imap\Token + * @see https://www.ietf.org/rfc/rfc2822.txt + * Primitive Tokens Types + */ +class TokenType +{ + // Token Indicator for parsing purposes only + const RECOMMENDED_LINE_SIZE = -100; + const REQUIRED_LINE_SIZE = -101; + + // Types + const FWSP = 1; + const NO_WS_CTL = 2; + const SPECIAL = 3; + const TEXT = 4; + const QUOTED_PAIR = 5; + const EOL = 6; + const CTL = 7; + const GROUP = 8; + const OPTIONAL = 9; + const ANGLE_ADDR = 10; + const AT = 11; + const DOT = 12; + const LIST_SEPARATOR = 13; + const WSP = 14; + const PAIRED = 15; + + private $type; + + /** + * TokenType constructor. + * @param $type + */ + private function __construct($type) + { + $this->type = $type; + } + + /** + * @return bool + */ + public function isFoldingWhiteSpace() + { + return self::FWSP === $this->type; + } + + /** + * @return TokenType + */ + public static function foldingWhiteSpace() + { + return new self(self::FWSP); + } + + /** + * @return bool + */ + public function isEndOfLine() + { + return self::EOL === $this->type; + } + + /** + * @return TokenType + */ + public static function endOfLine() + { + return new self(self::EOL); + } + + /** + * @return bool + */ + public function isGroup() + { + return self::GROUP === $this->type; + } + + /** + * @return TokenType + */ + public static function group() + { + return new self(self::GROUP); + } + + /** + * @return bool + */ + public function isNonFoldedLiteral() + { + return self::OPTIONAL === $this->type; + } + + /** + * @return TokenType + */ + public static function nonFoldedLiteral() + { + return new self(self::OPTIONAL); + } + + /** + * @return bool + */ + public function isControlCharacter() + { + return self::CTL === $this->type; + } + + /** + * @return TokenType + */ + public static function controlCharacter() + { + return new self(self::CTL); + } + + /** + * @return bool + */ + public function isQuoted() + { + return self::QUOTED_PAIR === $this->type; + } + + /** + * @return TokenType + */ + public static function quoted() + { + return new self(self::QUOTED_PAIR); + } + + /** + * @return bool + */ + public function isAngledAddress() + { + return self::ANGLE_ADDR === $this->type; + } + + /** + * @return TokenType + */ + public static function angledAddress() + { + return new self(self::ANGLE_ADDR); + } + + /** + * @return bool + */ + public function isAtSign() + { + return self::AT === $this->type; + } + + /** + * @return TokenType + */ + public static function atSign() + { + return new self(self::AT); + } + + /** + * @return bool + */ + public function isDot() + { + return self::DOT === $this->type; + } + + /** + * @return TokenType + */ + public static function dot() + { + return new self(self::DOT); + } + + /** + * @return bool + */ + public function isListSeparator() + { + return self::LIST_SEPARATOR === $this->type; + } + + /** + * @return TokenType + */ + public static function listSeparator() + { + return new self(self::LIST_SEPARATOR); + } + + /** + * @return bool + */ + public function isWhiteSpace() + { + return self::WSP === $this->type; + } + + /** + * @return TokenType + */ + public static function whiteSpace() + { + return new self(self::WSP); + } + + /** + * @return bool + */ + public function isNotWhiteSpaceOrControl() + { + return self::NO_WS_CTL === $this->type; + } + + /** + * @return TokenType + */ + public static function notWhiteSpaceOrControl() + { + return new self(self::NO_WS_CTL); + } + + /** + * @return bool + */ + public function isSpecial() + { + return self::SPECIAL === $this->type; + } + + /** + * @return TokenType + */ + public static function special() + { + return new self(self::SPECIAL); + } + + // Token Indicator for parsing purposes only + + /** + * @return bool + */ + public function isRecommendedLineLength() + { + return self::RECOMMENDED_LINE_SIZE === $this->type; + } + + /** + * @return TokenType + */ + public static function recommendedLineLength() + { + return new self(self::RECOMMENDED_LINE_SIZE); + } + + /** + * @return bool + */ + public function isRequiredLineLength() + { + return self::REQUIRED_LINE_SIZE === $this->type; + } + + /** + * @return TokenType + */ + public static function requiredLineLength() + { + return new self(self::REQUIRED_LINE_SIZE); + } + + /** + * @return bool + */ + public function isPaired() + { + return self::PAIRED === $this->type; + } + + /** + * @return TokenType + */ + public static function paired() + { + return new self(self::PAIRED); + } + + +} \ No newline at end of file diff --git a/src/Imap/Token/Tokenizer.php b/src/Imap/Token/Tokenizer.php new file mode 100755 index 0000000..c3042ab --- /dev/null +++ b/src/Imap/Token/Tokenizer.php @@ -0,0 +1,755 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Imap\Token; + + +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Iteration\StringIteratorInterface; + +/** + * Class Tokenizer + * @package SalesAgility\Imap\Token + * @see https://www.ietf.org/rfc/rfc2822.txt + * Detects Primitive Tokens and boundary positions + */ +class Tokenizer +{ + /** + * @param StringIterator $characters + * @return TokenList + * @throws TokenException + */ + public function parse(StringIterator $characters) + { + $list = new TokenList(); + $lastEOL = 0; + $recommendedLineLength = 78; + $requiredLineLength = 998; + $requiredLineLengthFound = false; + + // When groups are being parsed the $characterIndexOffset + // ensures that the line length detection is correct. + $characterIndexOffset = $characters->key(); + + foreach ($characters as $characterIndex => $character) { + // Line length error detection + if (($characterIndex - $characterIndexOffset) - $lastEOL === $recommendedLineLength) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::recommendedLineLength()); + } elseif (($characterIndex - $characterIndexOffset) - $lastEOL === $requiredLineLength) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::requiredLineLength()); + $requiredLineLengthFound = true; + } + + if ($this->isCarriageReturn($character)) { + // what kind of token? + if (false !== ($token = $this->seekLineFolding($characters))) { + $list[] = $token; + $lastEOL = $characterIndex; + $requiredLineLengthFound = false; + continue; + } elseif (false !== ($token = $this->seekEndOfLine($characters))) { + $list[] = $token; + $lastEOL = $characterIndex; + $requiredLineLengthFound = false; + continue; + } else { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::controlCharacter()); + if ($requiredLineLengthFound) { + throw TokenException::requiredLineLengthExceeded(); + } + continue; + } + } + + if ($this->isWhiteSpace($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::whiteSpace()); + if ($requiredLineLengthFound) { + throw TokenException::requiredLineLengthExceeded(); + } + continue; + } + + if ($this->isLineFeed($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::controlCharacter()); + + if ($requiredLineLengthFound) { + throw TokenException::requiredLineLengthExceeded(); + } + continue; + } + + if ($this->isSpecial($character)) { + if ($requiredLineLengthFound) { + throw TokenException::requiredLineLengthExceeded(); + } + // What kind? + if ($this->isSpecialEscape($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + + if ($this->isSpecialAt($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::atSign()); + continue; + } + + if ($this->isSpecialDot($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::dot()); + continue; + } + + if ($this->isSpecialListSeparator($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::listSeparator()); + continue; + } + + if ($this->isSpecialColon($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + /** + * Note: + * The tokenizer does not recursively process inside + * groups, options, addresses, quoted pairs etc. + * + * It is left to the high level parsers to decide when + * to tokenize the inner tokens of a pair, as it can pass the subset back to + * the tokenizer later for further processing + */ + // what kind of token? + if ($this->isSpecialGroup($character)) { + if (false !== ($token = $this->seekClosingPair($characters, '(', ')'))) { + $list[] = $token; + continue; + } else { + // possible error? - let the higher level parsers decide + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + } + + if ($this->isSpecialOption($character)) { + if (false !== ($token = $this->seekClosingPair($characters, '[', ']'))) { + $list[] = $token; + continue; + } else { + // possible error? - let the higher level parsers decide + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + } + + if ($this->isSpecialAngledAddress($character)) { + if (false !== ($token = $this->seekClosingPair($characters, '<', '>'))) { + $list[] = $token; + continue; + } else { + // possible error? - let the higher level parsers decide + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + } + // Character is maybe error? + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + + if ($this->isQuotedPair($character)) { + if (false !== ($token = $this->seekClosingPair($characters, '"', '"'))) { + $list[] = $token; + continue; + } else { + // possible error? - let the higher level parsers decide + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + } + + if ($this->isNotWhiteSpaceOrControl($character)) { + if ($requiredLineLengthFound) { + throw TokenException::requiredLineLengthExceeded(); + } + $token = $this->seekNotWhiteSpaceOrControl($characters); + $list[] = $token; + continue; + } + } + + return $list; + } + + public function parseWithoutLineRestrictions(StringIterator $characters) + { + $list = new TokenList(); + + foreach ($characters as $characterIndex => $character) { + + + if ($this->isCarriageReturn($character)) { + // what kind of token? + if (false !== ($token = $this->seekLineFolding($characters))) { + $list[] = $token; + continue; + } elseif (false !== ($token = $this->seekEndOfLine($characters))) { + $list[] = $token; + continue; + } else { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::controlCharacter()); + continue; + } + } + + if ($this->isWhiteSpace($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::whiteSpace()); + continue; + } + + if ($this->isLineFeed($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::controlCharacter()); + continue; + } + + if ($this->isSpecial($character)) { + + // What kind? + if ($this->isSpecialEscape($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + + if ($this->isSpecialAt($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::atSign()); + continue; + } + + if ($this->isSpecialDot($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::dot()); + continue; + } + + if ($this->isSpecialListSeparator($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::listSeparator()); + continue; + } + + if ($this->isSpecialColon($character)) { + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + /** + * Note: + * The tokenizer does not recursively process inside + * groups, options, addresses, quoted pairs etc. + * + * It is left to the high level parsers to decide when + * to tokenize the inner tokens of a pair, as it can pass the subset back to + * the tokenizer later for further processing + */ + // what kind of token? + if ($this->isSpecialGroup($character)) { + if (false !== ($token = $this->seekClosingPair($characters, '(', ')'))) { + $list[] = $token; + continue; + } else { + // possible error? - let the higher level parsers decide + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + } + + if ($this->isSpecialOption($character)) { + if (false !== ($token = $this->seekClosingPair($characters, '[', ']'))) { + $list[] = $token; + continue; + } else { + // possible error? - let the higher level parsers decide + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + } + + if ($this->isSpecialAngledAddress($character)) { + if (false !== ($token = $this->seekClosingPair($characters, '<', '>'))) { + $list[] = $token; + continue; + } else { + // possible error? - let the higher level parsers decide + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + } + // Character is maybe error? + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + + if ($this->isQuotedPair($character)) { + if (false !== ($token = $this->seekClosingPair($characters, '"', '"'))) { + $list[] = $token; + continue; + } else { + // possible error? - let the higher level parsers decide + $list[] = $this->tokenFrom($characters, $characterIndex, 1, TokenType::special()); + continue; + } + } + + if ($this->isNotWhiteSpaceOrControl($character)) { + $token = $this->seekNotWhiteSpaceOrControl($characters); + $list[] = $token; + continue; + } + } + + return $list; + } + + + /** + * @param string $character + * @return bool + */ + private function isNotWhiteSpaceOrControl($character) + { + // though this token type does allow specials + // we need to detect them for the purposes of helping + // the higher level parsers + return !$this->isWhiteSpace($character) + && !$this->isCarriageReturn($character) + && !$this->isLineFeed($character); + } + + /** + * @param string $character + * @return bool + */ + private function isWhiteSpace($character) + { + return $character === "\x20" || $character === "\x09"; + } + + /** + * @param string $character + * @return bool + */ + private function isCarriageReturn($character) + { + return $character === "\x0D"; + } + + /** + * @param string $character + * @return bool + */ + private function isLineFeed($character) + { + return $character === "\x0A"; + } + + /** + * @param string $character + * @return bool + */ + private function isSpecial($character) + { + switch ($character) { + case "\x28": + return true; + case "\x29": + return true; + case "\x3C": + return true; + case "\x3E": + return true; + case "\x5B": + return true; + case "\x5D": + return true; + case "\x3A": + return true; + case "\x3B": + return true; + case "\x40": + return true; + case "\x5C": + return true; + case "\x2E": + return true; + case "\x2C": + return true; + default: + return false; + } + } + + /** + * @param string $character + * @return bool + */ + private function isQuotedPair($character) + { + return $character === "\x22"; + } + + /** + * @param string $character + * @return bool + */ + private function isSpecialGroup($character) + { + return $character === "\x28"; + } + + /** + * @param string $character + * @return bool + */ + public function isSpecialClosingGroup($character) + { + return $character === "\x29"; + } + + /** + * @param string $character + * @return bool + */ + public function isSpecialClosingOption($character) + { + return $character === "\x5D"; + } + + /** + * @param string $character + * @return bool + */ + private function isSpecialOption($character) + { + return $character === "\x5B"; + } + + /** + * @param string $character + * @return bool + */ + private function isSpecialAngledAddress($character) + { + return $character === "\x3C"; + } + + /** + * @param string $character + * @return bool + */ + public function isSpecialClosingAngledAddress($character) + { + return $character === "\x3E"; + } + + /** + * @param string $character + * @return bool + */ + private function isSpecialAt($character) + { + return $character === "\x40"; + } + + /** + * @param string $character + * @return bool + */ + private function isSpecialDot($character) + { + return $character === "\x2E"; + } + + /** + * @param string $character + * @return bool + */ + private function isSpecialListSeparator($character) + { + return $character === "\x2C"; + } + + /** + * @param string $character + * @return bool + */ + public function isSpecialEscape($character) + { + return $character === "\x5C"; + } + + /** + * @param string $character + * @return bool + */ + public function isSpecialColon($character) + { + return $character === "\x3A"; + } + + /** + * @param string $character + * @return bool + */ + public function isDoubleQuote($character) + { + return $character === "\x22"; + } + + /** + * # IMPORTANT REQUIREMENT: seek[something] functions + * + * On a successful outcome: + * StringIterator::seek() must be set to the last character of the token you are seeking. This is to + * ensure that all tokens will be correctly identified. + * + * On a unsuccessful outcome + * StringIterator::seek() must be set to the initial StringIterator::key() that was passed into the function + * + * On an exception: + * An exception must be thrown, an exception is as sign that the process cannot continue. + */ + + + /** + * WARNING: Moves the StringIterator::key() to the position the start of the next line + * @param StringIteratorInterface $characters + * @return bool|Token false when EOL is not found + */ + private function seekEndOfLine(StringIteratorInterface $characters) + { + if (!$this->isCarriageReturn($characters->current())) { + throw new \InvalidArgumentException('Iterator::current() must start with a carriage return'); + } + + $startPosition = $characters->key(); + // We have already check that the current is CR + $characters->next(); + + if (!$characters->valid()) { + $characters->seek($startPosition); + return false; + } + + if (!$this->isLineFeed($characters->current())) { + $characters->seek($startPosition); + return false; + } + + $endPosition = $characters->key(); + $count = $endPosition - $startPosition + 1; + + return $this->tokenFrom($characters, $startPosition, $count, TokenType::endOfLine()); + } + + /** + * WARNING: Moves the StringIterator::key() to the position of the closing character + * @param StringIterator $characters current position must be the opening character + * @param string $openCharacter start of the pair "(" / "{" / DQUOTE + * @param string $closeCharacter end of the part ")" / "}" / DQUOTE + * @return bool|Token false when closing pair is not found + */ + private function seekClosingPair(StringIterator &$characters, $openCharacter, $closeCharacter) + { + if ($characters->current() !== $openCharacter) { + throw new \InvalidArgumentException('Iterator->current() position must be at the open character'); + } + + $startPosition = $characters->key(); + $groupsTotal = 0; + + // can't use foreach statement as it runs StringIteratorInterface::rewind() + while ($characters->valid()) { + $character = $characters->current(); + if ($character === $openCharacter) { + if ($openCharacter !== $closeCharacter) { + ++$groupsTotal; + } elseif ($openCharacter === $closeCharacter && $groupsTotal === 1) { + --$groupsTotal; + } else { + // since the open and close character are the same + // we need to bypass the increment + // the second " will not be de-incremented + // when expected + // handle escaped close character + $groupsTotal = 1; + } + } elseif ($character === $closeCharacter) { + // handle escaped close character + --$groupsTotal; + } else { + $characters->next(); + continue; + } + + if ($groupsTotal === 0) { + if ($character === $closeCharacter) { + $endPosition = $characters->key() + 1; + $count = $endPosition - $startPosition; + + if ($this->isSpecialGroup($openCharacter)) { + return $this->tokenFrom($characters, $startPosition, $count, TokenType::group()); + } elseif ($this->isSpecialOption($openCharacter)) { + return $this->tokenFrom($characters, $startPosition, $count, TokenType::nonFoldedLiteral()); + } elseif ($this->isDoubleQuote($openCharacter)) { + return $this->tokenFrom($characters, $startPosition, $count, TokenType::quoted()); + } elseif ($this->isSpecialAngledAddress($openCharacter)) { + return $this->tokenFrom($characters, $startPosition, $count, TokenType::angledAddress()); + } else { + return $this->tokenFrom($characters, $startPosition, $count, TokenType::paired()); + } + } + } + + $characters->next(); + } + + $characters->seek($startPosition); + return false; + } + + /** + * WARNING: Moves the StringIterator::key() to end of the fold + * Iterates over the currentLine and the next line to determine + * where the line is folding. + * @param StringIteratorInterface $characters + * @return bool|Token false when line folding is not found + */ + private function seekLineFolding(StringIteratorInterface &$characters) + { + if (!$this->isCarriageReturn($characters->current())) { + throw new \InvalidArgumentException('Iterator::current() must start with a carriage return'); + } + + $startPosition = $characters->key(); + // We have already check that the current is CR + $characters->next(); + if (!$characters->valid()) { + $characters->seek($startPosition); + return false; + } + + if (!$this->isLineFeed($characters->current())) { + $characters->seek($startPosition); + return false; + } + + $characters->next(); + if (!$characters->valid()) { + $characters->seek($startPosition); + return false; + } + + if (!$this->isWhiteSpace($characters->current())) { + $characters->seek($startPosition); + return false; + } + + // can't use foreach statement as it runs StringIteratorInterface::rewind() + // Find where folding ends + while ($characters->valid()) { + $character = $characters->current(); + + if ($this->isNotWhiteSpaceOrControl($character)) { + break; + } + + $characters->next(); + } + + $endPosition = $characters->key(); + $characters->seek($endPosition - 1); + $ref = $characters->getInnerString(); + $tokenString = new StringIterator($ref, $startPosition, $endPosition - $startPosition); + + return new Token($tokenString, TokenType::foldingWhiteSpace()); + } + + + /** + * WARNING: Moves the StringIterator::key() to the position of the last NO_WSP_CTL + * @param StringIteratorInterface $characters + * @return Token + */ + private function seekNotWhiteSpaceOrControl(StringIteratorInterface &$characters) + { + if (!$this->isNotWhiteSpaceOrControl($characters->current())) { + throw new \InvalidArgumentException('Iterator::current() must not start with whitespace or control character'); + } + + $startPosition = $characters->key(); + $count = 0; + // can't use foreach statement as it runs StringIteratorInterface::rewind() + while ($characters->valid()) { + $character = $characters->current(); + + if ($this->isQuotedPair($character)) { + $startQuotedPair = $characters->key(); + // skip passed quoted pair (for comments in header fields) + if (false !== ($pair = $this->seekClosingPair($characters, '"', '"'))) { + $firstKeyInPair = $pair->first(); + $lastKeyInPair = $pair->last(); + $skippedCharacters = $lastKeyInPair - $firstKeyInPair; + $characters->seek($lastKeyInPair); + $count += $skippedCharacters; + } else { + $characters->seek($startQuotedPair); + } + } + + if (!$this->isNotWhiteSpaceOrControl($character)) { + break; + } + + // Even through this function should include specials + // we need to detect them to help the higher level + // parsers. + if ($this->isSpecial($character)) { + break; + } + + ++$count; + $characters->next(); + } + + $endPosition = $characters->key() - 1; // TODO: work out if this is where the header error is coming from + $characters->seek($endPosition); + + return $this->tokenFrom($characters, $startPosition, $count, TokenType::notWhiteSpaceOrControl()); + } + + + /** + * This is just to keep the line length of the parse method to less than 120 characters + * @param StringIterator $characters + * @param int $offset + * @param int $count + * @param TokenType $tokenType + * @return Token + */ + public function tokenFrom(StringIterator $characters, $offset, $count, TokenType $tokenType) + { + return new Token(StringIterator::withStringIterator($characters, $offset, $count), $tokenType); + } +} \ No newline at end of file diff --git a/src/Iteration/StringIterator.php b/src/Iteration/StringIterator.php new file mode 100755 index 0000000..2aa3a09 --- /dev/null +++ b/src/Iteration/StringIterator.php @@ -0,0 +1,212 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Iteration; + +/** + * Class StringIterator + * @package SalesAgility\Iteration + */ +class StringIterator implements StringIteratorInterface +{ + /** @var int $first */ + private $first; + + /** @var int $current */ + private $current; + + /** @var int $last */ + private $last; + + /** @var int $length */ + private $length; + + /** @var &string $string */ + private $string; + + /** + * StringIterator constructor. + * @param string $string + * @param int $offset + * @param int $count + * @throws \InvalidArgumentException + */ + public function __construct(&$string, $offset = 0, $count = -1) + { + if (!is_string($string)) { + throw new \InvalidArgumentException('$string must be a string'); + } + + if (!is_integer($offset)) { + throw new \InvalidArgumentException('$offset must be a integer'); + } + + if (!is_integer($count)) { + throw new \InvalidArgumentException('$count must be a integer'); + } + + // calculate starting position + $this->string = &$string; + $this->first = $offset; + $this->current = $this->first; + + // calculate ending position + if ($count === -1) { + $this->length = strlen($string); + $this->last = ($this->length - 1); + } elseif ($count === 0) { + $this->length = 0; + $this->last = 0; + } else { + $this->length = $count; + $this->last = $this->first + ($this->length - 1); + } + } + + /** + * @param string $string + * @param int $offset + * @param int $count + * @return StringIterator + */ + public static function withLiteral($string, $offset = 0, $count = -1) + { + return new self($string, $offset, $count); + } + + /** + * @param StringIterator $iterator + * @param int $offset + * @param int $count + * @return StringIterator + */ + public static function withStringIterator(StringIterator $iterator, $offset = 0, $count = -1) + { + return new StringIterator($iterator->string, $offset, $count); + } + + /** + * Return the current element + * @return string + */ + public function current() + { + return $this->string[$this->current]; + } + + /** + * Move forward to next element + */ + public function next() + { + $this->current += 1; + } + + /** + * Return the key of the current element + * @return int + */ + public function key() + { + return $this->current; + } + + /** + * Checks if current position is valid + * @return bool + */ + public function valid() + { + return $this->length > 0 + && $this->current <= $this->last + && ($this->current - $this->first) < $this->length; + } + + /** + * Rewind the Iterator to the first element + */ + public function rewind() + { + $this->current = $this->first; + } + + /** + * Fast forward the iterator to the last element + */ + public function fastForward() + { + $this->current = $this->last; + } + + /** + * @param int $pos + * @return bool|int + */ + public function seek($pos) + { + $cpos = $this->current; + $this->current = $pos; + if (!$this->valid()) { + $this->current = $cpos; + return false; + } + + return $pos; + } + + /** + * @return string innerString + */ + public function getInnerString() + { + return $this->string; + } + + /** + * @return int + */ + public function count() + { + return $this->length; + } + + /** + * @return int first character position + */ + public function first() + { + return $this->first; + } + + /** + * @return int last character position + */ + public function last() + { + return $this->last; + } + + /** + * @return bool|string + */ + public function toString() + { + return substr($this->string, $this->first, $this->last - $this->first - 1); + } +} \ No newline at end of file diff --git a/src/Iteration/StringIteratorInterface.php b/src/Iteration/StringIteratorInterface.php new file mode 100755 index 0000000..d7139d8 --- /dev/null +++ b/src/Iteration/StringIteratorInterface.php @@ -0,0 +1,86 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Iteration; + +/** + * Interface StringIteratorInterface + * @package SalesAgility\Iteration + */ +interface StringIteratorInterface extends \Iterator +{ + /** + * Return the current element + * @return string + */ + public function current(); + + /** + * Move forward to next element + */ + public function next(); + + /** + * Return the key of the current element + * @return int + */ + public function key(); + + /** + * Checks if current position is valid + * @return bool + */ + public function valid(); + + /** + * Rewind the Iterator to the first element + */ + public function rewind(); + + /** + * Fast forward the iterator to the last element + */ + public function fastForward(); + + /** + * @param int $position + */ + public function seek($position); + + /** + * @return string innerString + */ + public function getInnerString(); + + /**] + * @return int + */ + public function count(); + + /** + * @return int first character position + */ + public function first(); + + /** + * @return int last character position + */ + public function last(); +} \ No newline at end of file diff --git a/src/Pattern/ContainerAwareInterface.php b/src/Pattern/ContainerAwareInterface.php new file mode 100755 index 0000000..fb0f97c --- /dev/null +++ b/src/Pattern/ContainerAwareInterface.php @@ -0,0 +1,37 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Pattern; + + +use Psr\Container\ContainerInterface; + +/** + * Interface ContainerAwareInterface + * @package SalesAgility\Pattern + */ +interface ContainerAwareInterface +{ + /** + * ContainerAwareInterface constructor. + * @param ContainerInterface $container + */ + public function __construct(ContainerInterface $container); +} \ No newline at end of file diff --git a/src/Pattern/Singleton.php b/src/Pattern/Singleton.php new file mode 100755 index 0000000..3e1f4b1 --- /dev/null +++ b/src/Pattern/Singleton.php @@ -0,0 +1,31 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Pattern; + + +/** + * Interface Singleton + * @package SalesAgility\Pattern + */ +interface Singleton +{ + public static function instance(); +} \ No newline at end of file diff --git a/src/Stream/Connection.php b/src/Stream/Connection.php new file mode 100755 index 0000000..2807453 --- /dev/null +++ b/src/Stream/Connection.php @@ -0,0 +1,162 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Stream; + +use Psr\Container\ContainerInterface; +use SalesAgility\Pattern\ContainerAwareInterface; +use SalesAgility\Utility\Assert; + +/** + * Class Connection + * @package SalesAgility\Stream + */ +class Connection implements StreamConnectionInterface, ContainerAwareInterface +{ + /** @var ContainerInterface */ + private $container; + /** + * Constants for setting encryption + * @see Connection::enableEncryption(); + */ + const TLS = STREAM_CRYPTO_METHOD_TLS_CLIENT; + + /** @var null|Resource $resource */ + private $resource = null; + + /** @var int $connectionTimeout */ + private $connectionTimeout = 30; + + /** @var int $connectionTTL */ + private $connectionTTL = 1800; + + /** @var string $connectionString */ + private $connectionString; + + /** @var int $security */ + private $security; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * @param string $string eg tcp://localhost:143 + * @throws \Exception + */ + public function setConnectionString($string) + { + Assert::is(gettype($string) === 'string', 'connection string must be a string'); + Assert::is(!empty($string), 'connection string must not be empty'); + $this->connectionString = $string; + } + + /** + * @param $security + */ + public function enableEncryption($security = STREAM_CRYPTO_METHOD_TLS_CLIENT) + { + $this->security = $security; + } + + /** + * + */ + public function disableEncryption() + { + $this->security = false; + } + + /** + * @throws ConnectionException + */ + public function connect() + { + $errorNumber = null; + $errorString = null; + set_time_limit($this->connectionTTL); + ignore_user_abort(true); + + if (empty($this->connectionString)) { + throw ConnectionException::connectionFailure('Connection String is empty. expected tcp://address:port'); + } + + $this->resource = stream_socket_client($this->connectionString, $errorNumber, $errorString, $this->connectionTimeout); + + + if (empty($this->resource)) { + throw ConnectionException::connectionFailure('Unable to connect to ' . $this->connectionString); + } + + if (!empty($this->security)) { + try { + stream_socket_enable_crypto($this->resource, true, $this->security); + } catch (\Exception $e) { + $this->container->get('Logger')->error($e->getMessage()); + throw ConnectionException::connectionFailure('Failed to enable security'); + } + } + } + + /** + * + */ + public function disconnect() + { + fclose($this->resource); + $this->resource = null; + } + + /** + * @return bool + */ + public function isConnected() + { + return $this->resource !== null; + } + + /** + * @param $message + * @throws \Exception + */ + public function transmitMessage($message) + { + Assert::is(gettype($message) === 'string', 'message must be a string'); + Assert::is(!empty($message), 'message must not be empty'); + fwrite($this->resource, $message); + } + + /** + * @return bool|string + */ + public function readMessage() + { + return fgets($this->resource, 1000); + } + + /** + * @param string $string + * @return bool + */ + public function isEndOfFile($string = "") + { + return feof($this->resource); + } +} \ No newline at end of file diff --git a/src/Stream/ConnectionException.php b/src/Stream/ConnectionException.php new file mode 100755 index 0000000..b25a113 --- /dev/null +++ b/src/Stream/ConnectionException.php @@ -0,0 +1,49 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Stream; + +/** + * Class ConnectionException + * @package SalesAgility\Stream + */ +class ConnectionException extends \Exception +{ + const CODE_TIMEOUT = 408; + const CODE_CONNECTION_FAILURE = 503; + + /** + * @param $message + * @return ConnectionException + */ + public static function connectionFailure($message) + { + return new self('Failed to connected: ' . $message, self::CODE_CONNECTION_FAILURE); + } + + /** + * @param $message + * @return ConnectionException + */ + public static function timeout($message) + { + return new self('Timed Out: ' . $message, self::CODE_TIMEOUT); + } +} \ No newline at end of file diff --git a/src/Stream/MessageTransporter.php b/src/Stream/MessageTransporter.php new file mode 100755 index 0000000..616b06d --- /dev/null +++ b/src/Stream/MessageTransporter.php @@ -0,0 +1,108 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Stream; + +use SalesAgility\Utility\Assert; + +/** + * Class MessageTransporter + * @package SalesAgility\Stream + * Maintains a protocol link between the consumer and the connection + * Relays Messages + * Determines the end of file flag as not all protocols use eof to signify the end of a transmission + */ +class MessageTransporter implements MessageTransporterInterface +{ + /** @var string $command */ + private $command; + + /** @var StreamConnectionInterface $connection */ + private $connection; + + /** @var int $waitFor */ + private $TTL = 160; + + /** + * @param StreamConnectionInterface $connection + */ + public function setConnection(StreamConnectionInterface $connection) + { + $this->connection = $connection; + } + + /** + * @return StreamConnectionInterface + */ + public function connection() + { + return $this->connection; + } + + /** + * @param string $string + * @throws \Exception + */ + public function transmit($string) + { + $this->isConfigured(); + $this->command = null; + $this->connection->transmitMessage($string); + } + + /** + * @return string + * @throws \Exception + */ + public function receive() + { + $this->isConfigured(); + $response = ''; + $timeout = time() + $this->TTL; + while (time() < $timeout) { + $message = $this->connection->readMessage(); + $timeout = time() + $this->TTL; + $response .= $message; + + if ($this->isEndOfFile($message)) { + break; + } + } + return $response; + } + + /** + * @param string $string + * @return bool + */ + public function isEndOfFile($string) + { + return $this->connection->isEndOfFile($string); + } + + /** + * @throws \Exception + */ + private function isConfigured() + { + Assert::is($this->connection !== null, 'Connection must be set'); + return true; + } +} \ No newline at end of file diff --git a/src/Stream/MessageTransporterInterface.php b/src/Stream/MessageTransporterInterface.php new file mode 100755 index 0000000..452f60c --- /dev/null +++ b/src/Stream/MessageTransporterInterface.php @@ -0,0 +1,54 @@ +. + *********************************************************************************/ + + +namespace SalesAgility\Stream; + +/** + * Interface MessageTransporterInterface + * @package SalesAgility\Stream + */ +interface MessageTransporterInterface +{ + /** + * @param StreamConnectionInterface $connection + */ + public function setConnection(StreamConnectionInterface $connection); + + /** + * @return StreamConnectionInterface + */ + public function connection(); + + /** + * @param string $string + */ + public function transmit($string); + + /** + * @return string + */ + public function receive(); + + /** + * @param string $string + * @return bool + */ + public function isEndOfFile($string); +} \ No newline at end of file diff --git a/src/Stream/MockConnection.php b/src/Stream/MockConnection.php new file mode 100755 index 0000000..61768d9 --- /dev/null +++ b/src/Stream/MockConnection.php @@ -0,0 +1,155 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Stream; + +use Psr\Log\LoggerInterface; +use Psr\Log\LoggerAwareInterface; +use SalesAgility\Utility\Assert; + +/** + * Class MockConnection + * @package SalesAgility\Stream + */ +class MockConnection implements StreamConnectionInterface, LoggerAwareInterface +{ + /** + * Constants for setting encryption + * @see Connection::enableEncryption(); + */ + const TLS = STREAM_CRYPTO_METHOD_TLS_CLIENT; + + /** @var null|Resource $resource */ + private $resource = null; + + /** @var string $connectionString */ + private $connectionString; + + /** @var int $security */ + private $security; + + /** @var LoggerInterface */ + private $log; + + /** @var string */ + public $messageSent = ''; + + /** @var string[] $messageReceived */ + public $messageReceived = array( + '* OK mock server v1.0', + 'some header', + 'some body', + ); + + /** @var int $messagePosition */ + private $messagePosition = 0; + + /** @var int $messageLast */ + private $messageLast = 3; + + /** + * @param string $string eg tcp://localhost:143 + * @throws \Exception + */ + public function setConnectionString($string) + { + Assert::is(gettype($string) === 'string', 'connection string must be a string'); + Assert::is(!empty($string), 'connection string must not be empty'); + $this->connectionString = $string; + } + + /** + * @param $security + */ + public function enableEncryption($security) + { + $this->security = STREAM_CRYPTO_METHOD_TLS_CLIENT; + } + + /** + * + */ + public function disableEncryption() + { + $this->security = false; + } + + /** + */ + public function connect() + { + $this->resource = 1; + } + + /** + * + */ + public function disconnect() + { + $this->resource = null; + } + + /** + * @return bool + */ + public function isConnected() + { + return $this->resource !== null; + } + + /** + * @param $message + * @return void + */ + public function transmitMessage($message) + { + $this->messageSent .= $message; + } + + /** + * @return bool|string + */ + public function readMessage() + { + if ($this->messagePosition >= count($this->messageReceived)) { + return null; + } + + $msg = $this->messageReceived[$this->messagePosition]; + ++$this->messagePosition; + return $msg; + } + + /** + * @param string $string + * @return bool + */ + public function isEndOfFile($string = "") + { + return $this->messagePosition >= $this->messageLast; + } + + /** + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->log = $logger; + } +} \ No newline at end of file diff --git a/src/Stream/StreamConnectionInterface.php b/src/Stream/StreamConnectionInterface.php new file mode 100755 index 0000000..20e1533 --- /dev/null +++ b/src/Stream/StreamConnectionInterface.php @@ -0,0 +1,77 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Stream; + + +use Psr\Log\LoggerAwareInterface; + +/** + * Interface StreamConnectionInterface + * @package SalesAgility\Stream + */ +interface StreamConnectionInterface +{ + /** + * @param string $string eg tcp://localhost:143 + * @throws \Exception + */ + public function setConnectionString($string); + + /** + * @param $security + */ + public function enableEncryption($security); + + /** + * + */ + public function disableEncryption(); + + /** + * @throws \ErrorException + */ + public function connect(); + + /** + * + */ + public function disconnect(); + + /** + * @return bool + */ + public function isConnected(); + + /** + * @param $message + * @throws \Exception + */ + public function transmitMessage($message); + + /** + * @return bool|string + */ + public function readMessage(); + + /** + * @return bool + */ + public function isEndOfFile($string = ""); +} \ No newline at end of file diff --git a/src/Utility/Assert.php b/src/Utility/Assert.php new file mode 100755 index 0000000..19f2923 --- /dev/null +++ b/src/Utility/Assert.php @@ -0,0 +1,41 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Utility; + + +/** + * Class Assert + * @package SalesAgility\Utility + * Provides more deterministic behaviour across different versions of php + */ +class Assert +{ + /** + * @param bool $assertion + * @param string $exceptionMessage + * @throws \Exception + */ + public static function is($assertion, $exceptionMessage) + { + if ($assertion === false) { + throw new \Exception($exceptionMessage); + } + } +} \ No newline at end of file diff --git a/src/Utility/PimapLogger.php b/src/Utility/PimapLogger.php new file mode 100755 index 0000000..b2665bf --- /dev/null +++ b/src/Utility/PimapLogger.php @@ -0,0 +1,96 @@ + 'joe') + * @throws InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + $message = $this->interpolate($message, $context); + switch ($level) { + case LogLevel::EMERGENCY: + /** @noinspection PhpUndefinedMethodInspection */ + $this->php_error_log('[EMERGENCY] ' . $message); + break; + case LogLevel::ALERT: + /** @noinspection PhpUndefinedMethodInspection */ + $this->php_error_log('[ALERT] ' . $message); + break; + case LogLevel::CRITICAL: + /** @noinspection PhpUndefinedMethodInspection */ + $this->php_error_log('[CRITICAL] ' . $message); + break; + case LogLevel::ERROR: + /** @noinspection PhpUndefinedMethodInspection */ + $this->php_error_log('[ERROR] ' . $message); + break; + case LogLevel::WARNING: + /** @noinspection PhpUndefinedMethodInspection */ + $this->php_error_log('[WARNING] ' . $message); + break; + case LogLevel::NOTICE: + /** @noinspection PhpUndefinedMethodInspection */ + $this->php_error_log('[NOTICE] ' . $message); + break; + case LogLevel::INFO: + /** @noinspection PhpUndefinedMethodInspection */ + $this->php_error_log('[INFO] ' . $message); + break; + case LogLevel::DEBUG: + /** @noinspection PhpUndefinedMethodInspection */ + $this->php_error_log('[DEBUG] ' . $message); + break; + default: + throw new \InvalidArgumentException('Invalid Log Level'); + } + } + + /** + * build a replacement array with braces around the context keys + * @param $message + * @param array $context + * @return string + */ + private function interpolate($message, array $context = array()) + { + $replace = array(); + + if (empty($context)) { + return $message; + } + + foreach ($context as $key => $val) { + // check that the value can be casted to string + if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { + $replace['{' . $key . '}'] = $val; + } + } + + return strtr($message, $replace); + } + + /** + * @param string $message + */ + protected function php_error_log($message) + { + error_log($message); + } +} diff --git a/src/Utility/StringValue.php b/src/Utility/StringValue.php new file mode 100755 index 0000000..f226830 --- /dev/null +++ b/src/Utility/StringValue.php @@ -0,0 +1,57 @@ +. + *********************************************************************************/ + +namespace SalesAgility\Utility; + +/** + * Class StringValue + * @package SalesAgility\Utility + */ +class StringValue +{ + /** + * @param string $haystack + * @param string $needle + * @return bool true when $haystack starts with $needle + * @throws \Exception + */ + public static function startsWith($haystack, $needle) + { + Assert::is(is_string($haystack), 'StringValue::startsWith $haystack must be a string'); + Assert::is(is_string($needle), 'StringValue::startsWith $needle must be a string'); + $length = strlen($needle); + return (substr($haystack, 0, $length) === $needle); + } + + /** + * @param string $haystack + * @param string $needle + * @return bool true when $haystack ends with $needle + * @throws \Exception + */ + public static function endsWith($haystack, $needle) + { + Assert::is(is_string($haystack), 'StringValue::endsWith $haystack must be a string'); + Assert::is(is_string($needle), 'StringValue::endsWith $needle must be a string'); + + $length = strlen($needle); + + return $length === 0 || (substr($haystack, -$length) === $needle); + } +} \ No newline at end of file diff --git a/tests/_data/.gitkeep b/tests/_data/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/tests/_data/1534938081_php_imap_extension_command.serialise b/tests/_data/1534938081_php_imap_extension_command.serialise new file mode 100755 index 0000000000000000000000000000000000000000..82599e5b1aa88d703ac6d0a2d5ad3cb9bd8031ce GIT binary patch literal 854 zcmb`EO;3YB5Qh6x$e#3uRKW}<3fq>XrVR@wMm(&t;wF4FEY%vqe|L8AV&aXSHnZ$J z^YY9d+$9jkE1`l?R9N4TE-vzd9Q%Rl>ifsN)a6sV6f4 literal 0 HcmV?d00001 diff --git a/tests/_data/1534938082_php_imap_extension_imap_fetch_overview.serialise b/tests/_data/1534938082_php_imap_extension_imap_fetch_overview.serialise new file mode 100755 index 0000000..b0a8283 --- /dev/null +++ b/tests/_data/1534938082_php_imap_extension_imap_fetch_overview.serialise @@ -0,0 +1 @@ +a:1:{s:19:"imap_fetch_overview";a:1:{i:0;O:8:"stdClass":15:{s:7:"subject";s:16:"Plain text email";s:4:"from";s:35:"Daniel Samson ";s:2:"to";s:28:"qweqwe ";s:4:"date";s:31:"Wed, 20 Jun 2018 12:32:20 +0100";s:10:"message_id";s:68:"";s:4:"size";i:451;s:3:"uid";i:2684;s:5:"msgno";i:2677;s:6:"recent";i:0;s:7:"flagged";i:0;s:8:"answered";i:0;s:7:"deleted";i:0;s:4:"seen";i:1;s:5:"draft";i:0;s:5:"udate";i:1529494340;}}} \ No newline at end of file diff --git a/tests/_data/1534938083_php_imap_extension_imap_fetchstructure.serialise b/tests/_data/1534938083_php_imap_extension_imap_fetchstructure.serialise new file mode 100755 index 0000000..f457714 --- /dev/null +++ b/tests/_data/1534938083_php_imap_extension_imap_fetchstructure.serialise @@ -0,0 +1 @@ +a:1:{s:19:"imap_fetchstructure";O:8:"stdClass":12:{s:4:"type";i:0;s:8:"encoding";i:0;s:9:"ifsubtype";i:1;s:7:"subtype";s:5:"PLAIN";s:13:"ifdescription";i:0;s:4:"ifid";i:0;s:5:"lines";i:1;s:5:"bytes";i:38;s:13:"ifdisposition";i:0;s:13:"ifdparameters";i:0;s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:7:"CHARSET";s:5:"value";s:5:"UTF-8";}}}} \ No newline at end of file diff --git a/tests/_data/1534938084_php_imap_extension_message.serialise b/tests/_data/1534938084_php_imap_extension_message.serialise new file mode 100755 index 0000000000000000000000000000000000000000..9fe19c46f775c4dacd4565355f17a898dd324abf GIT binary patch literal 1916 zcmbVN+m6#P5Y4AVenq>=trsQ;u?P@-U>9vGo+=?Hou)>yQ({L;srv7XonBN4QHQDz zsXKFKeCC|V6QmiiIj>PaRH7DUx1347T|T4UHcI323u?_PWH5rghAaUV+>Q=5$XD31 z{0Xwi2mW&(4^vlU>{X=C30B;oEfi0HeW2%skT{dFGrXv&?bIOx>%%l81fE3k^e!6T zB~chBkS35s_mTUsTtl1!GX(OxQTW~LwuCMqraz{aa~nrMuc8>Dh~)=jhG_I)=Ge4p zSk57ay?}_I5`zBug7gjF=uQ|cTbaD8Bp3C4*~qtjg+a8e+eTj1NJo|*C9n?XTj$u61t7$Sr=A z=I1n0{GMn5wzX`ryl~nj-EWK}#`>QzvBr(zTUilpuE4O=Z5V&QRs``sLRwvHn4UK}pf3iPZmZ-u(l ztC{q6vrhFJOQ1Sg*nLhJv_8j^iG57{#20kJnZjjGGY8^ltzcdAiX=qFpi;Nx`pJdc zwMu>ZZA+JOs>6GQlL;S`e!&*0?_SmR-GVim%Zc5lEnm|(&Jh-?xQw%F@_6 zckb~9;~7FKDyiosRb+nhbuAA0vZ-sKip7ztLbmz#^Wet(WTev6sQ=f13_FeN24ON1 zHF+O`x2^o36C@f#1b49Ld~nCRM!4nK^4_zr&cGIJd90-}8WMq#_kr-p({z5vAt8)n z#%ykEbJzS~mAYqB>";s:2:"to";s:28:"qweqwe ";s:4:"date";s:31:"Wed, 20 Jun 2018 12:32:20 +0100";s:10:"message_id";s:68:"";s:4:"size";i:451;s:3:"uid";i:2684;s:5:"msgno";i:2677;s:6:"recent";i:0;s:7:"flagged";i:0;s:8:"answered";i:0;s:7:"deleted";i:0;s:4:"seen";i:1;s:5:"draft";i:0;s:5:"udate";i:1529494340;}}} \ No newline at end of file diff --git a/tests/_data/1534938087_php_imap_extension_imap_fetchstructure.serialise b/tests/_data/1534938087_php_imap_extension_imap_fetchstructure.serialise new file mode 100755 index 0000000..f457714 --- /dev/null +++ b/tests/_data/1534938087_php_imap_extension_imap_fetchstructure.serialise @@ -0,0 +1 @@ +a:1:{s:19:"imap_fetchstructure";O:8:"stdClass":12:{s:4:"type";i:0;s:8:"encoding";i:0;s:9:"ifsubtype";i:1;s:7:"subtype";s:5:"PLAIN";s:13:"ifdescription";i:0;s:4:"ifid";i:0;s:5:"lines";i:1;s:5:"bytes";i:38;s:13:"ifdisposition";i:0;s:13:"ifdparameters";i:0;s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:7:"CHARSET";s:5:"value";s:5:"UTF-8";}}}} \ No newline at end of file diff --git a/tests/_data/1534938088_php_imap_extension_imap_body.serialise b/tests/_data/1534938088_php_imap_extension_imap_body.serialise new file mode 100755 index 0000000..48e6487 --- /dev/null +++ b/tests/_data/1534938088_php_imap_extension_imap_body.serialise @@ -0,0 +1,2 @@ +a:1:{s:9:"imap_body";s:38:"Hi this should only be in plain text +";} \ No newline at end of file diff --git a/tests/_data/1534938089_php_imap_extension_message.serialise b/tests/_data/1534938089_php_imap_extension_message.serialise new file mode 100755 index 0000000000000000000000000000000000000000..9c238e4ee1d605272d3bba907ef519d8f457688b GIT binary patch literal 1955 zcmbVN+m6#P5Y00Z{}A~V?JBoka1deezoHKc(X-3JMHK6Zm-f**B&IH>mpFnSv)Uf;tTC*Am9nqbpSwcy0Jvvwe ztDq(MBh4Zo`1?MdPF<0)SCKv^sF(p;D4tOA37;2Sz)XnFu%f}XQ-?@NAEs%7;7Jru zZ=&%{5`}R>(}X6`ZR9>A*ECKkF$nUaB>XVj4We@z;~&%Wxs5}hH&INZh~#@@25IzQ z=FqfJBxf|HJ5D2nN)YU8u)EO zu&mqjQ9Ps<(fedp*;LegKQesqxK2>0YHKx|q7JUR>!XdPnG?3(rENx_P1}{GYn@v% zbcs@IL?KizZcA~IrA2V>vjwch> zte-}MjyvzT%xUI8{2Ump8djsy&_7t}x~jjpaJzl6Prs$=QjT?4Zv>pdoz&0J0`}dD q-@byN0dqdFOSfe!oZ2bEV%6seY~Tq>l=(3P377*of5rKJzyAYT`=Pb~ literal 0 HcmV?d00001 diff --git a/tests/_data/1534938090_php_imap_extension_command.serialise b/tests/_data/1534938090_php_imap_extension_command.serialise new file mode 100755 index 0000000000000000000000000000000000000000..76f63bf5c5ec148a5ae4cbd07474e0394d61e810 GIT binary patch literal 854 zcmb`EO;3YB5Qh6x$e#3uRG}G86t*o%O&b=P81b;miktA!uvBYE|GTq`7ZY#vw3%h+ znU`mF;Ld?CUI`VHvXcFG9@qSshizT+rdXb2RfsO%e;oBSJ@=wfvTgo;Cg8!Kz*_*B zO+?M!#=-keyvsB371%)Z;W^iXYu*Ep6-~p9Vc%bY&Aakci>6n^=inG0KprV$!I~0Z zg5$aioa^G;m~JDcpUq*AVE?As25Y>OaECSSB#R@{hLwX&%U$&JyrDrvlNU=DEKRa7 z%@TxPF6akaCd=oF^_^@=BM8M?MvugdCUZYWYTKDT*)V8#_^Xq_QYHLrjC%P2zo#cN literal 0 HcmV?d00001 diff --git a/tests/_data/1534938092_php_imap_extension_imap_fetch_overview.serialise b/tests/_data/1534938092_php_imap_extension_imap_fetch_overview.serialise new file mode 100755 index 0000000..50e9839 --- /dev/null +++ b/tests/_data/1534938092_php_imap_extension_imap_fetch_overview.serialise @@ -0,0 +1 @@ +a:1:{s:19:"imap_fetch_overview";a:1:{i:0;O:8:"stdClass":15:{s:7:"subject";s:24:"Test Email: Kiara Cremin";s:4:"from";s:28:"Mailer ";s:2:"to";s:28:"Daniel ";s:4:"date";s:30:"Thu, 7 Jun 2018 09:21:55 +0100";s:10:"message_id";s:47:"";s:4:"size";i:1665;s:3:"uid";i:2675;s:5:"msgno";i:2668;s:6:"recent";i:0;s:7:"flagged";i:0;s:8:"answered";i:0;s:7:"deleted";i:0;s:4:"seen";i:1;s:5:"draft";i:0;s:5:"udate";i:1528359716;}}} \ No newline at end of file diff --git a/tests/_data/1534938093_php_imap_extension_imap_fetchstructure.serialise b/tests/_data/1534938093_php_imap_extension_imap_fetchstructure.serialise new file mode 100755 index 0000000..033d00e --- /dev/null +++ b/tests/_data/1534938093_php_imap_extension_imap_fetchstructure.serialise @@ -0,0 +1 @@ +a:1:{s:19:"imap_fetchstructure";O:8:"stdClass":11:{s:4:"type";i:1;s:8:"encoding";i:0;s:9:"ifsubtype";i:1;s:7:"subtype";s:5:"MIXED";s:13:"ifdescription";i:0;s:4:"ifid";i:0;s:13:"ifdisposition";i:0;s:13:"ifdparameters";i:0;s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:8:"BOUNDARY";s:5:"value";s:43:"b1_EjuxnAKlUhzO4MXUA03f8lcG8xcCJLa273hl4yyo";}}s:5:"parts";a:2:{i:0;O:8:"stdClass":11:{s:4:"type";i:1;s:8:"encoding";i:0;s:9:"ifsubtype";i:1;s:7:"subtype";s:11:"ALTERNATIVE";s:13:"ifdescription";i:0;s:4:"ifid";i:0;s:13:"ifdisposition";i:0;s:13:"ifdparameters";i:0;s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:8:"BOUNDARY";s:5:"value";s:43:"b2_EjuxnAKlUhzO4MXUA03f8lcG8xcCJLa273hl4yyo";}}s:5:"parts";a:2:{i:0;O:8:"stdClass":12:{s:4:"type";i:0;s:8:"encoding";i:0;s:9:"ifsubtype";i:1;s:7:"subtype";s:5:"PLAIN";s:13:"ifdescription";i:0;s:4:"ifid";i:0;s:5:"lines";i:2;s:5:"bytes";i:58;s:13:"ifdisposition";i:0;s:13:"ifdparameters";i:0;s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:7:"CHARSET";s:5:"value";s:8:"us-ascii";}}}i:1;O:8:"stdClass":12:{s:4:"type";i:0;s:8:"encoding";i:0;s:9:"ifsubtype";i:1;s:7:"subtype";s:4:"HTML";s:13:"ifdescription";i:0;s:4:"ifid";i:0;s:5:"lines";i:1;s:5:"bytes";i:49;s:13:"ifdisposition";i:0;s:13:"ifdparameters";i:0;s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:7:"CHARSET";s:5:"value";s:8:"us-ascii";}}}}}i:1;O:8:"stdClass":15:{s:4:"type";i:0;s:8:"encoding";i:3;s:9:"ifsubtype";i:1;s:7:"subtype";s:5:"PLAIN";s:13:"ifdescription";i:0;s:4:"ifid";i:1;s:2:"id";s:11:"";s:5:"lines";i:1;s:5:"bytes";i:18;s:13:"ifdisposition";i:1;s:11:"disposition";s:10:"ATTACHMENT";s:13:"ifdparameters";i:1;s:11:"dparameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:8:"FILENAME";s:5:"value";s:9:"Plaintext";}}s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:4:"NAME";s:5:"value";s:9:"Plaintext";}}}}}} \ No newline at end of file diff --git a/tests/_data/1534938094_php_imap_extension_message.serialise b/tests/_data/1534938094_php_imap_extension_message.serialise new file mode 100755 index 0000000000000000000000000000000000000000..4b2294d45e9fcdb51b72d761a64b26a091d9893b GIT binary patch literal 1917 zcmbVNU5nc=5cE$8dM$U@PMqY{rS#fdX()FGm(Zs`k)4gBV%bKLY15GZ-j$pWj+RDl zo)md^c0D_@eum))*n*d+?+Z~1vs=w2Z&oj;o4V4tdPJ>xfeaGZYZ%4Ag6q-27Wo<# zOP^sB`oMqg;$iBFjJ=9<Il-D6w1uJxuo<0~LgHM?*6^&PwucT8SQn-tCh$0nrZ?gE zCY%hyJBTAllH1VzSgIi!0y6~itFG{y*)@bNAfgA;%e{>wpf_OzVaU=0F+((ZFmr6$ z)GXx?!CpW}P%%NzcqOnLyw$BRm@65-FC-V`ZC=ZFU4>q>Y*a1(sPRaEsQy*suFqR2 zY|7ozy-EoC-X!F?ZSa_x^eLRZO6}FNZnN*08#}<5Mz%z41`n2K7<>_2@xe!hQdIVN z67}gxc1D@6Z7PYrn;9KER1swLdS^vEBq3b4*9RL7Bd2V?m$w;#Hmc1{tDId%WEa0m zi*p)DeowRjYf5e@%A9zK#jTO$>2E}=abtMCl~@^lx}NxL4%_ItV3609A&{lwmENFw zsPcL(s)D6Y#PQgV+rJ|^TmLtTr1kI>l@>Lz@YKWpZVnw=N0E4M4wq{1r<1PJm$yP) z>Z_S_wzH1)8%t2NGP4VvGH8E}$5VS8`iU=Sg)@cAoFop!&sxEz<ONBHj6tQY%k`5B zw|kZP^jnuM<y41v3MUiZSNaDkq`o_m+m8#DXf8KogPGzR8pk=pVilJNYTyNP((fsR O2v{IG<3#D<aQF>RDwF#F literal 0 HcmV?d00001 diff --git a/tests/_data/1534938095_php_imap_extension_command.serialise b/tests/_data/1534938095_php_imap_extension_command.serialise new file mode 100755 index 0000000000000000000000000000000000000000..2ead189d3ede21f29b7eb2c6a032d52755e9556f GIT binary patch literal 782 zcmb`E%TB{E5Jmeb(X3#X21u&Ost*H|K&?oGKus5hm@rb}RJIF6QT`p<Ss>Qw%F@_6 zckb~9<12)W*Fw)Ls>%Em>xLhSW!p4dmWw0RrBKE8^WetpWQ5ez%Kz7Z33eKj8Ny^F zYVtk=Z<Y9<6QmkL1a~CS`QVOsgK*38<-KQLpMlL)b!>z*8d8pt_kr-pvTT0GASI0A zcxH2Bo4aNYtIR!{Vn-O->D0DdY*w$koW10GNc-M4wjVQEC)b%#Br8`lvNZ!=I&jMb y7k`PDUW46jbRg+xUQ@}9C?S3G<r3?9H1}(uw#xerv2%v3wb5YGPyBmWwf_Y>zZ3KT literal 0 HcmV?d00001 diff --git a/tests/_data/1534938096_php_imap_extension_imap_fetch_overview.serialise b/tests/_data/1534938096_php_imap_extension_imap_fetch_overview.serialise new file mode 100755 index 0000000..50e9839 --- /dev/null +++ b/tests/_data/1534938096_php_imap_extension_imap_fetch_overview.serialise @@ -0,0 +1 @@ +a:1:{s:19:"imap_fetch_overview";a:1:{i:0;O:8:"stdClass":15:{s:7:"subject";s:24:"Test Email: Kiara Cremin";s:4:"from";s:28:"Mailer <suitecrm2@gmail.com>";s:2:"to";s:28:"Daniel <suitecrm2@gmail.com>";s:4:"date";s:30:"Thu, 7 Jun 2018 09:21:55 +0100";s:10:"message_id";s:47:"<EjuxnAKlUhzO4MXUA03f8lcG8xcCJLa273hl4yyo@u255>";s:4:"size";i:1665;s:3:"uid";i:2675;s:5:"msgno";i:2668;s:6:"recent";i:0;s:7:"flagged";i:0;s:8:"answered";i:0;s:7:"deleted";i:0;s:4:"seen";i:1;s:5:"draft";i:0;s:5:"udate";i:1528359716;}}} \ No newline at end of file diff --git a/tests/_data/1534938097_php_imap_extension_imap_fetchstructure.serialise b/tests/_data/1534938097_php_imap_extension_imap_fetchstructure.serialise new file mode 100755 index 0000000..033d00e --- /dev/null +++ b/tests/_data/1534938097_php_imap_extension_imap_fetchstructure.serialise @@ -0,0 +1 @@ +a:1:{s:19:"imap_fetchstructure";O:8:"stdClass":11:{s:4:"type";i:1;s:8:"encoding";i:0;s:9:"ifsubtype";i:1;s:7:"subtype";s:5:"MIXED";s:13:"ifdescription";i:0;s:4:"ifid";i:0;s:13:"ifdisposition";i:0;s:13:"ifdparameters";i:0;s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:8:"BOUNDARY";s:5:"value";s:43:"b1_EjuxnAKlUhzO4MXUA03f8lcG8xcCJLa273hl4yyo";}}s:5:"parts";a:2:{i:0;O:8:"stdClass":11:{s:4:"type";i:1;s:8:"encoding";i:0;s:9:"ifsubtype";i:1;s:7:"subtype";s:11:"ALTERNATIVE";s:13:"ifdescription";i:0;s:4:"ifid";i:0;s:13:"ifdisposition";i:0;s:13:"ifdparameters";i:0;s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:8:"BOUNDARY";s:5:"value";s:43:"b2_EjuxnAKlUhzO4MXUA03f8lcG8xcCJLa273hl4yyo";}}s:5:"parts";a:2:{i:0;O:8:"stdClass":12:{s:4:"type";i:0;s:8:"encoding";i:0;s:9:"ifsubtype";i:1;s:7:"subtype";s:5:"PLAIN";s:13:"ifdescription";i:0;s:4:"ifid";i:0;s:5:"lines";i:2;s:5:"bytes";i:58;s:13:"ifdisposition";i:0;s:13:"ifdparameters";i:0;s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:7:"CHARSET";s:5:"value";s:8:"us-ascii";}}}i:1;O:8:"stdClass":12:{s:4:"type";i:0;s:8:"encoding";i:0;s:9:"ifsubtype";i:1;s:7:"subtype";s:4:"HTML";s:13:"ifdescription";i:0;s:4:"ifid";i:0;s:5:"lines";i:1;s:5:"bytes";i:49;s:13:"ifdisposition";i:0;s:13:"ifdparameters";i:0;s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:7:"CHARSET";s:5:"value";s:8:"us-ascii";}}}}}i:1;O:8:"stdClass":15:{s:4:"type";i:0;s:8:"encoding";i:3;s:9:"ifsubtype";i:1;s:7:"subtype";s:5:"PLAIN";s:13:"ifdescription";i:0;s:4:"ifid";i:1;s:2:"id";s:11:"<Plaintext>";s:5:"lines";i:1;s:5:"bytes";i:18;s:13:"ifdisposition";i:1;s:11:"disposition";s:10:"ATTACHMENT";s:13:"ifdparameters";i:1;s:11:"dparameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:8:"FILENAME";s:5:"value";s:9:"Plaintext";}}s:12:"ifparameters";i:1;s:10:"parameters";a:1:{i:0;O:8:"stdClass":2:{s:9:"attribute";s:4:"NAME";s:5:"value";s:9:"Plaintext";}}}}}} \ No newline at end of file diff --git a/tests/_data/1534938098_php_imap_extension_imap_fetchbody.serialise b/tests/_data/1534938098_php_imap_extension_imap_fetchbody.serialise new file mode 100755 index 0000000..bc46785 --- /dev/null +++ b/tests/_data/1534938098_php_imap_extension_imap_fetchbody.serialise @@ -0,0 +1,13 @@ +a:1:{s:14:"imap_fetchbody";s:345:"--b2_EjuxnAKlUhzO4MXUA03f8lcG8xcCJLa273hl4yyo +Content-Type: text/plain; charset=us-ascii + +This is the body in plain text for non-HTML mail clients + +--b2_EjuxnAKlUhzO4MXUA03f8lcG8xcCJLa273hl4yyo +Content-Type: text/html; charset=us-ascii + +This is the HTML message body <b>in bold!</b> + + +--b2_EjuxnAKlUhzO4MXUA03f8lcG8xcCJLa273hl4yyo-- +";} \ No newline at end of file diff --git a/tests/_data/1534938099_php_imap_extension_imap_fetchbody.serialise b/tests/_data/1534938099_php_imap_extension_imap_fetchbody.serialise new file mode 100755 index 0000000..050ed83 --- /dev/null +++ b/tests/_data/1534938099_php_imap_extension_imap_fetchbody.serialise @@ -0,0 +1,2 @@ +a:1:{s:14:"imap_fetchbody";s:58:"This is the body in plain text for non-HTML mail clients +";} \ No newline at end of file diff --git a/tests/_data/1534938100_php_imap_extension_imap_fetchbody.serialise b/tests/_data/1534938100_php_imap_extension_imap_fetchbody.serialise new file mode 100755 index 0000000..22a77a2 --- /dev/null +++ b/tests/_data/1534938100_php_imap_extension_imap_fetchbody.serialise @@ -0,0 +1,3 @@ +a:1:{s:14:"imap_fetchbody";s:49:"This is the HTML message body <b>in bold!</b> + +";} \ No newline at end of file diff --git a/tests/_data/1534938101_php_imap_extension_imap_fetchbody.serialise b/tests/_data/1534938101_php_imap_extension_imap_fetchbody.serialise new file mode 100755 index 0000000..aed25e4 --- /dev/null +++ b/tests/_data/1534938101_php_imap_extension_imap_fetchbody.serialise @@ -0,0 +1,2 @@ +a:1:{s:14:"imap_fetchbody";s:18:"bG9ydW0gaXBzdW0K +";} \ No newline at end of file diff --git a/tests/_data/1534938102_php_imap_extension_message.serialise b/tests/_data/1534938102_php_imap_extension_message.serialise new file mode 100755 index 0000000000000000000000000000000000000000..56d6966ab4e0fcdbbe4b3532b8cf549842569628 GIT binary patch literal 2769 zcmbtWU2obj6wPy`{s%9w6Kw(nsOhS8{ZP@?c52a{DoswffT!3&wxf+A{`;;SzN%Kk z=@1Vg_?~-n?z!jYp0?UFd}KN3^9j$nnLiB#n>{^%o)uEV(=BMtCJ@quE;Ma7X&CH3 z9ZZ3ZK!yE#+KznSZ>saO=aO{1l2kFlh#9bn;w}v@@p{Mw41_2ROLHu{<q&CD9n-Xl z;LRxRHKI-<>XPV$He;G3N0Ix4eNE#Q4Gn@k7ZQG&`3%uHjq#7(_P#xbKp&%+Mp4*b zA~Q&%2Q#apO%e7Pjp>5Z2%(w?eaR$;oSf>C8_1Msex3-%^P{W~XH|w;w2W0DUgfw& zKv4h6aZ=|g6wLDZ&^<~-7qv;qGF#v(GO3d|eGyu#W?iPgATzdsO>Ef^v>{iPXiC0v zrWm<UAh@*uCvlycL^UXbkqt%BS3ScE4^;$dF`8Quw@?WC$LotdP1{b{eh+UW0&P^8 znNqoNY2&!~WqRC%q2w1x3k_#EvlOLHyvX9zh<yDsM%I`yESn0DMz3#o{hY%Vx=9#h zg)|5<R7~nIs4JNjBQ7Ul{|-6s_;LF-q8saXqX@KzAD}cZkcGP%<L~-Vv4xg_y&F!s zCipc|AUDI?Z)7DSk|Gi0G(F=IFZ0od)8q8)@Zj*ks>{=BTrvZHoY=d_)^0L~kKnGA z#GWGKLJ?WWMpXxn1j%x4dw6(|7pfG5&vYqV1)ihfDtIA=jp^ZL@LD|Q8!4?;P~ztl z+qjpyPte`>>wX)(5#wr=Bz^<;20&2llFIEZ;m3~qKFOv`X)vEk-C#Q7+<DLY(?}+8 z!Zk&N?{-et3iyx8HGi(QV9#nj@mqCU3IeVNGUpO8D$7XxR@n~1l<AAg3@nD-#<r0j z*1ChSaudr<jBfNw)3t@Q0^GIyrQL#lRu{-pr1m8VBk-N1)9KmEmS5w8lDkFYBD;wL z@mp8HIA;^IWn4CxYX2x)yW{p<1?%+dn+sXzVb#{a#da?BZ%}}BS1PbCY><Q5-oXxB hDmF&L+MKXhlkEvC-~lo;rS%bOA^>g~#r@@S`4^Dl#4G>+ literal 0 HcmV?d00001 diff --git a/tests/_data/BoundaryContainsSeparators.txt b/tests/_data/BoundaryContainsSeparators.txt new file mode 100755 index 0000000..adcd767 --- /dev/null +++ b/tests/_data/BoundaryContainsSeparators.txt @@ -0,0 +1,891 @@ +* 2675 FETCH (BODY[TEXT] {37605} +------=_Part_-147098772_744397080.1529438164945 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Thank you to everyone who attended our first-ever Postman User Conference! = +If you missed out this year or just want to relive the fun: + + + +=09- You can see speaker and workshop slides=C2=A0here <http://go.getpostma= +n.com/HU0000a0GlYm7DF0D3M00ph>=C2=A0on our site. We=E2=80=99ll update the s= +ite with POST/CON videos shortly - stay tuned! +=09- We're hosting a series of webinars in the upcoming weeks to cover work= +shop material.=C2=A0Tune into our first webinar this=C2=A0Thursday, June 21= + at 10:30am PST and use=C2=A0this link <http://go.getpostman.com/XhD0lZM0G0= +00D7Fma003Uq0>=C2=A0to register. This week we'll cover workshop pre-reqs an= +d sending your first request. +The Postman API Network is the most authentic collection of APIs currently = +published - and we've made it=C2=A0easier than ever to get your API listed = +in our API Network, directly within the Postman app. +What is an API? How does Postman fit in? We get these questions all the tim= +e, so we created a 2 minute animation to answer them. Watch our Postmanaut = +help at every stage of the Lifecycle of an API. +Check out this=C2=A0Programming Throwdown Podcast <http://go.getpostman.com= +/o00tG0027003lU0FmDMhaD0>=C2=A0where our=C2=A0CEO and Co-Founder Abhinav As= +thana discusses building, scaling, and testing web services. +"We use Postman as a knowledge base for our APIs. We also use Postman for m= +onitoring and loading our backend. Lots of systems can do that, but when we= + need to customize that flow, Postman makes it easy." + +Read about how Postman helps The League=C2=A0sleep better at night <http://= +go.getpostman.com/hDl3uFM00U000ah7G000D3m>. +Find out=C2=A0how we=C2=A0used Postman scripts and the=C2=A0Airtable and Su= +rveyMonkey APIs to=C2=A0automatically upload <http://go.getpostman.com/v0Fm= +0G7v0hDDUl43M00a000>=C2=A0the Postman Certification Quiz=C2=A0for our POST/= +CON Workshop. +Read about=C2=A0how to=C2=A0write functional tests <http://go.getpostman.co= +m/F3mMD00w50U0l07a000DhGF>=C2=A0for your API using Postman, Newman, and Bla= +zeMeter in DZone. + +In Dzone, Dwight Gunning of Stream API=C2=A0shares=C2=A0how his team=C2=A0c= +reated a Postman Collection <http://go.getpostman.com/thDl0a07Um0D600GMF000= +3x>=C2=A0of API request examples that showcases all of their major features= +. + +Sanjay Patel walks through=C2=A0using environments, dynamic variables, and = +pre-request scripts with Postman and=C2=A0shares=C2=A0clever tips to use Po= +stman <http://go.getpostman.com/MG0D0a0mU7F3h007D0lM0y0>=C2=A0in=C2=A0DZone= +. + +------=_Part_-147098772_744397080.1529438164945 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<!doctype html> +<html> +<head>=20 +<title>New Newsletter Template</title>=20 +<!-- <link href=3D"https://fonts.googleapis.com/css?family=3DOpen+Sans:400,= +600,700" rel=3D"stylesheet"> -->=20 +<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=3D1"= +>=20 +<!-- Marketo Variables --> =20 +<style type=3D"text/css"> +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://f= +onts.gstatic.com/s/opensans/v15/u-WUoqrET9fUeobQW7jkRYX0hVgzZQUfRDuZrPvH3D8= +.woff2) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60= +-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://f= +onts.gstatic.com/s/opensans/v15/cJZKeOuBrn4kERxqtaUH3ZBw1xU1rKptJj_0jans920= +.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, = +U+2000-206F, U+2074, U+20AC, U+2212, U+2215; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https:/= +/fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNSojoYw3YTyktCCer_ilOl= +hE.woff2) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60= +-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), url(https:/= +/fonts.gstatic.com/s/opensans/v15/MTP_ySUJH_bn48VBG8sNShampu5_7CjHW5spxoeN3= +Vs.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, = +U+2000-206F, U+2074, U+20AC, U+2212, U+2215; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 700; + src: local('Open Sans Bold'), local('OpenSans-Bold'), url(https://fonts.g= +static.com/s/opensans/v15/k3k702ZOKiLJc3WVjuplzIjoYw3YTyktCCer_ilOlhE.woff2= +) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60= +-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 700; + src: local('Open Sans Bold'), local('OpenSans-Bold'), url(https://fonts.g= +static.com/s/opensans/v15/k3k702ZOKiLJc3WVjuplzBampu5_7CjHW5spxoeN3Vs.woff2= +) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, = +U+2000-206F, U+2074, U+20AC, U+2212, U+2215; +} +=09=09a { +=09=09=09color: #F26B3A !important; +=09=09} + a.button { + color: #FFFFFF !important; + } + #footerText { + color: #FFFFFF !important; + } + #footerText a:link { + color: #FFFFFF !important; =20 + } +=09</style>=20 +</head>=20 +<!-- [if !((gte mso 9)|(IE))]><!-->=20 +<body bgcolor=3D"#F26B3A" style=3D"font-family:'Open Sans', -apple-system, = +Helvetica, Arial, sans-serif !important; color: #282828;"><style type=3D"te= +xt/css">div#emailPreHeader{ display: none !important; }</style><div id=3D"e= +mailPreHeader" style=3D"mso-hide:all; visibility:hidden; opacity:0; color:t= +ransparent; mso-line-height-rule:exactly; line-height:0; font-size:0px; ove= +rflow:hidden; border-width:0; display:none !important;">POST/CON was a blas= +t - thank you to everyone who attended our first ever conference! If you mi= +ssed out this year, or want to relive the fun, POST/CON slide decks and vid= +eos are now available:</div>=20 +<!-- Pre-header - set this hidden -->=20 +<div style=3D"display: none !important; width:0; height:0; overflow:hidden;= + float:none; visibility:hidden; line-height:0; font-size:0; max-height:0; m= +so-hide: all">=20 +</div>=20 +<!-- <![endif] -->=20 +<!-- Email Content -->=20 +<table id=3D"body" class=3D"body" summary=3D"body" width=3D"100%" height=3D= +"100%" align=3D"center" border=3D"0" cellspacing=3D"0" cellpadding=3D"0" st= +yle=3D"background-color: #F26B3A;">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center" valign=3D"top">=20 +<!-- START LIQUID WRAPPER -->=20 +<!--[if mso]> + =09=09=09=09<table cellpadding=3D"0" cellspacing=3D"0" border=3D"0"= + style=3D"padding:0px;margin:0px;width:100%;"> + =09=09=09=09=09<tr> + =09=09=09=09=09<td style=3D"padding:0px;margin:0px;">&nbsp;<= +/td> + =09=09=09=09<td style=3D"padding:0px;margin:0px;" width=3D"= +640"> + =09=09=09<![endif]-->=20 +<table class=3D"outer-table mktoContainer" id=3D"template-wrapper" summary= +=3D"outer-table" align=3D"center" valign=3D"top" border=3D"0" cellpadding= +=3D"0" cellspacing=3D"0" style=3D"width: 100%; max-width: 550px; margin: 0 = +auto;" width=3D"100%"> +<tr class=3D"mktoModule" id=3D"mkto-header-module">=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +92.7%; max-width: 510px; margin: 0 auto;" width=3D"100%">=20 +<tbody>=20 +<tr height=3D"20">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +<tr height=3D"50">=20 +<td align=3D"left" valign=3D"middle"> <a href=3D +"http://go.getpostman.com/WU03XGMFD0o0Dhl00a0m700" target=3D"_blank" +><img src=3D"http://pages.getpostman.com/rs/067-UMD-991/images/pm-logo.png"= + width=3D"51" height=3D"50" style=3D"border:0; width:51px; height: 50px;"><= +/a> </td>=20 +<td align=3D"right" valign=3D"middle"> <span style=3D"text-align: right!imp= +ortant; font-size: 12px; color: #FFFFFF; font-weight: 600;">Postman Monthly= + Newsletter: June 2018</span> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr> +<tr class=3D"mktoModule" id=3D"mkto-article-image-module68e1075b-7882-4f7e-= +a69c-bae67a444e021046f722-b80c-4750-a15e-115b93d173da">=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +92.7%; max-width: 510px; margin: 0 auto;" width=3D"100%">=20 +<tbody>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +<tr>=20 +<td align=3D"center" valign=3D"top">=20 +<div class=3D"mktoImg" id=3D"mkto-article-image68e1075b-7882-4f7e-a69c-bae6= +7a444e021046f722-b80c-4750-a15e-115b93d173da" mktolockimgsize=3D"true">=20 +<a><img src=3D"https://pages.getpostman.com/rs/067-UMD-991/images/postcon-f= +inal.jpg" width=3D"510" style=3D"width:100%; max-width: 510px; display: blo= +ck; background-color: #FFFFFF; box-shadow: 0px 7px 15px rgba(0, 0, 0, .3)!i= +mportant;" alt=3D"postcon-final.jpg"></a>=20 +</div> </td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; background= +-image: url(http://pages.getpostman.com/rs/067-UMD-991/images/image-shadow.= +png); background-repeat: repeat-x; background-size: 2px 25px; background-po= +sition: 0px 0px;margin: 0 auto; text-align:center;" width=3D"100%" bgcolor= +=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td> <h1 style=3D"font-family:'Open Sans', -apple-system, Helvetica, Arial,= + sans-serif !important; font-weight: 600; margin: 25px 20px 25px 20px; padd= +ing: 0px;text-align: left; color: #282828; font-size: 20px;">POST/CON was a= + Blast!</h1>=20 +<div class=3D"mktoText" id=3D"articleContent168e1075b-7882-4f7e-a69c-bae67a= +444e021046f722-b80c-4750-a15e-115b93d173da" style=3D"font-size: 12px; line-= +height: 22px; font-weight: 400; color: #282828; margin: 0 20px 25px 20px; p= +adding: 0px; text-align: left;">=20 +<p>Thank you to everyone who attended our first-ever Postman User Conferenc= +e! If you missed out this year or just want to relive the fun:<br></p>=20 +<ul>=20 +<li>You can see speaker and workshop slides&nbsp;<a href=3D +"http://go.getpostman.com/HU0000a0GlYm7DF0D3M00ph" target=3D"_blank" id=3D"= +" +>here</a>&nbsp;on our site. We=E2=80=99ll update the site with POST/CON vid= +eos shortly - stay tuned!</li>=20 +<li>We're hosting a series of webinars in the upcoming weeks to cover works= +hop material.&nbsp;Tune into our first webinar this&nbsp;<strong>Thursday, = +June 21 at 10:30am PST</strong> and use&nbsp;<a href=3D +"http://go.getpostman.com/XhD0lZM0G000D7Fma003Uq0" target=3D"_blank" +>this link</a>&nbsp;to register. This week we'll cover workshop pre-reqs an= +d sending your first request.</li>=20 +</ul>=20 +</div> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<div style=3D"display: none !important;">=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; margin: 0 = +auto; text-align:center;" width=3D"100%" bgcolor=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td>=20 +<table width=3D"100%" border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center">=20 +<table border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center" style=3D"border-radius: 35px;" bgcolor=3D"#ff6c37"> <a= + href=3D"#" target=3D"_blank" class=3D"button" style=3D"font-size: 12px; li= +ne-height: 22px !important; color: #ffffff !important; text-decoration: non= +e; text-decoration: none;border-radius: 35px; padding: 3px 30px; border: 1p= +x solid #ff6c37; display: inline-block;font-family:'Open Sans', -apple-syst= +em, Helvetica, Arial, sans-serif !important;">Read More</a> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td> &nbsp; </td>=20 +</tr>=20 +</tbody>=20 +</table>=20 +</div> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr> +<tr class=3D"mktoModule" id=3D"mkto-article-textonly-modulee1fa4564-63cd-45= +55-ab67-561bbb3403f6">=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +92.7%; max-width: 510px; margin: 0 auto;" width=3D"100%">=20 +<tbody>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; margin: 0 = +auto; text-align:center;" width=3D"100%" bgcolor=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td> <h1 style=3D"font-family:'Open Sans', -apple-system, Helvetica, Arial,= + sans-serif !important; font-weight: 600; margin:25px 20px 8px 20px;padding= +:0px;text-align: left; color: #282828; font-size: 20px;">Join the Postman A= +PI Network</h1>=20 +<div class=3D"mktoText" id=3D"articleContent2e1fa4564-63cd-4555-ab67-561bbb= +3403f6" style=3D"font-size: 12px; line-height: 22px; font-weight: 400; colo= +r: #282828; margin: 0 20px 25px 20px; padding: 0px; text-align: left;">=20 +<p>The Postman API Network is the most authentic collection of APIs current= +ly published - and we've made it&nbsp;easier than ever to get your API list= +ed in our API Network, directly within the Postman app.<br></p>=20 +</div> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<div style=3D"display: block;">=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; margin: 0 = +auto; text-align:center;" width=3D"100%" bgcolor=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td>=20 +<table width=3D"100%" border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center">=20 +<table border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center" style=3D"border-radius: 35px;" bgcolor=3D"#ff6c37"> <a= + href=3D +"http://go.getpostman.com/gr0mFGD0a003MD0hU00007l" target=3D"_blank" class= +=3D"button" style=3D"font-size: 12px; line-height: 22px !important; color: = +#ffffff !important; text-decoration: none; text-decoration: none;border-rad= +ius: 35px; padding: 3px 30px; border: 1px solid #ff6c37; display: inline-bl= +ock;font-family:'Open Sans', -apple-system, Helvetica, Arial, sans-serif !i= +mportant;" +>Learn More</a> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td> &nbsp; </td>=20 +</tr>=20 +</tbody>=20 +</table>=20 +</div> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr> +<tr class=3D"mktoModule" id=3D"mkto-article-image-module0e1e9080-abd9-4e87-= +b33c-190c313010ef">=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +92.7%; max-width: 510px; margin: 0 auto;" width=3D"100%">=20 +<tbody>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +<tr>=20 +<td align=3D"center" valign=3D"top">=20 +<div class=3D"mktoImg" id=3D"mkto-article-image0e1e9080-abd9-4e87-b33c-190c= +313010ef" mktolockimgsize=3D"true">=20 +<a><img src=3D"https://pages.getpostman.com/rs/067-UMD-991/images/lifecycle= +-of-api-video.png" width=3D"510" style=3D"width:100%; max-width: 510px; dis= +play: block; background-color: #FFFFFF; box-shadow: 0px 7px 15px rgba(0, 0,= + 0, .3)!important;" alt=3D"lifecycle-of-api-video.png"></a>=20 +</div> </td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; background= +-image: url(http://pages.getpostman.com/rs/067-UMD-991/images/image-shadow.= +png); background-repeat: repeat-x; background-size: 2px 25px; background-po= +sition: 0px 0px;margin: 0 auto; text-align:center;" width=3D"100%" bgcolor= +=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td> <h1 style=3D"font-family:'Open Sans', -apple-system, Helvetica, Arial,= + sans-serif !important; font-weight: 600; margin: 25px 20px 25px 20px; padd= +ing: 0px;text-align: left; color: #282828; font-size: 20px;">APIs from Desi= +gn through Publication (Animated Short)</h1>=20 +<div class=3D"mktoText" id=3D"articleContent10e1e9080-abd9-4e87-b33c-190c31= +3010ef" style=3D"font-size: 12px; line-height: 22px; font-weight: 400; colo= +r: #282828; margin: 0 20px 25px 20px; padding: 0px; text-align: left;">=20 +<p>What is an API? How does Postman fit in? We get these questions all the = +time, so we created a 2 minute animation to answer them. Watch our Postmana= +ut help at every stage of the Lifecycle of an API.</p>=20 +</div> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<div style=3D"display: block !important;">=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; margin: 0 = +auto; text-align:center;" width=3D"100%" bgcolor=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td>=20 +<table width=3D"100%" border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center">=20 +<table border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center" style=3D"border-radius: 35px;" bgcolor=3D"#ff6c37"> <a= + href=3D +"http://go.getpostman.com/LaF0DM0030s70U1ml0D0h0G" target=3D"_blank" class= +=3D"button" style=3D"font-size: 12px; line-height: 22px !important; color: = +#ffffff !important; text-decoration: none; text-decoration: none;border-rad= +ius: 35px; padding: 3px 30px; border: 1px solid #ff6c37; display: inline-bl= +ock;font-family:'Open Sans', -apple-system, Helvetica, Arial, sans-serif !i= +mportant;" +>Watch the Video</a> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td> &nbsp; </td>=20 +</tr>=20 +</tbody>=20 +</table>=20 +</div> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr> +<tr class=3D"mktoModule" id=3D"mkto-article-textonly-module3b93dd58-d39a-4c= +d0-a352-e9166e94acd7">=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +92.7%; max-width: 510px; margin: 0 auto;" width=3D"100%">=20 +<tbody>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; margin: 0 = +auto; text-align:center;" width=3D"100%" bgcolor=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td> <h1 style=3D"font-family:'Open Sans', -apple-system, Helvetica, Arial,= + sans-serif !important; font-weight: 600; margin:25px 20px 8px 20px;padding= +:0px;text-align: left; color: #282828; font-size: 20px;">Podcast: Building = +Web Services With Postman</h1>=20 +<div class=3D"mktoText" id=3D"articleContent23b93dd58-d39a-4cd0-a352-e9166e= +94acd7" style=3D"font-size: 12px; line-height: 22px; font-weight: 400; colo= +r: #282828; margin: 0 20px 25px 20px; padding: 0px; text-align: left;">=20 +<p><strong></strong>Check out this&nbsp;<a href=3D +"http://go.getpostman.com/o00tG0027003lU0FmDMhaD0" target=3D"_blank" id=3D"= +" +>Programming Throwdown Podcast</a>&nbsp;where our&nbsp;CEO and Co-Founder A= +bhinav Asthana discusses building, scaling, and testing web services.</p>= +=20 +</div> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<div style=3D"display: none;">=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; margin: 0 = +auto; text-align:center;" width=3D"100%" bgcolor=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td>=20 +<table width=3D"100%" border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center">=20 +<table border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center" style=3D"border-radius: 35px;" bgcolor=3D"#ff6c37"> <a= + href=3D"#" target=3D"_blank" class=3D"button" style=3D"font-size: 12px; li= +ne-height: 22px !important; color: #ffffff !important; text-decoration: non= +e; text-decoration: none;border-radius: 35px; padding: 3px 30px; border: 1p= +x solid #ff6c37; display: inline-block;font-family:'Open Sans', -apple-syst= +em, Helvetica, Arial, sans-serif !important;">Listen</a> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td> &nbsp; </td>=20 +</tr>=20 +</tbody>=20 +</table>=20 +</div> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr> +<tr class=3D"mktoModule" id=3D"mkto-article-textonly-moduleb8ae3080-b0f2-40= +74-9350-787cecfa1f52">=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +92.7%; max-width: 510px; margin: 0 auto;" width=3D"100%">=20 +<tbody>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; margin: 0 = +auto; text-align:center;" width=3D"100%" bgcolor=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td> <h1 style=3D"font-family:'Open Sans', -apple-system, Helvetica, Arial,= + sans-serif !important; font-weight: 600; margin:25px 20px 8px 20px;padding= +:0px;text-align: left; color: #282828; font-size: 20px;">Case Study: The Le= +ague</h1>=20 +<div class=3D"mktoText" id=3D"articleContent2b8ae3080-b0f2-4074-9350-787cec= +fa1f52" style=3D"font-size: 12px; line-height: 22px; font-weight: 400; colo= +r: #282828; margin: 0 20px 25px 20px; padding: 0px; text-align: left;">=20 +<p><em>"We use Postman as a knowledge base for our APIs. We also use Postma= +n for monitoring and loading our backend. Lots of systems can do that, but = +when we need to customize that flow, Postman makes it easy."</em><br><br>Re= +ad about how <a href=3D +"http://go.getpostman.com/hDl3uFM00U000ah7G000D3m" target=3D"_blank" id=3D"= +" +>Postman helps The League&nbsp;sleep better at night</a>.</p>=20 +</div> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<div style=3D"display: none;">=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; margin: 0 = +auto; text-align:center;" width=3D"100%" bgcolor=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td>=20 +<table width=3D"100%" border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center">=20 +<table border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center" style=3D"border-radius: 35px;" bgcolor=3D"#ff6c37"> <a= + href=3D"#" target=3D"_blank" class=3D"button" style=3D"font-size: 12px; li= +ne-height: 22px !important; color: #ffffff !important; text-decoration: non= +e; text-decoration: none;border-radius: 35px; padding: 3px 30px; border: 1p= +x solid #ff6c37; display: inline-block;font-family:'Open Sans', -apple-syst= +em, Helvetica, Arial, sans-serif !important;">Read More</a> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td> &nbsp; </td>=20 +</tr>=20 +</tbody>=20 +</table>=20 +</div> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr> +<tr class=3D"mktoModule" id=3D"mkto-article-image-module60953f0c-d091-4f86-= +910b-783c94acc996">=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +92.7%; max-width: 510px; margin: 0 auto;" width=3D"100%">=20 +<tbody>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +<tr>=20 +<td align=3D"center" valign=3D"top">=20 +<div class=3D"mktoImg" id=3D"mkto-article-image60953f0c-d091-4f86-910b-783c= +94acc996" mktolockimgsize=3D"true">=20 +<a><img src=3D"https://pages.getpostman.com/rs/067-UMD-991/images/UserGroup= + copy%402x.png" width=3D"510" style=3D"width:100%; max-width: 510px; displa= +y: block; background-color: #FFFFFF; box-shadow: 0px 7px 15px rgba(0, 0, 0,= + .3)!important;" alt=3D"UserGroup copy@2x.png"></a>=20 +</div> </td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; background= +-image: url(http://pages.getpostman.com/rs/067-UMD-991/images/image-shadow.= +png); background-repeat: repeat-x; background-size: 2px 25px; background-po= +sition: 0px 0px;margin: 0 auto; text-align:center;" width=3D"100%" bgcolor= +=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td> <h1 style=3D"font-family:'Open Sans', -apple-system, Helvetica, Arial,= + sans-serif !important; font-weight: 600; margin: 25px 20px 25px 20px; padd= +ing: 0px;text-align: left; color: #282828; font-size: 20px;">Postman Tutori= +al: The Lazy Dev's Guide to API Automation</h1>=20 +<div class=3D"mktoText" id=3D"articleContent160953f0c-d091-4f86-910b-783c94= +acc996" style=3D"font-size: 12px; line-height: 22px; font-weight: 400; colo= +r: #282828; margin: 0 20px 25px 20px; padding: 0px; text-align: left;">=20 +<p><span style=3D"color: #282828; font-family: 'Open Sans', -apple-system, = +Helvetica, Arial, sans-serif; font-size: 12px; font-style: normal; font-var= +iant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter= +-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-tran= +sform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-tex= +t-stroke-width: 0px;=20 +background-color: #ffffff; text-decoration-style: initial; text-decoration-= +color: initial; display: inline !important; float: none;">Find out&nbsp;how= + we&nbsp;used Postman scripts and the&nbsp;Airtable and SurveyMonkey APIs t= +o<span>&nbsp;</span></span><a href=3D +"http://go.getpostman.com/v0Fm0G7v0hDDUl43M00a000" target=3D"_blank" style= +=3D"font-family: 'Open Sans', -apple-system,=20 +Helvetica, Arial, sans-serif; font-size: 12px; font-style: normal; font-var= +iant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter= +-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-tran= +sform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-tex= +t-stroke-width: 0px; background-color: #ffffff;" +>automatically upload</a><span style=3D"color:=20 +#282828; font-family: 'Open Sans', -apple-system, Helvetica, Arial, sans-se= +rif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; f= +ont-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans= +: 2; text-align: left; text-indent: 0px; text-transform: none; white-space:= + normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; back= +ground-color: #ffffff;=20 +text-decoration-style: initial; text-decoration-color: initial; display: in= +line !important; float: none;"><span>&nbsp;the Postman Certification Quiz&n= +bsp;</span>for our POST/CON Workshop.</span><br></p>=20 +</div> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<div style=3D"display: none !important;">=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; margin: 0 = +auto; text-align:center;" width=3D"100%" bgcolor=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td>=20 +<table width=3D"100%" border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center">=20 +<table border=3D"0" cellspacing=3D"0" cellpadding=3D"0">=20 +<tbody>=20 +<tr>=20 +<td align=3D"center" style=3D"border-radius: 35px;" bgcolor=3D"#ff6c37"> <a= + href=3D +"http://go.getpostman.com/v0Fm0G7v0hDDUl43M00a000" target=3D"_blank" class= +=3D"button" style=3D"font-size: 12px; line-height: 22px !important; color: = +#ffffff !important; text-decoration: none; text-decoration: none;border-rad= +ius: 35px; padding: 3px 30px; border: 1px solid #ff6c37; display: inline-bl= +ock;font-family:'Open Sans', -apple-system, Helvetica, Arial, sans-serif !i= +mportant;" +>Read More</a> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td> &nbsp; </td>=20 +</tr>=20 +</tbody>=20 +</table>=20 +</div> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr> +<tr class=3D"mktoModule" id=3D"mkto-article-textonly-module44e02a9e-5d15-46= +89-a18f-782f472e6be4">=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +92.7%; max-width: 510px; margin: 0 auto;" width=3D"100%">=20 +<tbody>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; margin: 0 = +auto; text-align:center;" width=3D"100%" bgcolor=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td> <h1 style=3D"font-family:'Open Sans', -apple-system, Helvetica, Arial,= + sans-serif !important; font-weight: 600; margin: 0 auto;padding:25px 20px = +8px 20px;text-align: left; color: #282828; font-size: 20px;">Press</h1>=20 +<div class=3D"mktoText" id=3D"articleContent244e02a9e-5d15-4689-a18f-782f47= +2e6be4" style=3D"font-size: 12px; line-height: 22px; font-weight: 400; colo= +r: #282828; margin: 0 auto; padding: 0px 20px 25px 20px; text-align: left;"= +>=20 +<p>Read about&nbsp;how to&nbsp;<a href=3D +"http://go.getpostman.com/F3mMD00w50U0l07a000DhGF" target=3D"_blank" id=3D"= +" +>write functional tests</a>&nbsp;for your API using Postman, Newman, and Bl= +azeMeter in <em>DZone</em>.</p>=20 +<p>In <em>Dzone</em>, Dwight Gunning of Stream API&nbsp;shares&nbsp;how his= + team&nbsp;<a href=3D +"http://go.getpostman.com/thDl0a07Um0D600GMF0003x" target=3D"_blank" +>created a Postman Collection</a>&nbsp;of API request examples that showcas= +es all of their major features.</p>=20 +<p>Sanjay Patel walks through&nbsp;using environments, dynamic variables, a= +nd pre-request scripts with Postman and&nbsp;<span style=3D"color: #282828;= + font-family: 'Open Sans', -apple-system, Helvetica, Arial, sans-serif; fon= +t-size: 12px; font-style: normal; font-variant-ligatures: normal; font-vari= +ant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; tex= +t-align: left;=20 +text-indent: 0px; text-transform: none; white-space: normal; widows: 2; wor= +d-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; = +text-decoration-style: initial; text-decoration-color: initial; display: in= +line !important; float: none;">shares&nbsp;</span><a href=3D +"http://go.getpostman.com/MG0D0a0mU7F3h007D0lM0y0" target=3D"_blank" id=3D"= +" style=3D"font-family: 'Open Sans', -apple-system, Helvetica, Arial, sans-= +serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal;= + font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orpha= +ns: 2; text-align: left; text-indent: 0px; text-transform: none; white-spac= +e: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; ba= +ckground-color:=20 +#ffffff;" +>clever tips to use Postman</a>&nbsp;in&nbsp;<em>DZone</em>.</p>=20 +</div> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<div style=3D"display: block;">=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +96%; max-width: 490px; margin: 0 auto;background-color: #FFFFFF; margin: 0 = +auto; text-align:center;" width=3D"100%" bgcolor=3D"#FFFFFF">=20 +<tbody>=20 +<tr>=20 +<td> <a class=3D"main-cta" summary=3D"main-cta" href=3D +"http://go.getpostman.com/R00DFm3Mah0000l0UzD78G0" style=3D"background-colo= +r:#EF5B25; margin: 0 auto;padding:3px 30px 3px 30px; border-radius:35px; co= +lor:#ffffff !important; display:inline-block; font-family:'Open Sans', -app= +le-system, Helvetica, Arial, sans-serif !important; font-weight:500; font-s= +ize:12px; color:#FFFFFF; line-height:22px !important; text-align:center; te= +xt-decoration:none; -webkit-text-size-adjust:none" bgcolor=3D"#EF5B25" alig= +n=3D"center" target=3D"_blank" +>Read the Latest Press</a> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td> &nbsp; </td>=20 +</tr>=20 +</tbody>=20 +</table>=20 +</div> </td>=20 +</tr>=20 +<tr height=3D"25">=20 +<td style=3D"font-size: 0px; line-height: 0;">&nbsp;</td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr> +</table>=20 +<!--[if mso]> + =09=09=09=09=09</td> + =09=09=09=09=09<td style=3D"padding:0px;margin:0px;">&nbsp;</td> + =09=09=09=09</tr> + =09=09=09</table> + =09=09=09<![endif]-->=20 +<!-- END LIQUID WRAPPER --> </td>=20 +</tr>=20 +<tr>=20 +<td>=20 +<table class=3D"inner-table" summary=3D"inner-table" align=3D"center" valig= +n=3D"top" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"width: = +100%;margin: 0 auto; background-color: #transparent; margin: 0 auto; text-a= +lign:center;" width=3D"100%" bgcolor=3D"#ff6c37">=20 +<tbody>=20 +<tr height=3D"136">=20 +<td>=20 +<div class=3D"mktoText" id=3D"footerText" style=3D"font-family: 'Open Sans'= +, -apple-system, Helvetica, Arial, sans-serif !important; font-size: 10px; = +line-height: 14px; color: #ffffff !important;"> +<p style=3D"font-family: 'Open Sans', -apple-system, Helvetica, Arial, sans= +-serif !important; font-size: 10px; line-height: 14px; color: #ffffff !impo= +rtant;">=C2=A9 2018 Postman Inc., All Rights Reserved.<br><a href=3D +"http://go.getpostman.com/NM00AUah0003l7D09GF0m0D" style=3D"text-decoration= +: none !important; color: #ffffff !important;" +>595 Market Street, Suite 1130&nbsp; San Francisco, CA 94105</a><br>Click t= +o <strong><a href=3D"https://pages.getpostman.com/UnsubscribePage.html?mkt_= +unsubscribe=3D1&mkt_tok=3DeyJpIjoiWlRJeU1tUTBOelU1TUdZMyIsInQiOiJLTGVPbDdSL= +3Y5ZG0vNHdsNkxOM09XcEFneXl1dDczRDVGcFgramk0K1AyUXdhWFVuTVQvaXNvdVdwUG9XNlFS= +azF2bWlqNC84YzU2cmdYOXcvcEpvcmdUYzFUMmRYRmorMVVvM24zV21JT09WVjA4WmgrS2VnTVZ= +hR2VwbUZDVCJ9" style=3D"color: #ffffff !important;">unsubscribe</a></strong= +> from these emails.</p> +</div> </td>=20 +</tr>=20 +</tbody>=20 +</table> </td>=20 +</tr>=20 +</tbody>=20 +</table> =20 + +<img src=3D"http://go.getpostman.com/trk?t=3D1&mid=3DMDY3LVVNRC05OTE6MTgwMz= +oyNzIxOjQ4NDM6MDoyODM3Ojk6MTIzNzQ6OTgxMTI3MzpzdWl0ZWNybTJAZ21haWwuY29t" wid= +th=3D"1" height=3D"1" style=3D"display:none !important;" alt=3D"" /> +</body> +</html> +------=_Part_-147098772_744397080.1529438164945-- +) diff --git a/tests/_data/CAPABILITY.txt b/tests/_data/CAPABILITY.txt new file mode 100755 index 0000000..9e57d98 --- /dev/null +++ b/tests/_data/CAPABILITY.txt @@ -0,0 +1 @@ +* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN AUTH=PLAIN-CLIENTTOKEN AUTH=OAUTHBEARER AUTH=XOAUTH diff --git a/tests/_data/FETCH_BODY.txt b/tests/_data/FETCH_BODY.txt new file mode 100755 index 0000000..24b623c --- /dev/null +++ b/tests/_data/FETCH_BODY.txt @@ -0,0 +1 @@ +* 1 FETCH (BODY ((("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 58 1)("text" "html" ("charset" "us-ascii") NIL NIL "7bit" 49 2) "alternative")("text" "plain" ("name" "Plaintext" "charset" "us-ascii") "<Plaintext>" NIL "base64" 18 1) "mixed")) diff --git a/tests/_data/FETCH_BODYSTRUCTURE.txt b/tests/_data/FETCH_BODYSTRUCTURE.txt new file mode 100755 index 0000000..26491c1 --- /dev/null +++ b/tests/_data/FETCH_BODYSTRUCTURE.txt @@ -0,0 +1 @@ +* 1 FETCH (BODYSTRUCTURE ((("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 58 1 NIL NIL NIL NIL)("text" "html" ("charset" "us-ascii") NIL NIL "7bit" 49 2 NIL NIL NIL NIL) "alternative" ("boundary" "b2_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s") NIL NIL NIL)("text" "plain" ("name" "Plaintext" "charset" "us-ascii") "<Plaintext>" NIL "base64" 18 1 NIL ("attachment" ("filename" "Plaintext")) NIL NIL) "mixed" ("boundary" "b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s") NIL NIL NIL)) diff --git a/tests/_data/FETCH_BODY_HEADER.txt b/tests/_data/FETCH_BODY_HEADER.txt new file mode 100755 index 0000000..56e8d5a --- /dev/null +++ b/tests/_data/FETCH_BODY_HEADER.txt @@ -0,0 +1,20 @@ +* 1 FETCH (BODY[HEADER] {782} +Return-Path: <root@localhost.localdomain> +X-Original-To: user@localhost.localdomain +Delivered-To: user@localhost.localdomain +Received: from u255 (localhost [127.0.0.1]) + by u255 (Postfix) with ESMTP id 9AFDAB813D1 + for <user@localhost.localdomain>; Tue, 5 Jun 2018 08:59:50 +0100 (BST) +Date: Tue, 5 Jun 2018 08:59:50 +0100 +To: Daniel <user@localhost.localdomain> +From: Mailer <root@localhost.localdomain> +Reply-To: Information <user@localhost.localdomain> +Subject: Test Email: Baron Hartmann +Message-ID: <DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s@u255> +X-Mailer: PHPMailer 6.0.5 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s" +Content-Transfer-Encoding: 8bit + +) diff --git a/tests/_data/FETCH_BODY_HEADER_WITH_RESPONSE.txt b/tests/_data/FETCH_BODY_HEADER_WITH_RESPONSE.txt new file mode 100755 index 0000000..1513fd5 --- /dev/null +++ b/tests/_data/FETCH_BODY_HEADER_WITH_RESPONSE.txt @@ -0,0 +1,21 @@ +* 1 FETCH (BODY[HEADER] {782} +Return-Path: <root@localhost.localdomain> +X-Original-To: user@localhost.localdomain +Delivered-To: user@localhost.localdomain +Received: from u255 (localhost [127.0.0.1]) + by u255 (Postfix) with ESMTP id 9AFDAB813D1 + for <user@localhost.localdomain>; Tue, 5 Jun 2018 08:59:50 +0100 (BST) +Date: Tue, 5 Jun 2018 08:59:50 +0100 +To: Daniel <user@localhost.localdomain> +From: Mailer <root@localhost.localdomain> +Reply-To: Information <user@localhost.localdomain> +Subject: Test Email: Baron Hartmann +Message-ID: <DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s@u255> +X-Mailer: PHPMailer 6.0.5 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s" +Content-Transfer-Encoding: 8bit + +) +A1 OK diff --git a/tests/_data/FETCH_EMAIL_EMBEDDED_IMAGE.txt b/tests/_data/FETCH_EMAIL_EMBEDDED_IMAGE.txt new file mode 100755 index 0000000..1d7d46c --- /dev/null +++ b/tests/_data/FETCH_EMAIL_EMBEDDED_IMAGE.txt @@ -0,0 +1,39 @@ +* 2681 FETCH (UID 2688 FLAGS (\Seen) BODY[TEXT] {1054} +--00000000000014634f0571f83c62 +Content-Type: multipart/alternative; boundary="0000000000001463430571f83c61" + +--0000000000001463430571f83c61 +Content-Type: text/plain; charset="UTF-8" + +This is an embedded image + +--0000000000001463430571f83c61 +Content-Type: text/html; charset="UTF-8" + +<div dir="ltr"><div>This is an embedded image</div><div><img src="cid:ii_jk3tkr6b1_164db31f1b15fb3b" width="4" height="4"><br></div><br></div> + +--0000000000001463430571f83c61-- +--00000000000014634f0571f83c62 +Content-Type: image/png; name="example.png" +Content-Disposition: inline; filename="example.png" +Content-Transfer-Encoding: base64 +Content-ID: <ii_jk3tkr6b1_164db31f1b15fb3b> +X-Attachment-Id: ii_jk3tkr6b1_164db31f1b15fb3b + +iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz +AAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABPSURB +VAiZAUQAu/8B/1VV/8ni4gA3L8kAyQs3AACsnZP//9Qq/9O8X/8A/8z/AuXt3ACrANYALe4pAADN +MwAAAMz//1UA1P//zAD/qtQA/7quI62D01ONAAAAAElFTkSuQmCC +--00000000000014634f0571f83c62-- BODY[HEADER] {443} +MIME-Version: 1.0 +Received: by 2002:a25:d905:0:0:0:0:0 with HTTP; Fri, 27 Jul 2018 03:04:17 + -0700 (PDT) +Date: Fri, 27 Jul 2018 11:04:17 +0100 +Delivered-To: suitecrm2@gmail.com +Message-ID: <CAMFzkGqe9cPZUGK0QkydNK-spZiY1k11POb49QiexKTPxgboYw@mail.gmail.com> +Subject: Embedded Images +From: Daniel Samson <suitecrm2@gmail.com> +To: qweqwe <suitecrm2@gmail.com> +Content-Type: multipart/related; boundary="00000000000014634f0571f83c62" + + BODYSTRUCTURE ((("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "7BIT" 27 1 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "7BIT" 144 3 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "0000000000001463430571f83c61") NIL NIL)("IMAGE" "PNG" ("NAME" "example.png") "<ii_jk3tkr6b1_164db31f1b15fb3b>" NIL "BASE64" 286 NIL ("INLINE" ("FILENAME" "example.png")) NIL) "RELATED" ("BOUNDARY" "00000000000014634f0571f83c62") NIL NIL)) diff --git a/tests/_data/FETCH_FLAGS.txt b/tests/_data/FETCH_FLAGS.txt new file mode 100755 index 0000000..56ff5dc --- /dev/null +++ b/tests/_data/FETCH_FLAGS.txt @@ -0,0 +1 @@ +* 1 FETCH (FLAGS (\Seen)) diff --git a/tests/_data/FETCH_FLAGS_BODY_HEADER_BODYSTRUCTURE_UTF8.txt b/tests/_data/FETCH_FLAGS_BODY_HEADER_BODYSTRUCTURE_UTF8.txt new file mode 100755 index 0000000..c97dd94 --- /dev/null +++ b/tests/_data/FETCH_FLAGS_BODY_HEADER_BODYSTRUCTURE_UTF8.txt @@ -0,0 +1,26 @@ +* 8790 FETCH (UID 1565488 BODY[HEADER] {1082} +Return-Path: <joe@example.com> +Received: from hoza15.fra2.bytemine.net ([::ffff:127.0.0.1]:40729) + by hoza15 (kopano-dagent) with LMTP; + Tue, 10 Jul 2018 17:07:05 +0200 (CEST) +Received: from localhost ([::1] helo=hoza15.fra2.bytemine.net) + by hoza15.fra2.bytemine.net with esmtp (Exim 4.86_2) + (envelope-from <joe@example.com>) + id 1fcuEL-0003AD-Jv; Tue, 10 Jul 2018 17:07:05 +0200 +Received: by hoza15 (kopano-spooler) with MAPI; Tue, 10 Jul 2018 17:07:05 + +0200 +Subject: RE: Test Email: Please Ignore +From: =?utf-8?Q?Joe?= <joe@example.com> +To: =?utf-8?Q?Daniel?= <daniel@example.com>, + =?utf-8?Q?Ashley?= <ashley@example.com> +Date: Tue, 10 Jul 2018 17:07:05 +0200 +Mime-Version: 1.0 +Content-Type: multipart/alternative; + boundary="=_pKchp9S4f4sDa+rX7rCOibp8+zxbpRMqfUke9HXcw8-axbG2" +In-Reply-To: <kcim.5b44c8c4.2d48.608c49985726624d@hoza15.fra2.bytemine.net> +References: <kcim.5b44c8c4.2d48.608c49985726624d@hoza15.fra2.bytemine.net> +X-Priority: 3 (Normal) +X-Mailer: Kopano 8.5.5 +Message-Id: <kcim.5b44cb99.2f81.6604c5126d8c32d7@hoza15.fra2.bytemine.net> + + BODYSTRUCTURE (("text" "plain" ("charset" "utf-8") NIL NIL "quoted-printable" 593 38 NIL NIL NIL NIL)("text" "html" ("charset" "utf-8") NIL NIL "quoted-printable" 3211 54 NIL NIL NIL NIL) "alternative" ("boundary" "=_pKchp9S4f4sDa+rX7rCOibp8+zxbpRMqfUke9HXcw8-axbG2") NIL NIL NIL) FLAGS (\Seen \Recent)) diff --git a/tests/_data/FETCH_RANGE.txt b/tests/_data/FETCH_RANGE.txt new file mode 100755 index 0000000..784913a --- /dev/null +++ b/tests/_data/FETCH_RANGE.txt @@ -0,0 +1,1265 @@ +* 1 FETCH (UID 1 FLAGS (\Seen) BODY[TEXT] {6479} +--001a113f90669765b2056255711f +Content-Type: text/plain; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + + Three tips to get the most out of Gmail +[image: Google] + +Hi UuUuUu + +Tips to get the most out of Gmail + +[image: Contacts] +Bring your contacts and mail into Gmail + +On your computer, you can copy your contacts and emails from your old email +account to make the transition to Gmail even better. Learn how +<https://support.google.com/mail/answer/164640?hl=3Den&ref_topic=3D1669014>= +. +[image: Search] +Find what you need fast + +With the power of Google Search right in your inbox, it's easy to sort your +email. Find what you're looking for with predictions based on email +content, past searches and contacts. +[image: Search] +Much more than email + +You can send text messages and make video calls with Hangouts +<https://www.google.com/intl/en/hangouts/> right from Gmail. To use this +feature on mobile, download the Hangouts app for Android +<https://play.google.com/store/apps/details?id=3Dcom.google.android.talk&hl= +=3Den> +and Apple <https://itunes.apple.com/en/app/hangouts/id643496868?mt=3D8> +devices. + + +[image: Gmail icon] Happy emailing, +The Gmail Team +=C2=A9 2018 Google Inc. 1600 Amphitheatre Parkway, Mountain View, CA 94043 + +--001a113f90669765b2056255711f +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +<!DOCTYPE html> +<html><head><meta http-equiv=3D"content-type" content=3D"text/html;charset= +=3DUTF-8"/><title>Three tips to get the most out of Gmail</title></head><bo= +dy style=3D"background-color:#e5e5e5; margin:20px 0;"><br/><div style=3D"ma= +rgin:2%;"><div style=3D"direction:ltr; text-align:left; font-family:'Open s= +ans','Arial',sans-serif; color:#444; background-color:white; padding:1.5em;= + border-radius:1em; box-shadow:1px -5px 8px 2px #bbb; max-width:580px; marg= +in:2% auto 0 auto;"><table style=3D"background:white;width:100%"><tr><td><d= +iv style=3D"width:90px; height:54px; margin:10px auto;"><img src=3D"https:/= +/services.google.com/fh/files/emails/google_logo_flat_90_color.png" alt=3D"= +Google" width=3D"90" height=3D"34"/></div><div style=3D"width:90%; padding-= +bottom:10px; padding-left:15px"><p><img alt=3D"" aria-hidden=3D"true" src= +=3D"https://ssl.gstatic.com/ui/v1/icons/mail/images/gmail_logo_large.png" s= +tyle=3D"display:inline-block; max-height:10px; margin-right:5px;"/><span st= +yle=3D"font-family:'Open sans','Arial',sans-serif; font-weight:bold; font-s= +ize:small; line-height:1.4em">Hi UuUuUu</span></p><p><span style=3D"font-fa= +mily:'Open sans','Arial',sans-serif; font-size:2.08em;">Tips to get the mos= +t out of Gmail</span><br/></p></div><p></p><div style=3D"float:left; clear:= +both; padding:0px 5px 10px 10px;"><img src=3D"https://services.google.com/f= +h/files/emails/importcontacts.png" alt=3D"Contacts" style=3D"display:block;= +" width=3D"129" height=3D"129"/></div><div style=3D"float:left; vertical-al= +ign:middle; padding:10px; max-width:398px; float:left;"><table style=3D"ver= +tical-align:middle;"><tr><td style=3D"font-family:'Open sans','Arial',sans-= +serif;"><span style=3D"font-size:20px;">Bring your contacts and mail into G= +mail</span><br/><br/><span style=3D"font-size:small; line-height:1.4em">On = +your computer, you can copy your contacts and emails from your old email ac= +count to make the transition to Gmail even better. <a href=3D"https://suppo= +rt.google.com/mail/answer/164640?hl=3Den&amp;ref_topic=3D1669014" style=3D"= +text-decoration:none; color:#15C">Learn how</a>.</span></td></tr></table></= +div><div style=3D"float:left; clear:both; padding:0px 5px 10px 10px;"><img = +src=3D"https://ssl.gstatic.com/mail/welcome/localized/en/welcome_search.png= +" alt=3D"Search" style=3D"display:block;" width=3D"129" height=3D"129"/></d= +iv><div style=3D"float:left; vertical-align:middle; padding:10px; max-width= +:398px; float:left;"><table style=3D"vertical-align:middle;"><tr><td style= +=3D"font-family:'Open sans','Arial',sans-serif;"><span style=3D"font-size:2= +0px;">Find what you need fast</span><br/><br/><span style=3D"font-size:smal= +l; line-height:1.4em">With the power of Google Search right in your inbox, = +it's easy to sort your email. Find what you're looking for with predictions= + based on email content, past searches and contacts.</span></td></tr></tabl= +e></div><div style=3D"float:left; clear:both; padding:0px 5px 10px 10px;"><= +img src=3D"https://ssl.gstatic.com/accounts/services/mail/msa/welcome_hango= +uts.png" alt=3D"Search" style=3D"display:block;" width=3D"129" height=3D"12= +9"/></div><div style=3D"float:left; vertical-align:middle; padding:10px; ma= +x-width:398px; float:left;"><table style=3D"vertical-align:middle;"><tr><td= + style=3D"font-family:'Open sans','Arial',sans-serif;"><span style=3D"font-= +size:20px;">Much more than email</span><br/><br/><span style=3D"font-size:s= +mall; line-height:1.4em">You can send text messages and make video calls wi= +th <a href=3D"https://www.google.com/intl/en/hangouts/" style=3D"text-decor= +ation:none; color:#15C">Hangouts</a> right from Gmail. To use this feature = +on mobile, download the Hangouts app for <a href=3D"https://play.google.com= +/store/apps/details?id=3Dcom.google.android.talk&amp;hl=3Den" style=3D"text= +-decoration:none; color:#15C">Android</a> and <a href=3D"https://itunes.app= +le.com/en/app/hangouts/id643496868?mt=3D8" style=3D"text-decoration:none; c= +olor:#15C">Apple</a> devices.</span></td></tr></table></div><br/><br/> +<div style=3D"clear:both; padding-left:13px; height:6.8em;"><table style=3D= +"width:100%; border-collapse:collapse; border:0"><tr><td style=3D"width:68p= +x"><img alt=3D'Gmail icon' width=3D"49" height=3D"37" src=3D"https://ssl.gs= +tatic.com/ui/v1/icons/mail/images/gmail_logo_large.png" style=3D"display:bl= +ock;"/></td><td style=3D"align:left; font-family:'Open sans','Arial',sans-s= +erif; vertical-align:bottom"><span style=3D"font-size:small">Happy emailing= +,<br/></span><span style=3D"font-size:x-large; line-height:1">The Gmail Tea= +m</span></td></tr></table></div> +</td></tr></table></div> +<div style=3D"direction:ltr;color:#777; font-size:0.8em; border-radius:1em;= + padding:1em; margin:0 auto 4% auto; font-family:'Arial','Helvetica',sans-s= +erif; text-align:center;">=C2=A9 2018 Google Inc. 1600 Amphitheatre Parkway= +, Mountain View, CA 94043<br/></div></div></body></html> + +--001a113f90669765b2056255711f-- BODY[HEADER] {444} +MIME-Version: 1.0 +x-no-auto-attachment: 1 +Received: by 10.107.68.11; Tue, 9 Jan 2018 02:33:21 -0800 (PST) +Date: Tue, 9 Jan 2018 02:33:21 -0800 +Message-ID: <CAMFzkGrvzYE3x89A2JqR=Tf9FdvWsZr8wnVm-y85q=i=kLhsZQ@mail.gmail.com> +Subject: Three tips to get the most out of Gmail +From: Gmail Team <mail-noreply@google.com> +To: UuUuUu UuUuUu <UuUuUuUuU@gmail.com> +Content-Type: multipart/alternative; boundary="001a113f90669765b2056255711f" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1161 35 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 5039 67 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "001a113f90669765b2056255711f") NIL NIL)) +* 2 FETCH (UID 2 FLAGS (\Seen) BODY[TEXT] {4391} +--94eb2c06de7a9bf6ba05625571db +Content-Type: text/plain; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + + The best of Gmail, wherever you are +[image: Google] +[image: Nexus 4 with Gmail] + +Hi UuUuUu + + +Get the official Gmail app + +The best features of Gmail are only available on your phone and tablet with +the official Gmail app. Download the app or go to gmail.com +<https://www.gmail.com/> on your computer or mobile device to get started. + +[image: Google Play] +<https://play.google.com/store/apps/details?id=3Dcom.google.android.gm> [i= +mage: +App Store] <https://itunes.apple.com/en/app/gmail/id422689480?mt=3D8> + + +[image: Gmail icon] Happy emailing, +The Gmail Team +=C2=A9 2018 Google Inc. 1600 Amphitheatre Parkway, Mountain View, CA 94043 + +--94eb2c06de7a9bf6ba05625571db +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +<!DOCTYPE html> +<html><head><meta http-equiv=3D"content-type" content=3D"text/html;charset= +=3DUTF-8"/><title>The best of Gmail, wherever you are</title></head><body s= +tyle=3D"background-color:#e5e5e5; margin:20px 0;"><br/><div style=3D"margin= +:2%;"><div style=3D"direction:ltr; text-align:left; font-family:'Open sans'= +,'Arial',sans-serif; color:#444; background-color:white; padding:1.5em; bor= +der-radius:1em; box-shadow:1px -5px 8px 2px #bbb; max-width:580px; margin:2= +% auto 0 auto;"><table style=3D"background:white;width:100%"><tr><td><div s= +tyle=3D"width:90px; height:54px; margin:10px auto;"><img src=3D"https://ser= +vices.google.com/fh/files/emails/google_logo_flat_90_color.png" alt=3D"Goog= +le" width=3D"90" height=3D"34"/></div><div style=3D"float:right; padding-to= +p:2em;"><img src=3D"https://ssl.gstatic.com/accounts/services/mail/msa/welc= +ome_nexus.png" alt=3D"Nexus 4 with Gmail" style=3D"border:0; margin-right:1= +0px;" width=3D"155" height=3D"242"/></div><div style=3D"width:90%; padding-= +bottom:10px; padding-left:15px"><p><img alt=3D"" aria-hidden=3D"true" src= +=3D"https://ssl.gstatic.com/ui/v1/icons/mail/images/gmail_logo_large.png" s= +tyle=3D"display:inline-block; max-height:10px; margin-right:5px;"/><span st= +yle=3D"font-family:'Open sans','Arial',sans-serif; font-weight:bold; font-s= +ize:small; line-height:1.4em">Hi UuUuUu</span></p><p><span style=3D"font-fa= +mily:'Open sans','Arial',sans-serif; font-size:2.08em;"><br/>Get the offici= +al Gmail app</span><br/></p></div><p></p><div style=3D"padding-left:15px"><= +p style=3D"size:small; line-height:1.4em;">The best features of Gmail are o= +nly available on your phone and tablet with the official Gmail app. Downloa= +d the app or go to <a href=3D"https://www.gmail.com/" target=3D"_blank" sty= +le=3D"text-decoration:none; color:#15C">gmail.com</a> on your computer or m= +obile device to get started.</p><p style=3D"line-height:2em; margin-right:1= +70px;"><a href=3D"https://play.google.com/store/apps/details?id=3Dcom.googl= +e.android.gm" style=3D"text-decoration:none"><img alt=3D"Google Play" width= +=3D"127" height=3D"44" src=3D"https://ssl.gstatic.com/accounts/services/mai= +l/buttons/google_play_en.png" style=3D"border:0"/></a>&nbsp;&nbsp;<a href= +=3D"https://itunes.apple.com/en/app/gmail/id422689480?mt=3D8" style=3D"text= +-decoration:none;"><img alt=3D"App Store" width=3D"144" height=3D"43" src= +=3D"https://ssl.gstatic.com/accounts/services/mail/buttons/apple_store_en.p= +ng" style=3D"border:0"/></a></p></div><br/><br/> +<div style=3D"clear:both; padding-left:13px; height:6.8em;"><table style=3D= +"width:100%; border-collapse:collapse; border:0"><tr><td style=3D"width:68p= +x"><img alt=3D'Gmail icon' width=3D"49" height=3D"37" src=3D"https://ssl.gs= +tatic.com/ui/v1/icons/mail/images/gmail_logo_large.png" style=3D"display:bl= +ock;"/></td><td style=3D"align:left; font-family:'Open sans','Arial',sans-s= +erif; vertical-align:bottom"><span style=3D"font-size:small">Happy emailing= +,<br/></span><span style=3D"font-size:x-large; line-height:1">The Gmail Tea= +m</span></td></tr></table></div> +</td></tr></table></div> +<div style=3D"direction:ltr;color:#777; font-size:0.8em; border-radius:1em;= + padding:1em; margin:0 auto 4% auto; font-family:'Arial','Helvetica',sans-s= +erif; text-align:center;">=C2=A9 2018 Google Inc. 1600 Amphitheatre Parkway= +, Mountain View, CA 94043<br/></div></div></body></html> + +--94eb2c06de7a9bf6ba05625571db-- BODY[HEADER] {440} +MIME-Version: 1.0 +x-no-auto-attachment: 1 +Received: by 10.107.68.11; Tue, 9 Jan 2018 02:33:21 -0800 (PST) +Date: Tue, 9 Jan 2018 02:33:21 -0800 +Message-ID: <CAMFzkGo4E_q1KrkG1bdBFq-x4XtS97fGbGtX01ACcKEWP7+rOg@mail.gmail.com> +Subject: The best of Gmail, wherever you are +From: Gmail Team <mail-noreply@google.com> +To: UuUuUu UuUuUu <UuUuUuUuU@gmail.com> +Content-Type: multipart/alternative; boundary="94eb2c06de7a9bf6ba05625571db" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 658 22 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 3454 47 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "94eb2c06de7a9bf6ba05625571db") NIL NIL)) +* 3 FETCH (UID 3 FLAGS (\Seen) BODY[TEXT] {7308} +--001a113ec2a09de2430562557108 +Content-Type: text/plain; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + + Stay more organized with Gmail's inbox +[image: Google] + +Hi UuUuUu + +Gmail's inbox puts you in control + +[image: Inbox video] <https://www.youtube.com/watch?v=3DCFf7dlewJus> +Meet the inbox + +Gmail's inbox sorts your email into categories so you can see what's new at +a glance, decide which emails you want to read when and view similar types +of emails together. Watch the video +<https://www.youtube.com/watch?v=3DCFf7dlewJus> +[image: Social tab] +Choose your categories + +The Social and Promotions categories are on by default. Add categories like +Updates and Forums or remove categories to have those emails show up in +your Primary inbox. Learn how to choose categories +<https://support.google.com/mail/?hl=3Den&p=3Dinboxcategories_all> +[image: Customize] +Customize your inbox + +If you see a message you want in a different category, you can move it +there. On mobile devices, you can even choose which categories create a +notification. More customization tips +<https://support.google.com/mail/?hl=3Den&p=3Dinboxcategories_all> + + + +To learn more about Gmail's inbox, check out the help center +<https://support.google.com/mail/?hl=3Den&p=3Dinboxcategories_all> or watch= + the +video <https://www.youtube.com/watch?v=3DCFf7dlewJus&amp>. + + +[image: Gmail icon] Happy emailing, +The Gmail Team +=C2=A9 2018 Google Inc. 1600 Amphitheatre Parkway, Mountain View, CA 94043 + +--001a113ec2a09de2430562557108 +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +<!DOCTYPE html> +<html><head><meta http-equiv=3D"content-type" content=3D"text/html;charset= +=3DUTF-8"/><title>Stay more organized with Gmail&#39;s inbox</title></head>= +<body style=3D"background-color:#e5e5e5; margin:20px 0;"><br/><div style=3D= +"margin:2%;"><div style=3D"direction:ltr; text-align:left; font-family:'Ope= +n sans','Arial',sans-serif; color:#444; background-color:white; padding:1.5= +em; border-radius:1em; box-shadow:1px -5px 8px 2px #bbb; max-width:580px; m= +argin:2% auto 0 auto;"><table style=3D"background:white;width:100%"><tr><td= +><div style=3D"width:90px; height:54px; margin:10px auto;"><img src=3D"http= +s://services.google.com/fh/files/emails/google_logo_flat_90_color.png" alt= +=3D"Google" width=3D"90" height=3D"34"/></div><div style=3D"width:90%; padd= +ing-bottom:10px; padding-left:15px"><p><img alt=3D"" aria-hidden=3D"true" s= +rc=3D"https://ssl.gstatic.com/ui/v1/icons/mail/images/gmail_logo_large.png"= + style=3D"display:inline-block; max-height:10px; margin-right:5px;"/><span = +style=3D"font-family:'Open sans','Arial',sans-serif; font-weight:bold; font= +-size:small; line-height:1.4em">Hi UuUuUu</span></p><p><span style=3D"font-= +family:'Open sans','Arial',sans-serif; font-size:2.08em;">Gmail&#39;s inbox= + puts you in control</span><br/></p></div><p></p><div style=3D"clear:both; = +float:left; padding: 0px 5px 10px 10px; vertical-align:top;"><a href=3D"htt= +ps://www.youtube.com/watch?v=3DCFf7dlewJus" style=3D"text-decoration:none; = +border:0;"><img alt=3D"Inbox video" width=3D"129" height=3D"129" style=3D"b= +order:0" src=3D"https://ssl.gstatic.com/mail/welcome/localized/en/welcome_v= +ideo_2.png"/></a></div><div style=3D"float:left; vertical-align:middle; pad= +ding:10px; max-width:398px; float:left;"><table style=3D"vertical-align:mid= +dle;"><tr><td style=3D"font-family:'Open sans','Arial',sans-serif;"><span s= +tyle=3D"font-size:20px;">Meet the inbox</span><br/><br/><span style=3D"font= +-size:small; line-height:1.4em">Gmail's inbox sorts your email into categor= +ies so you can see what's new at a glance, decide which emails you want to = +read when and view similar types of emails together. <a href=3D"https://www= +.youtube.com/watch?v=3DCFf7dlewJus" style=3D"text-decoration:none; color:#1= +5C">Watch the video</a></span></td></tr></table></div><div style=3D"float:l= +eft; clear:both; padding:0px 5px 10px 10px;"><img src=3D"https://ssl.gstati= +c.com/mail/welcome/localized/en/welcome_inbox_tab_2.png" alt=3D"Social tab"= + style=3D"display:block;" width=3D"129" height=3D"129"/></div><div style=3D= +"float:left; vertical-align:middle; padding:10px; max-width:398px; float:le= +ft;"><table style=3D"vertical-align:middle;"><tr><td style=3D"font-family:'= +Open sans','Arial',sans-serif;"><span style=3D"font-size:20px;">Choose your= + categories</span><br/><br/><span style=3D"font-size:small; line-height:1.4= +em">The Social and Promotions categories are on by default. Add categories = +like Updates and Forums or remove categories to have those emails show up = +in your Primary inbox. <a href=3D"https://support.google.com/mail/?hl=3Den&= +amp;p=3Dinboxcategories_all" style=3D"text-decoration:none; color:#15C">Lea= +rn how to choose categories</a></span></td></tr></table></div><div style=3D= +"float:left; clear:both; padding:0px 5px 10px 10px;"><img src=3D"https://ss= +l.gstatic.com/mail/welcome/localized/en/welcome_customize_2.png" alt=3D"Cus= +tomize" style=3D"display:block;" width=3D"129" height=3D"129"/></div><div s= +tyle=3D"float:left; vertical-align:middle; padding:10px; max-width:398px; f= +loat:left;"><table style=3D"vertical-align:middle;"><tr><td style=3D"font-f= +amily:'Open sans','Arial',sans-serif;"><span style=3D"font-size:20px;">Cust= +omize your inbox</span><br/><br/><span style=3D"font-size:small; line-heigh= +t:1.4em">If you see a message you want in a different category, you can mov= +e it there. On mobile devices, you can even choose which categories create = +a notification. <a href=3D"https://support.google.com/mail/?hl=3Den&amp;p= +=3Dinboxcategories_all" style=3D"text-decoration:none; color:#15C">More cus= +tomization tips</a></span></td></tr></table></div><br/><br/><br/><div style= +=3D"clear: both; vertical-align:middle; padding-left:15px; line-height:1.4e= +m; font-family:'Open sans','Arial',sans-serif; font-size: small; text-align= +: left">To learn more about Gmail's inbox, check out the <a href=3D"https:/= +/support.google.com/mail/?hl=3Den&amp;p=3Dinboxcategories_all" style=3D"tex= +t-decoration:none; color:#15C">help center</a> or <a href=3D"https://www.yo= +utube.com/watch?v=3DCFf7dlewJus&amp" style=3D"text-decoration:none; color:#= +15C">watch the video</a>.</div><br/><br/> +<div style=3D"clear:both; padding-left:13px; height:6.8em;"><table style=3D= +"width:100%; border-collapse:collapse; border:0"><tr><td style=3D"width:68p= +x"><img alt=3D'Gmail icon' width=3D"49" height=3D"37" src=3D"https://ssl.gs= +tatic.com/ui/v1/icons/mail/images/gmail_logo_large.png" style=3D"display:bl= +ock;"/></td><td style=3D"align:left; font-family:'Open sans','Arial',sans-s= +erif; vertical-align:bottom"><span style=3D"font-size:small">Happy emailing= +,<br/></span><span style=3D"font-size:x-large; line-height:1">The Gmail Tea= +m</span></td></tr></table></div> +</td></tr></table></div> +<div style=3D"direction:ltr;color:#777; font-size:0.8em; border-radius:1em;= + padding:1em; margin:0 auto 4% auto; font-family:'Arial','Helvetica',sans-s= +erif; text-align:center;">=C2=A9 2018 Google Inc. 1600 Amphitheatre Parkway= +, Mountain View, CA 94043<br/></div></div></body></html> + +--001a113ec2a09de2430562557108-- BODY[HEADER] {443} +MIME-Version: 1.0 +x-no-auto-attachment: 1 +Received: by 10.107.68.11; Tue, 9 Jan 2018 02:33:21 -0800 (PST) +Date: Tue, 9 Jan 2018 02:33:21 -0800 +Message-ID: <CAMFzkGp=kyhZACnRJoayOHGXPxxYrPJpfXr+A8+=+zShETytkg@mail.gmail.com> +Subject: Stay more organized with Gmail's inbox +From: Gmail Team <mail-noreply@google.com> +To: UuUuUu UuUuUu <UuUuUuUuU@gmail.com> +Content-Type: multipart/alternative; boundary="001a113ec2a09de2430562557108" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1395 40 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 5634 75 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "001a113ec2a09de2430562557108") NIL NIL)) +* 4 FETCH (UID 4 FLAGS (\Seen) BODY[TEXT] {53} +Hi, + +test email + +With Kind Regards, + +UuUuUu + + BODY[HEADER] {3039} +Delivered-To: UuUuUuUuU@gmail.com +Received: by 10.79.131.133 with SMTP id f127csp2680691ivd; Mon, 22 Jan 2018 + 05:57:03 -0800 (PST) +X-Google-Smtp-Source: AH8x224fsEuLv14q7K4898rdEQYejvFU0O/DTilndmoLFyqFSt+yOCaQc8DK8D7ZTQ4ecDV8zIS0 +X-Received: by 10.223.134.104 with SMTP id 37mr6422673wrw.103.1516629423363; + Mon, 22 Jan 2018 05:57:03 -0800 (PST) +ARC-Seal: i=1; a=rsa-sha256; t=1516629423; cv=none; d=google.com; + s=arc-20160816; + b=WPN4DljX3rtwM38kMd5SpUUZ35oE8mCQh7/573mUj2dX/SpuGI3TBKNi/RovSAh9Km + Gr9XJF8TWmrWVuYIqhcYF4/Z9yA8a/bW6Q73PdEDdFyto46RClL5cP0YNqBATDBu/aPE + Z3Bte04rp9kIhWw9oFpSYj/PNwR2j4qQheXqKvCoKIIodOL+Qkm/cl+6gn0AqOotM4u4 + wflDQ8hDFe73WeGXXx1Z8Oc0istA/5+K8leaCCprFOGCKBCGa7QQieEMWzVzSAHzyT+M + N/2ESaRGqA5ULK0X0j+3eA/yDce4jLUsvimvmKdug6lmP/nn9s59nH1JVy9epezVvURQ fWhw== +ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; + s=arc-20160816; + h=content-language:content-transfer-encoding:mime-version:user-agent + :date:message-id:subject:from:to:arc-authentication-results; + bh=1AUHZNXdICXlEHJntLFnjJ8BfW/UNWnWKfq/HwLwAgA=; + b=nnbr7xkKZdLIAPnkG1AEHRpEkS+73jzUaN9ptNA/d9krb+rM8lwdthI20trd/ardpT + VPCuKyCBzFWPyNbE+6h/BOC4jDEOzW46ImPM7M43jU8X1XAKC1IXSI4U0jrYsFNnRrxo + wAZaSzoVkOYuVaP48pFqcscbk/gepLRKd5rWB8BrSOhoOXgRP0NgMiH1uc9y74LBTuvH + IyHXfnG4mxoCJGv6FvnVm7sLgGvIXUCi0oYJECUqBnlLBHNnm6dk2hzt8cMe/oVtUeb/ + ZZNEyt9LVjnHoWJa9uAkXnHvXDG5yAayYyImBAK7V4b3AiV+2JrIJsken1Kqj9HYFMx+ nYAQ== +ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain + of UuUuUu.UuUuUu@DdDdDdDdDdDd.com designates 5.230.138.58 as permitted + sender) smtp.mailfrom=UuUuUu.UuUuUu@DdDdDdDdDdDd.com +Return-Path: <UuUuUu.UuUuUu@DdDdDdDdDdDd.com> +Received: from hoza15.fra2.bytemine.net (hoza15.fra2.bytemine.net. + [5.230.138.58]) by mx.google.com with ESMTPS id + c204si5332188wmh.59.2018.01.22.05.57.03 for <UuUuUuUuU@gmail.com> + (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 22 Jan + 2018 05:57:03 -0800 (PST) +Received-SPF: pass (google.com: domain of UuUuUu.UuUuUu@DdDdDdDdDdDd.com + designates 5.230.138.58 as permitted sender) client-ip=5.230.138.58; +Authentication-Results: mx.google.com; spf=pass (google.com: domain of + UuUuUu.UuUuUu@DdDdDdDdDdDd.com designates 5.230.138.58 as permitted sender) + smtp.mailfrom=UuUuUu.UuUuUu@DdDdDdDdDdDd.com +Received: from [185.8.93.2] (helo=[192.168.1.142]) by hoza15.fra2.bytemine.net + with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) + (envelope-from <UuUuUu.UuUuUu@DdDdDdDdDdDd.com>) id 1edcas-0003Ck-ML for + UuUuUuUuU@gmail.com; Mon, 22 Jan 2018 14:57:02 +0100 +To: UuUuUuUuU@gmail.com +From: UuUuUu UuUuUu <UuUuUu.UuUuUu@DdDdDdDdDdDd.com> +Subject: email +Message-ID: <760b3ff5-b975-044e-8934-44a56a05819d@DdDdDdDdDdDd.com> +Date: Mon, 22 Jan 2018 13:57:02 +0000 +User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 + Thunderbird/52.5.0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Transfer-Encoding: 7bit +Content-Language: en-US + + BODYSTRUCTURE ("TEXT" "PLAIN" ("CHARSET" "utf-8" "FORMAT" "flowed") NIL NIL "7BIT" 53 2 NIL NIL NIL)) +* 5 FETCH (UID 5 FLAGS (\Seen) BODY[TEXT] {227} +This email was sent in order to test the outgoing mail server information p= +rovided in the NnNnNnNn application. A successful receipt of this email ind= +icates that the outgoing mail server information provided is valid. + + BODY[HEADER] {729} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id i31sm2069483qtb.5.2018.01.24.05.45.12 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 05:45:13 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 13:45:11 +0000 +To: UuUuUuUuU@gmail.com +Subject: Test Email from NnNnNnNn +Message-ID: <1f2280f2be50eb844e5b4f4f413a4736@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + + BODYSTRUCTURE ("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 227 5 NIL NIL NIL)) +* 6 FETCH (UID 6 FLAGS (\Seen) BODY[TEXT] {835} +This is a multi-part message in MIME format. + +--b1_a8b16beb62b51887453a9f2666fa6530 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Hi qweqwe, \n Please confirm that you have opted in by selecting the follo= +wing link: http://DdDdDdDd.DdD.DdDdDdDd.com/index.php?entryPoint=3DConfirmO= +ptIn&from=3DUuUuUuUuU@gmail.com + + +--b1_a8b16beb62b51887453a9f2666fa6530 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<p>Hi qweqwe,</p>=0A <p>=0A Please confirm that= + you have opted in by selecting the following link:=0A <a hr= +ef=3D"http://DdDdDdDd.DdD.DdDdDdDd.com/index.php?entryPoint=3DConfirmOptIn&= +from=3DUuUuUuUuU@gmail.com">Opt In</a>=0A </p> + + +--b1_a8b16beb62b51887453a9f2666fa6530-- + + BODY[HEADER] {728} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id 92sm257642qky.72.2018.01.24.05.49.13 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 05:49:14 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 13:49:12 +0000 +To: qweqwe <UuUuUuUuU@gmail.com> +Subject: Confirm Opt In +Message-ID: <a8b16beb62b51887453a9f2666fa6530@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_a8b16beb62b51887453a9f2666fa6530" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 191 4 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 296 6 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_a8b16beb62b51887453a9f2666fa6530") NIL NIL)) +* 7 FETCH (UID 7 FLAGS (\Seen) BODY[TEXT] {835} +This is a multi-part message in MIME format. + +--b1_3416db96a9472e202d9896d1c61dd70c +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Hi qweqwe, \n Please confirm that you have opted in by selecting the follo= +wing link: http://DdDdDdDd.DdD.DdDdDdDd.com/index.php?entryPoint=3DConfirmO= +ptIn&from=3DUuUuUuUuU@gmail.com + + +--b1_3416db96a9472e202d9896d1c61dd70c +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<p>Hi qweqwe,</p>=0A <p>=0A Please confirm that= + you have opted in by selecting the following link:=0A <a hr= +ef=3D"http://DdDdDdDd.DdD.DdDdDdDd.com/index.php?entryPoint=3DConfirmOptIn&= +from=3DUuUuUuUuU@gmail.com">Opt In</a>=0A </p> + + +--b1_3416db96a9472e202d9896d1c61dd70c-- + + BODY[HEADER] {730} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id p31sm2154909qta.77.2018.01.24.05.49.20 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 05:49:20 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 13:49:19 +0000 +To: qweqwe <UuUuUuUuU@gmail.com> +Subject: Confirm Opt In +Message-ID: <3416db96a9472e202d9896d1c61dd70c@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_3416db96a9472e202d9896d1c61dd70c" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 191 4 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 296 6 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_3416db96a9472e202d9896d1c61dd70c") NIL NIL)) +* 8 FETCH (UID 8 FLAGS (\Seen) BODY[TEXT] {835} +This is a multi-part message in MIME format. + +--b1_f4392250b6c92b8d1b14722747985e7d +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Hi UuUuUu, \n Please confirm that you have opted in by selecting the follo= +wing link: http://DdDdDdDd.DdD.DdDdDdDd.com/index.php?entryPoint=3DConfirmO= +ptIn&from=3DUuUuUuUuU@gmail.com + + +--b1_f4392250b6c92b8d1b14722747985e7d +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<p>Hi UuUuUu,</p>=0A <p>=0A Please confirm that= + you have opted in by selecting the following link:=0A <a hr= +ef=3D"http://DdDdDdDd.DdD.DdDdDdDd.com/index.php?entryPoint=3DConfirmOptIn&= +from=3DUuUuUuUuU@gmail.com">Opt In</a>=0A </p> + + +--b1_f4392250b6c92b8d1b14722747985e7d-- + + BODY[HEADER] {730} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id r55sm2230376qta.64.2018.01.24.06.19.15 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:19:15 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:19:14 +0000 +To: UuUuUu <UuUuUuUuU@gmail.com> +Subject: Confirm Opt In +Message-ID: <f4392250b6c92b8d1b14722747985e7d@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_f4392250b6c92b8d1b14722747985e7d" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 191 4 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 296 6 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_f4392250b6c92b8d1b14722747985e7d") NIL NIL)) +* 9 FETCH (UID 9 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_aa569c523d2f6e074c0e549cae212f57 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..21424 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_aa569c523d2f6e074c0e549cae212f57 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">21424</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_aa569c523d2f6e074c0e549cae212f57-- + + BODY[HEADER] {721} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id l1sm307895qki.60.2018.01.24.06.26.15 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:26:15 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:26:14 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <aa569c523d2f6e074c0e549cae212f57@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_aa569c523d2f6e074c0e549cae212f57" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_aa569c523d2f6e074c0e549cae212f57") NIL NIL)) +* 10 FETCH (UID 17 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_117ff555f497aadce6e3ef0a181d0e3e +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..56292 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_117ff555f497aadce6e3ef0a181d0e3e +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">56292</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_117ff555f497aadce6e3ef0a181d0e3e-- + + BODY[HEADER] {721} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id y72sm340032qkg.3.2018.01.24.06.27.27 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:27:28 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:27:26 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <117ff555f497aadce6e3ef0a181d0e3e@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_117ff555f497aadce6e3ef0a181d0e3e" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_117ff555f497aadce6e3ef0a181d0e3e") NIL NIL)) +* 11 FETCH (UID 18 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_d2988d8c2ad42248e94357aff2bc659a +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..36765 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_d2988d8c2ad42248e94357aff2bc659a +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">36765</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_d2988d8c2ad42248e94357aff2bc659a-- + + BODY[HEADER] {722} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id g12sm2262406qtc.3.2018.01.24.06.27.58 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:27:59 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:27:57 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <d2988d8c2ad42248e94357aff2bc659a@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_d2988d8c2ad42248e94357aff2bc659a" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_d2988d8c2ad42248e94357aff2bc659a") NIL NIL)) +* 12 FETCH (UID 19 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_f74809ece1a24e05455d8f962f53376b +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..98644 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_f74809ece1a24e05455d8f962f53376b +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">98644</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_f74809ece1a24e05455d8f962f53376b-- + + BODY[HEADER] {723} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id w138sm307894qkb.28.2018.01.24.06.27.59 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:28:00 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:27:58 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <f74809ece1a24e05455d8f962f53376b@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_f74809ece1a24e05455d8f962f53376b" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_f74809ece1a24e05455d8f962f53376b") NIL NIL)) +* 13 FETCH (UID 20 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_4bcf0f269e8179697b9ec5ed7fc35390 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..63369 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_4bcf0f269e8179697b9ec5ed7fc35390 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">63369</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_4bcf0f269e8179697b9ec5ed7fc35390-- + + BODY[HEADER] {721} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id m68sm316577qkc.5.2018.01.24.06.28.18 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:28:19 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:28:17 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <4bcf0f269e8179697b9ec5ed7fc35390@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_4bcf0f269e8179697b9ec5ed7fc35390" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_4bcf0f269e8179697b9ec5ed7fc35390") NIL NIL)) +* 14 FETCH (UID 21 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_6f02d5e98c85ce665b6476998e8ccb2d +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..84001 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_6f02d5e98c85ce665b6476998e8ccb2d +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">84001</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_6f02d5e98c85ce665b6476998e8ccb2d-- + + BODY[HEADER] {722} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id r5sm2184345qtc.24.2018.01.24.06.28.22 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:28:22 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:28:20 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <6f02d5e98c85ce665b6476998e8ccb2d@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_6f02d5e98c85ce665b6476998e8ccb2d" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_6f02d5e98c85ce665b6476998e8ccb2d") NIL NIL)) +* 15 FETCH (UID 22 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_332e28c801f2a39dd6c59d9d5f2840ac +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..18689 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_332e28c801f2a39dd6c59d9d5f2840ac +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">18689</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_332e28c801f2a39dd6c59d9d5f2840ac-- + + BODY[HEADER] {722} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id w133sm348032wmg.5.2018.01.24.06.28.29 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:28:29 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:28:28 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <332e28c801f2a39dd6c59d9d5f2840ac@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_332e28c801f2a39dd6c59d9d5f2840ac" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_332e28c801f2a39dd6c59d9d5f2840ac") NIL NIL)) +* 16 FETCH (UID 23 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_acc03ea4ab336bc6ba238d35ce97cde0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..10904 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_acc03ea4ab336bc6ba238d35ce97cde0 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">10904</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_acc03ea4ab336bc6ba238d35ce97cde0-- + + BODY[HEADER] {720} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id m6sm390400wmb.6.2018.01.24.06.28.59 for <UuUuUuUuU@gmail.com> + (version=TLS1 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 24 Jan 2018 + 06:28:59 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:28:59 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <acc03ea4ab336bc6ba238d35ce97cde0@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_acc03ea4ab336bc6ba238d35ce97cde0" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_acc03ea4ab336bc6ba238d35ce97cde0") NIL NIL)) +* 17 FETCH (UID 24 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_386b8f61ba79571781267a7ac0a75f50 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..97333 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_386b8f61ba79571781267a7ac0a75f50 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">97333</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_386b8f61ba79571781267a7ac0a75f50-- + + BODY[HEADER] {723} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id w36sm2246255qtb.37.2018.01.24.06.29.20 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:29:20 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:29:19 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <386b8f61ba79571781267a7ac0a75f50@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_386b8f61ba79571781267a7ac0a75f50" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_386b8f61ba79571781267a7ac0a75f50") NIL NIL)) +* 18 FETCH (UID 25 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_d760d940fe4b5d606c6483c6f9e6c46a +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..13466 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_d760d940fe4b5d606c6483c6f9e6c46a +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">13466</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_d760d940fe4b5d606c6483c6f9e6c46a-- + + BODY[HEADER] {723} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id d199sm293425qkc.46.2018.01.24.06.29.22 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:29:23 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:29:21 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <d760d940fe4b5d606c6483c6f9e6c46a@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_d760d940fe4b5d606c6483c6f9e6c46a" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_d760d940fe4b5d606c6483c6f9e6c46a") NIL NIL)) +* 19 FETCH (UID 26 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_9fd1ec6d3e14efb3c50bd0f15320ebe7 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..62292 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_9fd1ec6d3e14efb3c50bd0f15320ebe7 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">62292</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_9fd1ec6d3e14efb3c50bd0f15320ebe7-- + + BODY[HEADER] {723} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id v125sm313663qka.10.2018.01.24.06.29.30 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:29:31 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:29:29 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <9fd1ec6d3e14efb3c50bd0f15320ebe7@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_9fd1ec6d3e14efb3c50bd0f15320ebe7" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_9fd1ec6d3e14efb3c50bd0f15320ebe7") NIL NIL)) +* 20 FETCH (UID 27 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_65efebc0040838bcc53e265c3d7aa240 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..41565 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_65efebc0040838bcc53e265c3d7aa240 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">41565</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_65efebc0040838bcc53e265c3d7aa240-- + + BODY[HEADER] {722} +Return-Path: <UuUuUuUuU@gmail.com> +Received: from DdDdDdDd.DdD.DdDdDdDd.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id y72sm320011qky.63.2018.01.24.06.30.01 for + <UuUuUuUuU@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 06:30:01 -0800 (PST) +From: NnNnNnNn <UuUuUuUuU@gmail.com> +X-Google-Original-From: NnNnNnNn <Uu@DdD.com> +Date: Wed, 24 Jan 2018 14:30:00 +0000 +To: Administrator <UuUuUuUuU@gmail.com> +Subject: +Message-ID: <65efebc0040838bcc53e265c3d7aa240@DdDdDdDd.DdD.DdDdDdDd.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_65efebc0040838bcc53e265c3d7aa240" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_65efebc0040838bcc53e265c3d7aa240") NIL NIL)) + diff --git a/tests/_data/FETCH_UID.txt b/tests/_data/FETCH_UID.txt new file mode 100755 index 0000000..b035ce6 --- /dev/null +++ b/tests/_data/FETCH_UID.txt @@ -0,0 +1 @@ +* 1 FETCH (UID 1) diff --git a/tests/_data/FETCH_UID_FLAGS_BODY_HEADER_BODYSTRUCTURE.txt b/tests/_data/FETCH_UID_FLAGS_BODY_HEADER_BODYSTRUCTURE.txt new file mode 100755 index 0000000..07277e7 --- /dev/null +++ b/tests/_data/FETCH_UID_FLAGS_BODY_HEADER_BODYSTRUCTURE.txt @@ -0,0 +1,21 @@ +* 1 FETCH (UID 1 FLAGS (\Seen) BODY[HEADER] {782} +Return-Path: <root@localhost.localdomain> +X-Original-To: user@localhost.localdomain +Delivered-To: user@localhost.localdomain +Received: from u255 (localhost [127.0.0.1]) + by u255 (Postfix) with ESMTP id 9AFDAB813D1 + for <user@localhost.localdomain>; Tue, 5 Jun 2018 08:59:50 +0100 (BST) +Date: Tue, 5 Jun 2018 08:59:50 +0100 +To: Daniel <user@localhost.localdomain> +From: Mailer <root@localhost.localdomain> +Reply-To: Information <user@localhost.localdomain> +Subject: Test Email: Baron Hartmann +Message-ID: <DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s@u255> +X-Mailer: PHPMailer 6.0.5 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s" +Content-Transfer-Encoding: 8bit + + BODYSTRUCTURE ((("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 58 1 NIL NIL NIL NIL)("text" "html" ("charset" "us-ascii") NIL NIL "7bit" 49 2 NIL NIL NIL NIL) "alternative" ("boundary" "b2_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s") NIL NIL NIL)("text" "plain" ("name" "Plaintext" "charset" "us-ascii") "<Plaintext>" NIL "base64" 18 1 NIL ("attachment" ("filename" "Plaintext")) NIL NIL) "mixed" ("boundary" "b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s") NIL NIL NIL) +) diff --git a/tests/_data/FETCH_UID_FLAGS_BODY_HEADER_BODYSTRUCTURE_BODY_TEXT.txt b/tests/_data/FETCH_UID_FLAGS_BODY_HEADER_BODYSTRUCTURE_BODY_TEXT.txt new file mode 100755 index 0000000..ef40465 --- /dev/null +++ b/tests/_data/FETCH_UID_FLAGS_BODY_HEADER_BODYSTRUCTURE_BODY_TEXT.txt @@ -0,0 +1,49 @@ +* 1 FETCH (UID 1 FLAGS (\Seen) BODY[HEADER] {782} +Return-Path: <root@localhost.localdomain> +X-Original-To: user@localhost.localdomain +Delivered-To: user@localhost.localdomain +Received: from u255 (localhost [127.0.0.1]) + by u255 (Postfix) with ESMTP id 9AFDAB813D1 + for <user@localhost.localdomain>; Tue, 5 Jun 2018 08:59:50 +0100 (BST) +Date: Tue, 5 Jun 2018 08:59:50 +0100 +To: Daniel <user@localhost.localdomain> +From: Mailer <root@localhost.localdomain> +Reply-To: Information <user@localhost.localdomain> +Subject: Test Email: Baron Hartmann +Message-ID: <DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s@u255> +X-Mailer: PHPMailer 6.0.5 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s" +Content-Transfer-Encoding: 8bit + + BODYSTRUCTURE ((("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 58 1 NIL NIL NIL NIL)("text" "html" ("charset" "us-ascii") NIL NIL "7bit" 49 2 NIL NIL NIL NIL) "alternative" ("boundary" "b2_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s") NIL NIL NIL)("text" "plain" ("name" "Plaintext" "charset" "us-ascii") "<Plaintext>" NIL "base64" 18 1 NIL ("attachment" ("filename" "Plaintext")) NIL NIL) "mixed" ("boundary" "b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s") NIL NIL NIL) BODY[TEXT] {835} +This is a multi-part message in MIME format. +--b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s +Content-Type: multipart/alternative; + boundary="b2_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s" + +--b2_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s +Content-Type: text/plain; charset=us-ascii + +This is the body in plain text for non-HTML mail clients + +--b2_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s +Content-Type: text/html; charset=us-ascii + +This is the HTML message body <b>in bold!</b> + + +--b2_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s-- + +--b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s +Content-Type: text/plain; name="Plaintext" +Content-Transfer-Encoding: base64 +Content-ID: <Plaintext> +Content-Disposition: attachment; filename=Plaintext + +bG9ydW0gaXBzdW0K + +--b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s-- + +) diff --git a/tests/_data/FLAGS_UID_BODY_HEADER_BODYSTRUCTURE_BODY_TEXT.txt b/tests/_data/FLAGS_UID_BODY_HEADER_BODYSTRUCTURE_BODY_TEXT.txt new file mode 100755 index 0000000..d084d4e --- /dev/null +++ b/tests/_data/FLAGS_UID_BODY_HEADER_BODYSTRUCTURE_BODY_TEXT.txt @@ -0,0 +1,58 @@ +* 100 FETCH (UID 107 FLAGS (\Seen) BODY[TEXT] {2052} +This is a multi-part message in MIME format. + +--b1_18dbf56cc0931ca2521074b117fba77b +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Add your headline here..34518 Lorem ipsum dolor sit amet, consectetur adipi= +scing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In pharetra t= +incidunt urna et malesuada. Etiam aliquet auctor justo eu placerat. In nec = +sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobortis nun= +c eleifend id. Curabitur semper tincidunt vulputate. Nullam fermentum pelle= +ntesque ullamcorper. + + +--b1_18dbf56cc0931ca2521074b117fba77b +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h1 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:24px;line-height:38.4px= +;color:#444444;padding:0px;margin:0px;">Add your headline here..</h1></div>= +<div class=3D"mozaik-inner" style=3D"font-family:Arial, Helvetica, sans-ser= +if;font-size:14px;line-height:22.4px;color:rgb(68,68,68);padding:0px 30px;m= +argin:0px auto;width:600px;background-color:rgb(250,250,250);"><h2 style=3D= +"font-family:Arial, Helvetica, sans-serif;font-size:18px;line-height:28.8px= +;color:rgb(68,68,68);padding:0px;margin:0px;">34518</h2><p style=3D"font-fa= +mily:Arial, Helvetica, sans-serif;font-size:14px;line-height:22.4px;color:r= +gb(68,68,68);padding:0px;margin:0px;">Lorem ipsum dolor sit amet, consectet= +ur adipiscing elit. Etiam tempus odio ante, in feugiat ex pretium eu. In ph= +aretra tincidunt urna et malesuada. Etiam aliquet auctor justo eu placerat.= + In nec sollicitudin enim. Nulla facilisi. In viverra velit turpis, et lobo= +rtis nunc eleifend id. Curabitur semper tincidunt vulputate. Nullam ferment= +um pellentesque ullamcorper.</p></div> + + +--b1_18dbf56cc0931ca2521074b117fba77b-- + + BODY[HEADER] {721} +Return-Path: <xxxxxxxxx@gmail.com> +Received: from suitecrm.xxx.xxxxxxxx.com ([154.61.32.168]) by smtp.gmail.com + with ESMTPSA id l1sm335625wmh.13.2018.01.24.07.08.18 for + <xxxxxxxxx@gmail.com> (version=TLS1 cipher=ECDHE-RSA-AES128-SHA + bits=128/128); Wed, 24 Jan 2018 07:08:19 -0800 (PST) +From: SuiteCRM <xxxxxxxxx@gmail.com> +X-Google-Original-From: SuiteCRM <my@crm.com> +Date: Wed, 24 Jan 2018 15:08:18 +0000 +To: Administrator <xxxxxxxxx@gmail.com> +Subject: +Message-ID: <18dbf56cc0931ca2521074b117fba77b@suitecrm.xxx.xxxxxxxx.com> +X-Mailer: PHPMailer 5.2.25 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_18dbf56cc0931ca2521074b117fba77b" + + BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 414 9 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 1290 26 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b1_18dbf56cc0931ca2521074b117fba77b") NIL NIL)) diff --git a/tests/_data/LIST.txt b/tests/_data/LIST.txt new file mode 100755 index 0000000..95a0f5d --- /dev/null +++ b/tests/_data/LIST.txt @@ -0,0 +1,9 @@ +* LIST (\HasNoChildren) "/" "INBOX" +* LIST (\HasChildren \Noselect) "/" "[Gmail]" +* LIST (\All \HasNoChildren) "/" "[Gmail]/All Mail" +* LIST (\Drafts \HasNoChildren) "/" "[Gmail]/Drafts" +* LIST (\HasNoChildren \Important) "/" "[Gmail]/Important" +* LIST (\HasNoChildren \Sent) "/" "[Gmail]/Sent Mail" +* LIST (\HasNoChildren \Junk) "/" "[Gmail]/Spam" +* LIST (\Flagged \HasNoChildren) "/" "[Gmail]/Sent Mail" +* LIST (\HasNoChildren \Trash) "/" "[Gmail]/Trash" diff --git a/tests/_data/LineLengthDetectedIncorrectly.txt b/tests/_data/LineLengthDetectedIncorrectly.txt new file mode 100755 index 0000000..2b81586 --- /dev/null +++ b/tests/_data/LineLengthDetectedIncorrectly.txt @@ -0,0 +1,21 @@ +* 2669 FETCH (UID 2676 FLAGS (\Seen) BODY[HEADER] {838} +Return-Path: <xxxxxxxxx@xxxxx.com> +Received: from u255 ([185.8.93.2]) by smtp.xxxxx.com with ESMTPSA id + n11-v6sm20263937wro.13.2018.06.07.01.22.06 for <xxxxxxxxx@xxxxx.com> + (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 07 Jun + 2018 01:22:07 -0700 (PDT) +From: Mailer <xxxxxxxxx@xxxxx.com> +X-Google-Original-From: Mailer <xxxxxx.xxxxxx@xxxxxxxxxxxx.com> +Date: Thu, 7 Jun 2018 09:22:06 +0100 +To: xxxxxx <xxxxxxxxx@xxxxx.com> +Reply-To: Information <xxxxxx.xxxxxx@xxxxxxxxxxxx.com> +Subject: Test Email: Prof. Whitney Steuber DDS +Message-ID: <Spp7KzQILIjJfL1dFD3eWNpAh6yMwE6SC0c7YjEsTM@u255> +X-Mailer: PHPMailer 6.0.5 (https://github.com/PHPMailer/PHPMailer) +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="b1_Spp7KzQILIjJfL1dFD3eWNpAh6yMwE6SC0c7YjEsTM" +Content-Transfer-Encoding: 8bit + + BODYSTRUCTURE ((("TEXT" "PLAIN" ("CHARSET" "us-ascii") NIL NIL "7BIT" 58 2 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "us-ascii") NIL NIL "7BIT" 49 1 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "b2_Spp7KzQILIjJfL1dFD3eWNpAh6yMwE6SC0c7YjEsTM") NIL NIL)("TEXT" "PLAIN" ("NAME" "Plaintext") "<Plaintext>" NIL "BASE64" 18 1 NIL ("ATTACHMENT" ("FILENAME" "Plaintext")) NIL) "MIXED" ("BOUNDARY" "b1_Spp7KzQILIjJfL1dFD3eWNpAh6yMwE6SC0c7YjEsTM") NIL NIL)) +A3 OK Success diff --git a/tests/_data/PASSIVE_CAPABILITY.txt b/tests/_data/PASSIVE_CAPABILITY.txt new file mode 100644 index 0000000..7df4fcd --- /dev/null +++ b/tests/_data/PASSIVE_CAPABILITY.txt @@ -0,0 +1 @@ +* CAPABILITY [ IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN AUTH=PLAIN-CLIENTTOKEN AUTH=OAUTHBEARER AUTH=XOAUTH ] \ No newline at end of file diff --git a/tests/_data/PLAINTEXT_FETCH_1_20_FLAGS_UID_HEADER_BODYSTRUCTURE.txt b/tests/_data/PLAINTEXT_FETCH_1_20_FLAGS_UID_HEADER_BODYSTRUCTURE.txt new file mode 100755 index 0000000..55eac7b --- /dev/null +++ b/tests/_data/PLAINTEXT_FETCH_1_20_FLAGS_UID_HEADER_BODYSTRUCTURE.txt @@ -0,0 +1,14 @@ +* 2677 FETCH (UID 2684 FLAGS (\Seen) BODY[HEADER] {413} +MIME-Version: 1.0 +Received: by 2002:a25:d989:0:0:0:0:0 with HTTP; Wed, 20 Jun 2018 04:32:20 + -0700 (PDT) +Date: Wed, 20 Jun 2018 12:32:20 +0100 +Delivered-To: suitecrm2@gmail.com +Message-ID: <CAMFzkGrn474i8Zy7V6jX04=rOM3SmeOvK3KmZFZLZ-0Xqz050w@mail.gmail.com> +Subject: Plain text email +From: Daniel Samson <suitecrm2@gmail.com> +To: qweqwe <suitecrm2@gmail.com> +Content-Type: text/plain; charset="UTF-8" + + BODYSTRUCTURE ("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "7BIT" 38 1 NIL NIL NIL)) +3 OK Success diff --git a/tests/_data/PLAIN_FETCH_UID_BODY_HEADER_BODYSTUCTURE_BODY.txt b/tests/_data/PLAIN_FETCH_UID_BODY_HEADER_BODYSTUCTURE_BODY.txt new file mode 100755 index 0000000..51993c8 --- /dev/null +++ b/tests/_data/PLAIN_FETCH_UID_BODY_HEADER_BODYSTUCTURE_BODY.txt @@ -0,0 +1,15 @@ +* 2677 FETCH (UID 2684 FLAGS (\Seen) BODY[TEXT] {38} +Hi this should only be in plain text + BODY[HEADER] {403} +MIME-Version: 1.0 +Received: by 2002:a25:d989:0:0:0:0:0 with HTTP; Wed, 20 Jun 2018 04:32:20 + -0700 (PDT) +Date: Wed, 20 Jun 2018 12:32:20 +0100 +Delivered-To: xxxxxxxxx@gmail.com +Message-ID: <CAMFzkGrn474i8Zy7V6jX04=rOM3SmeOvK3KmZFZLZ-0Xqz050w@mail.gmail.com> +Subject: Plain text email +From: Daniel xxxxxx <xxxxxxxxx@gmail.com> +To: qweqwe <xxxxxxxxx@gmail.com> +Content-Type: text/plain; charset="UTF-8" + + BODYSTRUCTURE ("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "7BIT" 38 1 NIL NIL NIL)) diff --git a/tests/_data/QUOTES_ISSUE.txt b/tests/_data/QUOTES_ISSUE.txt new file mode 100755 index 0000000..15c7c09 --- /dev/null +++ b/tests/_data/QUOTES_ISSUE.txt @@ -0,0 +1,3 @@ + +Content-Type: multipart/mixed; + boundary="b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s" diff --git a/tests/_data/SEARCH_1_5.txt b/tests/_data/SEARCH_1_5.txt new file mode 100644 index 0000000..716b51c --- /dev/null +++ b/tests/_data/SEARCH_1_5.txt @@ -0,0 +1,2 @@ +* SEARCH 1 2 3 4 5 +A1 OK SEARCH completed (Success) diff --git a/tests/_data/SELECT_INBOX.txt b/tests/_data/SELECT_INBOX.txt new file mode 100755 index 0000000..61ec00b --- /dev/null +++ b/tests/_data/SELECT_INBOX.txt @@ -0,0 +1,8 @@ +* FLAGS (\Answered \Flagged \Deleted \Seen \Draft) +* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted. +* 74090 EXISTS +* 0 RECENT +* OK [UNSEEN 22] First unseen. +* OK [UIDVALIDITY 1528185994] UIDs valid +* OK [UIDNEXT 74091] Predicted next UID +2 OK [READ-WRITE] Select completed (0.018 + 0.000 + 0.017 secs). \ No newline at end of file diff --git a/tests/_data/server_greeting.txt b/tests/_data/server_greeting.txt new file mode 100755 index 0000000..d4054e9 --- /dev/null +++ b/tests/_data/server_greeting.txt @@ -0,0 +1 @@ +* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE STARTTLS AUTH=PLAIN] Dovecot ready. diff --git a/tests/_output/.gitignore b/tests/_output/.gitignore new file mode 100755 index 0000000..c96a04f --- /dev/null +++ b/tests/_output/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/tests/_support/AcceptanceTester.php b/tests/_support/AcceptanceTester.php new file mode 100755 index 0000000..4c7dcbb --- /dev/null +++ b/tests/_support/AcceptanceTester.php @@ -0,0 +1,26 @@ +<?php + + +/** + * Inherited Methods + * @method void wantToTest($text) + * @method void wantTo($text) + * @method void execute($callable) + * @method void expectTo($prediction) + * @method void expect($prediction) + * @method void amGoingTo($argumentation) + * @method void am($role) + * @method void lookForwardTo($achieveValue) + * @method void comment($description) + * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL) + * + * @SuppressWarnings(PHPMD) +*/ +class AcceptanceTester extends \Codeception\Actor +{ + use _generated\AcceptanceTesterActions; + + /** + * Define custom actions here + */ +} diff --git a/tests/_support/FunctionalTester.php b/tests/_support/FunctionalTester.php new file mode 100755 index 0000000..7e888f8 --- /dev/null +++ b/tests/_support/FunctionalTester.php @@ -0,0 +1,26 @@ +<?php + + +/** + * Inherited Methods + * @method void wantToTest($text) + * @method void wantTo($text) + * @method void execute($callable) + * @method void expectTo($prediction) + * @method void expect($prediction) + * @method void amGoingTo($argumentation) + * @method void am($role) + * @method void lookForwardTo($achieveValue) + * @method void comment($description) + * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL) + * + * @SuppressWarnings(PHPMD) +*/ +class FunctionalTester extends \Codeception\Actor +{ + use _generated\FunctionalTesterActions; + + /** + * Define custom actions here + */ +} diff --git a/tests/_support/Helper/Acceptance.php b/tests/_support/Helper/Acceptance.php new file mode 100755 index 0000000..65b9421 --- /dev/null +++ b/tests/_support/Helper/Acceptance.php @@ -0,0 +1,10 @@ +<?php +namespace Helper; + +// here you can define custom actions +// all public methods declared in helper class will be available in $I + +class Acceptance extends \Codeception\Module +{ + +} diff --git a/tests/_support/Helper/Functional.php b/tests/_support/Helper/Functional.php new file mode 100755 index 0000000..4183cb0 --- /dev/null +++ b/tests/_support/Helper/Functional.php @@ -0,0 +1,10 @@ +<?php +namespace Helper; + +// here you can define custom actions +// all public methods declared in helper class will be available in $I + +class Functional extends \Codeception\Module +{ + +} diff --git a/tests/_support/Helper/Unit.php b/tests/_support/Helper/Unit.php new file mode 100755 index 0000000..6064d37 --- /dev/null +++ b/tests/_support/Helper/Unit.php @@ -0,0 +1,10 @@ +<?php +namespace Helper; + +// here you can define custom actions +// all public methods declared in helper class will be available in $I + +class Unit extends \Codeception\Module +{ + +} diff --git a/tests/_support/UnitTester.php b/tests/_support/UnitTester.php new file mode 100755 index 0000000..2835357 --- /dev/null +++ b/tests/_support/UnitTester.php @@ -0,0 +1,26 @@ +<?php + + +/** + * Inherited Methods + * @method void wantToTest($text) + * @method void wantTo($text) + * @method void execute($callable) + * @method void expectTo($prediction) + * @method void expect($prediction) + * @method void amGoingTo($argumentation) + * @method void am($role) + * @method void lookForwardTo($achieveValue) + * @method void comment($description) + * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL) + * + * @SuppressWarnings(PHPMD) +*/ +class UnitTester extends \Codeception\Actor +{ + use _generated\UnitTesterActions; + + /** + * Define custom actions here + */ +} diff --git a/tests/_support/_generated/.gitignore b/tests/_support/_generated/.gitignore new file mode 100755 index 0000000..c96a04f --- /dev/null +++ b/tests/_support/_generated/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/tests/acceptance.suite.yml b/tests/acceptance.suite.yml new file mode 100644 index 0000000..bb977c4 --- /dev/null +++ b/tests/acceptance.suite.yml @@ -0,0 +1,12 @@ +# Codeception Test Suite Configuration +# +# Suite for acceptance tests. +# Perform tests in browser using the WebDriver or PhpBrowser. +# If you need both WebDriver and PHPBrowser tests - create a separate suite. + +actor: AcceptanceTester +modules: + enabled: + - PhpBrowser: + url: http://localhost/myapp + - \Helper\Acceptance \ No newline at end of file diff --git a/tests/acceptance/_bootstrap.php b/tests/acceptance/_bootstrap.php new file mode 100755 index 0000000..b3d9bbc --- /dev/null +++ b/tests/acceptance/_bootstrap.php @@ -0,0 +1 @@ +<?php diff --git a/tests/functional.suite.yml b/tests/functional.suite.yml new file mode 100755 index 0000000..789a726 --- /dev/null +++ b/tests/functional.suite.yml @@ -0,0 +1,13 @@ +# Codeception Test Suite Configuration +# +# Suite for functional tests +# Emulate web requests and make application process them +# Include one of framework modules (Symfony2, Yii2, Laravel5) to use it +# Remove this suite if you don't use frameworks + +actor: FunctionalTester +modules: + enabled: + # add a framework module here + - \Helper\Functional + - Asserts \ No newline at end of file diff --git a/tests/functional/_bootstrap.php b/tests/functional/_bootstrap.php new file mode 100755 index 0000000..b3d9bbc --- /dev/null +++ b/tests/functional/_bootstrap.php @@ -0,0 +1 @@ +<?php diff --git a/tests/mock/imap-ext.php b/tests/mock/imap-ext.php new file mode 100755 index 0000000..802395b --- /dev/null +++ b/tests/mock/imap-ext.php @@ -0,0 +1,315 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ +/** + * Php imap extension + * @see \SalesAgility\Imap\Stream\PhpImpExtensionConnection + * @see http://php.net/manual/en/ref.imap.php + */ + +namespace SalesAgility\Imap\Stream; + +/** + * @param $mailbox + * @param $username + * @param $password + * @param int $options + * @param int $n_retries + * @param null $params + * @return \stdClass + */ +function imap_open($mailbox, $username, $password, $options = 0, $n_retries = 0, $params = NULL) +{ + if(array_key_exists('mock_imap_open', $GLOBALS)) { + return $GLOBALS['mock_imap_open']; + } + + return new \stdClass(); +} + +/** + * @param $imap_stream + * @return bool + */ +function imap_ping($imap_stream) +{ + if(array_key_exists('mock_imap_ping', $GLOBALS)) { + return $GLOBALS['mock_imap_ping']; + } + + return true; +} + +/** + * @param $imap_stream + * @param $mailbox + * @param int $options + * @param int $n_retries + * @return bool + */ +function imap_reopen($imap_stream, $mailbox, $options = 0, $n_retries = 0) +{ + if(array_key_exists('mock_imap_reopen', $GLOBALS)) { + return $GLOBALS['mock_imap_reopen']; + } + + return true; +} + +/** + * @return array + */ +function imap_errors() +{ + if(array_key_exists('mock_imap_errors', $GLOBALS)) { + return $GLOBALS['mock_imap_errors']; + } + + return array(); +} + +/** + * @param $imap_stream + * @return int + */ +function imap_num_msg($imap_stream) +{ + if(array_key_exists('mock_imap_num_msg', $GLOBALS)) { + return $GLOBALS['mock_imap_num_msg']; + } + + return 0; +} + +/** + * @param $imap_stream + * @return int + */ +function imap_num_recent($imap_stream) +{ + if(array_key_exists('mock_imap_num_recent', $GLOBALS)) { + return $GLOBALS['mock_imap_num_recent']; + } + + return 0; +} + + +/** + * @return array|mixed + */ +function imap_fetch_overview() +{ + if(array_key_exists('mock_imap_fetch_overview', $GLOBALS)) { + return array_pop($GLOBALS['mock_imap_fetch_overview']); + } + + return new \stdClass(); +} +/** + * @return array|mixed + */ +function imap_fetchstructure() +{ + if(array_key_exists('mock_imap_fetchstructure', $GLOBALS)) { + return array_pop($GLOBALS['mock_imap_fetchstructure']); + } + + return new \stdClass(); +} + + +/** + * @return array|mixed + */ +function imap_fetchbody() +{ + if(array_key_exists('mock_imap_fetchbody', $GLOBALS)) { + return array_pop($GLOBALS['mock_imap_fetchbody']); + } + + return new \stdClass(); +} + +/** + * @return array|mixed + */ +function imap_body() +{ + if(array_key_exists('mock_imap_body', $GLOBALS)) { + return array_pop($GLOBALS['mock_imap_body']); + } + + return new \stdClass(); +} + + +/** + * @return array|mixed + */ +function imap_search($imap_stream, $criteria, $options = SE_FREE, $charset = NULL) +{ + if(array_key_exists('mock_imap_search', $GLOBALS)) { + return $GLOBALS['mock_imap_search']; + } + + return array(); +} + +function imap_setflag_full() +{ + if(array_key_exists('mock_imap_setflag_full', $GLOBALS)) { + return $GLOBALS['mock_imap_setflag_full']; + } + + return false; +} + +function imap_clearflag_full() +{ + if(array_key_exists('mock_imap_clearflag_full', $GLOBALS)) { + return $GLOBALS['mock_imap_clearflag_full']; + } + + return false; +} + + +function imap_mail_copy() +{ + if(array_key_exists('mock_imap_mail_copy', $GLOBALS)) { + return $GLOBALS['mock_imap_mail_copy']; + } + + return false; +} + + +function imap_list() +{ + if(array_key_exists('mock_imap_list', $GLOBALS)) { + return $GLOBALS['mock_imap_list']; + } + + return array(); +} + +function imap_lsub() +{ + if(array_key_exists('mock_imap_lsub', $GLOBALS)) { + return $GLOBALS['mock_imap_lsub']; + } + + return array(); +} + +function imap_check() +{ + if(array_key_exists('mock_imap_check', $GLOBALS)) { + return $GLOBALS['mock_imap_check']; + } + + return true; +} + +function imap_delete() +{ + if(array_key_exists('mock_imap_delete', $GLOBALS)) { + return $GLOBALS['mock_imap_delete']; + } + + return true; +} + +function imap_renamemailbox() +{ + if(array_key_exists('mock_imap_renamemailbox', $GLOBALS)) { + return $GLOBALS['mock_imap_renamemailbox']; + } + + return true; +} + +function imap_createmailbox() +{ + if(array_key_exists('mock_imap_createmailbox', $GLOBALS)) { + return $GLOBALS['mock_imap_createmailbox']; + } + + return true; +} + +function imap_subscribe() +{ + if(array_key_exists('mock_imap_subscribe', $GLOBALS)) { + return $GLOBALS['mock_imap_subscribe']; + } + + return true; +} + +function imap_unsubscribe() +{ + if(array_key_exists('mock_imap_unsubscribe', $GLOBALS)) { + return $GLOBALS['mock_imap_unsubscribe']; + } + + return true; +} + +function imap_expunge() +{ + if(array_key_exists('mock_imap_expunge', $GLOBALS)) { + return $GLOBALS['mock_imap_expunge']; + } + + return true; +} + +function imap_append() +{ + if(array_key_exists('mock_imap_append', $GLOBALS)) { + return $GLOBALS['mock_imap_append']; + } + + return true; +} + + +function imap_last_error() { + if(array_key_exists('mock_imap_last_error', $GLOBALS)) { + return $GLOBALS['mock_imap_last_error']; + } + + return ''; +} + +/** + * @param $imap_stream + * @param int $flag + * @return bool + * @see http://php.net/manual/en/function.imap-close.php + */ +function imap_close($imap_stream, $flag = 0) +{ + if(array_key_exists('mock_imap_close', $GLOBALS)) { + return $GLOBALS['mock_imap_close']; + } + + return true; +} diff --git a/tests/mock/stream.php b/tests/mock/stream.php new file mode 100644 index 0000000..f81da19 --- /dev/null +++ b/tests/mock/stream.php @@ -0,0 +1,83 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +/** + * Used to mock stream calls + * @see \SalesAgility\Stream\Connection + * @see http://php.net/manual/en/ref.stream.php + */ +namespace SalesAgility\Stream; + +function stream_socket_client() +{ + if(array_key_exists('mock_stream_socket_client', $GLOBALS)) { + return $GLOBALS['mock_stream_socket_client']; + } + + return new \stdClass(); +} + +function stream_socket_enable_crypto() +{ + if(array_key_exists('mock_stream_socket_enable_crypto_exception', $GLOBALS)) { + throw $GLOBALS['mock_stream_socket_enable_crypto_exception']; + } + + if(array_key_exists('mock_stream_socket_enable_crypto', $GLOBALS)) { + return $GLOBALS['mock_stream_socket_enable_crypto']; + } + + return false; +} + +function fclose() +{ + if(array_key_exists('mock_fclose', $GLOBALS)) { + return $GLOBALS['mock_fclose']; + } + + return false; +} + +function fwrite() +{ + if(array_key_exists('mock_fwrite', $GLOBALS)) { + return $GLOBALS['mock_fwrite']; + } + + return false; +} + +function fgets() +{ + if(array_key_exists('mock_fgets', $GLOBALS)) { + return $GLOBALS['mock_fgets']; + } + + return false; +} + +function feof() +{ + if(array_key_exists('mock_feof', $GLOBALS)) { + return $GLOBALS['mock_feof']; + } + + return false; +} \ No newline at end of file diff --git a/tests/unit.suite.yml b/tests/unit.suite.yml new file mode 100755 index 0000000..026c91d --- /dev/null +++ b/tests/unit.suite.yml @@ -0,0 +1,9 @@ +# Codeception Test Suite Configuration +# +# Suite for unit or integration tests. + +actor: UnitTester +modules: + enabled: + - Asserts + - \Helper\Unit \ No newline at end of file diff --git a/tests/unit/CommonErrorsTest.php b/tests/unit/CommonErrorsTest.php new file mode 100755 index 0000000..8e07c88 --- /dev/null +++ b/tests/unit/CommonErrorsTest.php @@ -0,0 +1,46 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Interpreter\ResponseInterpreter; +use SalesAgility\Iteration\StringIterator; + +class CommonErrorsTest extends \Codeception\Test\Unit +{ + public function testLineLengthDetectedIncorrectlyIssue() + { + $rawResponse = file_get_contents(codecept_data_dir('LineLengthDetectedIncorrectly.txt')); + $response = StringIterator::withLiteral($rawResponse); + $interpreter = new ResponseInterpreter(); + // Expect Exception + $interpreter->parse('A3', $response); + } + + /** + * Test when mime type boundary indicator has a separator like "." + * @throws \SalesAgility\Imap\Token\TokenException + */ + public function testMimeTypeBoundaryIndicator() + { + $rawResponse = file_get_contents(codecept_data_dir('BoundaryContainsSeparators.txt')); + $response = StringIterator::withLiteral($rawResponse); + $interpreter = new \SalesAgility\Imap\Interpreter\MessageInterpreter(); + $interpreted = $interpreter->parse($response); + $this->assertFalse(\SalesAgility\Utility\StringValue::startsWith($interpreted->offsetGet(0)->body()->text(),'--')); + } +} diff --git a/tests/unit/SalesAgility/Imap/CommandBuilder/PhpImapExtensionCommandBuilderTest.php b/tests/unit/SalesAgility/Imap/CommandBuilder/PhpImapExtensionCommandBuilderTest.php new file mode 100755 index 0000000..9166a8a --- /dev/null +++ b/tests/unit/SalesAgility/Imap/CommandBuilder/PhpImapExtensionCommandBuilderTest.php @@ -0,0 +1,1073 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\CommandBuilder\PhpImapExtensionCommandBuilder; + +class PhpImapExtensionCommandBuilderTest extends \Codeception\Test\Unit +{ + /** @var ReflectionClass */ + protected static $reflection; + /** @var ReflectionProperty */ + protected static $propertyCommandPrefix; + /** @var ReflectionProperty */ + protected static $propertyCommand; + /** @var ReflectionProperty */ + protected static $propertyArguments; + /** @var ReflectionProperty */ + protected static $propertyAsString; + /** @var ReflectionProperty */ + protected static $propertyIsValidated; + /** @var ReflectionProperty */ + protected static $propertyValidators; + /** @var ReflectionProperty */ + protected static $propertyIsRaw; + + protected function _before() + { + if (self::$reflection === null) { + // we will need these to check all the properties + // lets just declare it once. + // It will help keep this class as small as possible. + // It will make it a little easier to read. + self::$reflection = new \ReflectionClass(PhpImapExtensionCommandBuilder::class); + + self::$propertyCommandPrefix = self::$reflection->getProperty('commandPrefix'); + self::$propertyCommand = self::$reflection->getProperty('command'); + self::$propertyArguments = self::$reflection->getProperty('arguments'); + self::$propertyAsString = self::$reflection->getProperty('asString'); + self::$propertyIsValidated = self::$reflection->getProperty('isValidated'); + self::$propertyValidators = self::$reflection->getProperty('validators'); + self::$propertyIsRaw = self::$reflection->getProperty('isRaw'); + + self::$propertyCommandPrefix->setAccessible(true); + self::$propertyCommand->setAccessible(true); + self::$propertyArguments->setAccessible(true); + self::$propertyAsString->setAccessible(true); + self::$propertyIsValidated->setAccessible(true); + self::$propertyValidators->setAccessible(true); + self::$propertyIsRaw->setAccessible(true); + } + } + + // Constructor + public function testInstance() + { + $object = PhpImapExtensionCommandBuilder::instance(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals('', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + // Command Builder Util Functions + public function testUntagged() + { + $object = PhpImapExtensionCommandBuilder::instance()->raw('test')->build()->untagged(); + + $this->assertEquals('*', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('test', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' test', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(true, self::$propertyIsRaw->getValue($object)); + } + + public function testTagged() + { + $object = PhpImapExtensionCommandBuilder::instance()->raw('test')->build()->tagged('A1'); + + $this->assertEquals('A1', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('test', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' test', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(true, self::$propertyIsRaw->getValue($object)); + } + + public function testBuild() + { + $object = PhpImapExtensionCommandBuilder::instance(); + $validator = new \SalesAgility\Imap\CommandBuilder\CommandValidator\Command\NoopCommandValidator(); + $object->addCommandValidator( + $validator + ); + $object = $object->noop()->build(); + + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('NOOP', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' NOOP', self::$propertyAsString->getValue($object)); + $this->assertEquals(true, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array('NOOP' => $validator), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testCommand() + { + $object = PhpImapExtensionCommandBuilder::instance()->raw('test')->build()->command(); + $this->assertEquals('test', $object); + } + + public function testCommandArguments() + { + $object = PhpImapExtensionCommandBuilder::instance()->login()->user('user')->password('password'); + $expect = array( + 'USER' => 'user', + 'PASSWORD' => 'password' + ); + $this->assertEquals($expect, $object->build()->commandArguments()); + } + + public function testAddCommandValidator() + { + $object = PhpImapExtensionCommandBuilder::instance(); + $validator = new \SalesAgility\Imap\CommandBuilder\CommandValidator\Command\NoopCommandValidator(); + $object->addCommandValidator( + $validator + ); + + $this->assertEquals(array('NOOP' => $validator), self::$propertyValidators->getValue($object)); + } + + public function testCommandPrefix() + { + $object = PhpImapExtensionCommandBuilder::instance()->raw('test')->build()->untagged(); + $this->assertEquals('*', $object->commandPrefix()); + } + + public function testAsString() + { + $object = PhpImapExtensionCommandBuilder::instance()->raw('test')->build()->untagged(); + $this->assertEquals('* test', $object->asString()); + } + + public function testAsArray() + { + $object = PhpImapExtensionCommandBuilder::instance()->raw('test')->build(); + $this->assertEquals(array( + 'command' => 'test', + 'argument' => array(), + 'validated' => false, + 'raw' => true, + ), $object->asArray()); + } + + // Commands && Arguments + public function testRaw() + { + $object = PhpImapExtensionCommandBuilder::instance()->raw('test'); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('test', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' test', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(true, self::$propertyIsRaw->getValue($object)); + } + + public function testNoop() + { + $object = PhpImapExtensionCommandBuilder::instance()->noop(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('NOOP', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' NOOP', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testUid() + { + $object = PhpImapExtensionCommandBuilder::instance()->uid()->fetch(1)->header()->body()->flags()->uids(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('UID', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'FETCH' => array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE', + 'BODY[TEXT]', + 'FLAGS', + 'UID' + ) + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' UID FETCH 1 BODY[HEADER] BODYSTRUCTURE BODY[TEXT] FLAGS UID', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + + $object = PhpImapExtensionCommandBuilder::instance()->uid()->fetchRange(1, 2)->header()->body()->flags()->uids(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('UID', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'FETCH' => array( + 'MESSAGE' => '1:2', + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE', + 'BODY[TEXT]', + 'FLAGS', + 'UID' + ) + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' UID FETCH 1:2 BODY[HEADER] BODYSTRUCTURE BODY[TEXT] FLAGS UID', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + public function testFetch() + { + $object = PhpImapExtensionCommandBuilder::instance()->fetch(1)->header()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1 (BODY[HEADER] BODYSTRUCTURE)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testFetchRange() + { + $object = PhpImapExtensionCommandBuilder::instance()->fetchRange(1, 2)->header()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => '1:2', + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1:2 (BODY[HEADER] BODYSTRUCTURE)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testFetchSet() + { + $messageList = new \SalesAgility\Imap\Response\MessageList(); + + $message = new \SalesAgility\Imap\Response\Message(); + $message->offsetSet('number', '1'); + $message->offsetSet('uid', '1'); + $messageList[] = $message; + + $message = new \SalesAgility\Imap\Response\Message(); + $message->offsetSet('number', '3'); + $message->offsetSet('uid', '3'); + $messageList[] = $message; + + $object = PhpImapExtensionCommandBuilder::instance()->fetchSet($messageList)->header()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => '1,3', + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1,3 (BODY[HEADER] BODYSTRUCTURE)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + + + $object = PhpImapExtensionCommandBuilder::instance()->uid()->fetchSet($messageList)->header()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('UID', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'FETCH' => array( + 'MESSAGE' => '1,3', + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE' + ) + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' UID FETCH 1,3 (BODY[HEADER] BODYSTRUCTURE)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testUids() + { + $object = PhpImapExtensionCommandBuilder::instance()->fetch(1)->uids()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'UID' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1 (UID)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + + $object = PhpImapExtensionCommandBuilder::instance()->uid()->fetchRange(1,2)->header()->body()->flags()->uids()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('UID', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'FETCH' => array( + 'MESSAGE' => '1:2', + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE', + 'BODY[TEXT]', + 'FLAGS', + 'UID' + ) + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' UID FETCH 1:2 (BODY[HEADER] BODYSTRUCTURE BODY[TEXT] FLAGS UID)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + + + public function testFlags() + { + $object = PhpImapExtensionCommandBuilder::instance()->fetch(1)->flags(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'FLAGS' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1 FLAGS', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testHeader() + { + $object = PhpImapExtensionCommandBuilder::instance()->fetch(1)->header(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1 BODY[HEADER] BODYSTRUCTURE', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testBody() + { + $object = PhpImapExtensionCommandBuilder::instance()->fetch(1)->body(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'BODY[TEXT]' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1 BODY[TEXT]', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testSelect() + { + $object = PhpImapExtensionCommandBuilder::instance()->select('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('SELECT', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' SELECT "drafts"', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testLogout() + { + $object = PhpImapExtensionCommandBuilder::instance()->logout(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LOGOUT', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LOGOUT', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testLogin() + { + $object = PhpImapExtensionCommandBuilder::instance()->login(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LOGIN', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('USER' => '', 'PASSWORD' => ''), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LOGIN', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testUser() + { + $object = PhpImapExtensionCommandBuilder::instance()->login()->user('username'); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LOGIN', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('USER' => 'username', 'PASSWORD' => ''), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LOGIN username', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testPassword() + { + $object = PhpImapExtensionCommandBuilder::instance()->login()->user('username')->password('password'); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LOGIN', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('USER' => 'username', 'PASSWORD' => 'password'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LOGIN username password', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testExamine() + { + $object = PhpImapExtensionCommandBuilder::instance()->examine('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('EXAMINE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' EXAMINE drafts', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testCreate() + { + $object = PhpImapExtensionCommandBuilder::instance()->create('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('CREATE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' CREATE drafts', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testDelete() + { + $object = PhpImapExtensionCommandBuilder::instance()->delete('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('DELETE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' DELETE drafts', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testSubscribe() + { + $object = PhpImapExtensionCommandBuilder::instance()->subscribe('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('SUBSCRIBE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' SUBSCRIBE drafts', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testUnsubscribe() + { + $object = PhpImapExtensionCommandBuilder::instance()->unsubscribe('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('UNSUBSCRIBE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' UNSUBSCRIBE drafts', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testListMailbox() + { + $object = PhpImapExtensionCommandBuilder::instance()->listMailbox()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LIST', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('REFERENCE_NAME' => '', 'MAILBOX' => '*'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LIST "" "*"', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testListSubsetMailbox() + { + $object = PhpImapExtensionCommandBuilder::instance()->listSubsetMailbox()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LSUB', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('REFERENCE_NAME' => '', 'MAILBOX' => '*'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LSUB "" "*"', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testRename() + { + $object = PhpImapExtensionCommandBuilder::instance()->rename('INVOICES', 'RECEIPTS')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('RENAME', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'INVOICES', 'NEW_MAILBOX' => 'RECEIPTS'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' RENAME INVOICES RECEIPTS', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testClose() + { + $object = PhpImapExtensionCommandBuilder::instance()->close()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('CLOSE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' CLOSE', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testExpunge() + { + $object = PhpImapExtensionCommandBuilder::instance()->expunge()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('EXPUNGE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' EXPUNGE', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testAppend() + { + $object = PhpImapExtensionCommandBuilder::instance()->append('IN')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('APPEND', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MAILBOX' => 'IN', + 'FLAGS' => array(), + 'DATE' => '', + 'MESSAGE' => '{0}' . "\r\n\r\n" + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' APPEND ' . '{0}' . "\r\n\r\n", self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testWithFlag() + { + $object = PhpImapExtensionCommandBuilder::instance()->append('IN')->withFlag('Seen')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('APPEND', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MAILBOX' => 'IN', + 'FLAGS' => array('Seen'), + 'DATE' => '', + 'MESSAGE' => '{0}' . "\r\n\r\n" + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' APPEND ' . '(Seen) {0}' . "\r\n\r\n", self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + + public function testWithDate() + { + $date = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2018-01-01 1:00:00'); + $object = PhpImapExtensionCommandBuilder::instance()->append('IN')->withFlag('Seen')->withDateTime($date)->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('APPEND', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MAILBOX' => 'IN', + 'FLAGS' => array('Seen'), + 'DATE' => 'Mon, 01 Jan 2018 01:00:00 +0000', + 'MESSAGE' => '{0}' . "\r\n\r\n" + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' APPEND (Seen) Mon, 01 Jan 2018 01:00:00 +0000 ' . '{0}' . "\r\n\r\n", self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testWithMessage() + { + $message = ''; + $message .= "{310}\r\n"; + $message .= "Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)\r\n"; + $message .= "From: Fred Foobar <foobar@Blurdybloop.COM>\r\n"; + $message .= "Subject: afternoon meeting\r\n"; + $message .= "To: mooch@owatagu.siam.edu\r\n"; + $message .= "Message-Id: <B27397-0100000@Blurdybloop.COM>\r\n"; + $message .= "MIME-Version: 1.0\r\n"; + $message .= "Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\r\n"; + $message .= "Hello Joe, do you think we can meet at 3:30 tomorrow?\r\n\r\n"; + + $date = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2018-01-01 1:00:00'); + $object = PhpImapExtensionCommandBuilder::instance()->append('IN')->withFlag('Seen')->withDateTime($date)->withMessage($message)->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('APPEND', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MAILBOX' => 'IN', + 'FLAGS' => array('Seen'), + 'DATE' => 'Mon, 01 Jan 2018 01:00:00 +0000', + 'MESSAGE' => $message + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' APPEND (Seen) Mon, 01 Jan 2018 01:00:00 +0000 ' . $message, self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testCheck() + { + $object = PhpImapExtensionCommandBuilder::instance()->check()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('CHECK', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' CHECK', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testCopy() + { + $object = PhpImapExtensionCommandBuilder::instance()->copy()->withRange(1, 2)->toMailbox('IN')->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('COPY', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MESSAGE' => '1:2', 'MAILBOX' => 'IN'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' COPY 1:2 IN', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + + $object = PhpImapExtensionCommandBuilder::instance()->uid()->copy()->withRange(1, 2)->toMailbox('IN')->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('UID', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'COPY' => array('MESSAGE' => '1:2', 'MAILBOX' => 'IN') + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' UID COPY 1:2 IN', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function withRange() + { + $object = PhpImapExtensionCommandBuilder::instance()->copy()->withRange(1, 2)->toMailbox('IN')->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('COPY', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MESSAGE' => '1:2', 'MAILBOX' => 'IN'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' COPY', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function toMailBox() + { + $object = PhpImapExtensionCommandBuilder::instance()->copy()->withRange(1, 2)->toMailbox('IN')->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('COPY', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MESSAGE' => '1:2', 'MAILBOX' => 'IN'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' COPY', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testSearch() + { + $date = new DateTimeImmutable(); + $date = $date->setDate(2018, 8, 29); + + $messageList = new \SalesAgility\Imap\Response\MessageList(); + $message1 = new \SalesAgility\Imap\Response\Message(); + $message1->offsetSet('number', '1'); + $message1->offsetSet('uid', '1'); + $message3 = new \SalesAgility\Imap\Response\Message(); + $message3->offsetSet('number', '3'); + $message3->offsetSet('uid', '3'); + $messageList[] = $message1; + $messageList[] = $message3; + + $searchCommand = PhpImapExtensionCommandBuilder::instance() + ->search() + ->withSequence($messageList) + ->withRange(1, 2) + ->searchAll() + ->searchAnswered() + ->searchBcc('TestBcc') + ->searchBefore($date) + ->searchBody('TestBody') + ->searchCc('TestCc') + ->searchDeleted() + ->searchDraft() + ->searchFlagged() + ->searchFrom('TestFrom') + ->searchHeader('TestHeaderName', 'TestHeaderValue') + ->searchKeyword('test') + ->searchLarger(1000) + ->searchNew() + ->searchNot() + ->searchOld() + ->searchOn($date) + ->searchOr() + ->searchRecent() + ->searchSeen() + ->searchSentBefore($date) + ->searchSentOn($date) + ->searchSentSince($date) + ->searchSince($date) + ->searchSmaller(1000) + ->searchSubject('TestSubject') + ->searchText('TestText') + ->searchTo('TestTo') + ->searchUid('TestUid') + ->searchUnanswered() + ->searchUndeleted() + ->searchUnflagged() + ->searchUnkeyword('TestUnkeyword') + ->searchUnseen() + ->searchUndraft() + ->build(); + + + $expectedArguments = array( + '1 3', + '1:2', + 'ALL', + 'ANSWERED', + 'BCC', + '"TestBcc"', + 'BEFORE', + '29-Aug-2018', + 'BODY', + '"TestBody"', + 'CC', + '"TestCc"', + 'DELETED', + 'DRAFT', + 'FLAGGED', + 'FROM', + '"TestFrom"', + 'HEADER', + 'TestHeaderName:"TestHeaderValue"', + 'KEYWORD', + '"test"', + 'LARGER', + 1000, + 'NEW', + 'NOT', + 'OLD', + 'ON', + '29-Aug-2018', + 'OR', + 'RECENT', + 'SEEN', + 'SENTBEFORE', + '29-Aug-2018', + 'SENTON', + '29-Aug-2018', + 'SENTSINCE', + '29-Aug-2018', + 'SINCE', + '29-Aug-2018', + 'SMALLER', + 1000, + 'SUBJECT', + '"TestSubject"', + 'TEXT', + '"TestText"', + 'TEXT', + '"TestTo"', + 'UID', + 'TestUid', + 'UNANSWERED', + 'UNDELETED', + 'UNFLAGGED', + 'UNKEYWORD', + '"TestUnkeyword"', + 'UNSEEN', + 'UNDRAFT', + + ); + + $actualArguments = $searchCommand->commandArguments(); + + $expectedString = ' SEARCH 1 3 1:2 ALL ANSWERED BCC "TestBcc" BEFORE 29-Aug-2018 BODY "TestBody" CC "TestCc" DELETED DRAFT FLAGGED FROM "TestFrom" HEADER TestHeaderName:"TestHeaderValue" KEYWORD "test" LARGER 1000 NEW NOT OLD ON 29-Aug-2018 OR RECENT SEEN SENTBEFORE 29-Aug-2018 SENTON 29-Aug-2018 SENTSINCE 29-Aug-2018 SINCE 29-Aug-2018 SMALLER 1000 SUBJECT "TestSubject" TEXT "TestText" TEXT "TestTo" UID TestUid UNANSWERED UNDELETED UNFLAGGED UNKEYWORD "TestUnkeyword" UNSEEN UNDRAFT'; + $actualstring = $searchCommand->asString(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($searchCommand)); + $this->assertEquals($expectedString, $actualstring); + $this->assertEquals($expectedArguments, $actualArguments); + + + $searchUidCommand = PhpImapExtensionCommandBuilder::instance() + ->uid() + ->search() + ->withSequence($messageList) + ->withRange(1, 2) + ->searchAll() + ->searchAnswered() + ->searchBcc('TestBcc') + ->searchBefore($date) + ->searchBody('TestBody') + ->searchCc('TestCc') + ->searchDeleted() + ->searchDraft() + ->searchFlagged() + ->searchFrom('TestFrom') + ->searchHeader('TestHeaderName', 'TestHeaderValue') + ->searchKeyword('test') + ->searchLarger(1000) + ->searchNew() + ->searchNot() + ->searchOld() + ->searchOn($date) + ->searchOr() + ->searchRecent() + ->searchSeen() + ->searchSentBefore($date) + ->searchSentOn($date) + ->searchSentSince($date) + ->searchSince($date) + ->searchSmaller(1000) + ->searchSubject('TestSubject') + ->searchText('TestText') + ->searchTo('TestTo') + ->searchUid('TestUid') + ->searchUnanswered() + ->searchUndeleted() + ->searchUnflagged() + ->searchUnkeyword('TestUnkeyword') + ->searchUnseen() + ->searchUndraft() + ->build(); + + $expectedArguments = array( + "SEARCH" => array( + '1 3', + '1:2', + 'ALL', + 'ANSWERED', + 'BCC', + '"TestBcc"', + 'BEFORE', + '29-Aug-2018', + 'BODY', + '"TestBody"', + 'CC', + '"TestCc"', + 'DELETED', + 'DRAFT', + 'FLAGGED', + 'FROM', + '"TestFrom"', + 'HEADER', + 'TestHeaderName:"TestHeaderValue"', + 'KEYWORD', + '"test"', + 'LARGER', + 1000, + 'NEW', + 'NOT', + 'OLD', + 'ON', + '29-Aug-2018', + 'OR', + 'RECENT', + 'SEEN', + 'SENTBEFORE', + '29-Aug-2018', + 'SENTON', + '29-Aug-2018', + 'SENTSINCE', + '29-Aug-2018', + 'SINCE', + '29-Aug-2018', + 'SMALLER', + 1000, + 'SUBJECT', + '"TestSubject"', + 'TEXT', + '"TestText"', + 'TEXT', + '"TestTo"', + 'UID', + 'TestUid', + 'UNANSWERED', + 'UNDELETED', + 'UNFLAGGED', + 'UNKEYWORD', + '"TestUnkeyword"', + 'UNSEEN', + 'UNDRAFT', + ) + ); + + $actualArguments = $searchUidCommand->commandArguments(); + $expectedString = ' UID SEARCH 1 3 1:2 ALL ANSWERED BCC "TestBcc" BEFORE 29-Aug-2018 BODY "TestBody" CC "TestCc" DELETED DRAFT FLAGGED FROM "TestFrom" HEADER TestHeaderName:"TestHeaderValue" KEYWORD "test" LARGER 1000 NEW NOT OLD ON 29-Aug-2018 OR RECENT SEEN SENTBEFORE 29-Aug-2018 SENTON 29-Aug-2018 SENTSINCE 29-Aug-2018 SINCE 29-Aug-2018 SMALLER 1000 SUBJECT "TestSubject" TEXT "TestText" TEXT "TestTo" UID TestUid UNANSWERED UNDELETED UNFLAGGED UNKEYWORD "TestUnkeyword" UNSEEN UNDRAFT'; + $actualstring = $searchUidCommand->asString(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($searchUidCommand)); + $this->assertEquals($expectedString, $actualstring); + $this->assertEquals($expectedArguments, $actualArguments); + } + + public function testStatus() + { + $statusCommand = PhpImapExtensionCommandBuilder::instance() + ->status('IN') + ->withRecent() + ->withUidNext() + ->withUidValidity() + ->withUnseen() + ->withMessages() + ->build(); + + $expectedArguments = array( + 'MAILBOX' => 'IN', + 'INCLUDE' => Array( + "RECENT", + "UIDNEXT", + "UIDVALIDITY", + "UNSEEN", + "MESSAGES" + ) + ); + $actualArguments = $statusCommand->commandArguments(); + $expectedString = ' STATUS IN (RECENT UIDNEXT UIDVALIDITY UNSEEN MESSAGES)'; + $actualstring = $statusCommand->asString(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($statusCommand)); + $this->assertEquals($expectedString, $actualstring); + $this->assertEquals($expectedArguments, $actualArguments); + } + + public function testStore() + { + $statusCommand = PhpImapExtensionCommandBuilder::instance() + ->store() + ->withRange(1, 2) + ->addFlag('Answered') + ->removeFlag('Seen') + ->replaceFlag('Custom') + ->build(); + + $expectedArguments = array( + 'MESSAGE' => '1:2', + '+FLAGS' => + array( + 0 => '\Answered', + ), + '-FLAGS' => + array( + 0 => '\Seen', + ), + 'FLAGS' => + array( + 0 => '\Custom', + ), + ); + + $actualArguments = $statusCommand->commandArguments(); + $expectedString = ' STORE 1:2 +FLAGS.SILENT (\Answered) -FLAGS.SILENT (\Seen) FLAGS.SILENT (\Custom)'; + $actualstring = $statusCommand->asString(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($statusCommand)); + $this->assertEquals($expectedString, $actualstring); + $this->assertEquals($expectedArguments, $actualArguments); + + $statusUidCommand = PhpImapExtensionCommandBuilder::instance() + ->uid() + ->store() + ->withRange(1, 2) + ->addFlag('Answered') + ->removeFlag('Seen') + ->replaceFlag('Custom') + ->build(); + + $expectedArguments = array( + 'STORE' => array( + 'MESSAGE' => '1:2', + '+FLAGS' => + array( + 0 => '\Answered', + ), + '-FLAGS' => + array( + 0 => '\Seen', + ), + 'FLAGS' => + array( + 0 => '\Custom', + ), + ) + ); + $actualArguments = $statusUidCommand->commandArguments(); + $expectedString = ' UID STORE 1:2 +FLAGS.SILENT (\Answered) -FLAGS.SILENT (\Seen) FLAGS.SILENT (\Custom)'; + $actualstring = $statusUidCommand->asString(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($statusUidCommand)); + $this->assertEquals($expectedString, $actualstring); + $this->assertEquals($expectedArguments, $actualArguments); + } + +} diff --git a/tests/unit/SalesAgility/Imap/CommandBuilder/PimapCommandBuilderTest.php b/tests/unit/SalesAgility/Imap/CommandBuilder/PimapCommandBuilderTest.php new file mode 100755 index 0000000..090128a --- /dev/null +++ b/tests/unit/SalesAgility/Imap/CommandBuilder/PimapCommandBuilderTest.php @@ -0,0 +1,1086 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\CommandBuilder\PimapCommandBuilder; + +class PimapCommandBuilderTest extends \Codeception\Test\Unit +{ + + /** @var ReflectionClass */ + protected static $reflection; + /** @var ReflectionProperty */ + protected static $propertyCommandPrefix; + /** @var ReflectionProperty */ + protected static $propertyCommand; + /** @var ReflectionProperty */ + protected static $propertyArguments; + /** @var ReflectionProperty */ + protected static $propertyAsString; + /** @var ReflectionProperty */ + protected static $propertyIsValidated; + /** @var ReflectionProperty */ + protected static $propertyValidators; + /** @var ReflectionProperty */ + protected static $propertyIsRaw; + + protected function _before() + { + if(self::$reflection === null) { + // we will need these to check all the properties + // lets just declare it once. + // It will help keep this class as small as possible. + // It will make it a little easier to read. + self::$reflection = new \ReflectionClass(PimapCommandBuilder::class); + + self::$propertyCommandPrefix = self::$reflection->getProperty('commandPrefix'); + self::$propertyCommand = self::$reflection->getProperty('command'); + self::$propertyArguments = self::$reflection->getProperty('arguments'); + self::$propertyAsString = self::$reflection->getProperty('asString'); + self::$propertyIsValidated = self::$reflection->getProperty('isValidated'); + self::$propertyValidators = self::$reflection->getProperty('validators'); + self::$propertyIsRaw = self::$reflection->getProperty('isRaw'); + + self::$propertyCommandPrefix->setAccessible(true); + self::$propertyCommand->setAccessible(true); + self::$propertyArguments->setAccessible(true); + self::$propertyAsString->setAccessible(true); + self::$propertyIsValidated->setAccessible(true); + self::$propertyValidators->setAccessible(true); + self::$propertyIsRaw->setAccessible(true); + } + } + + // Constructor + public function testInstance() + { + $object = PimapCommandBuilder::instance(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals('', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + // Command Builder Util Functions + public function testUntagged() + { + $object = PimapCommandBuilder::instance()->raw('test')->build()->untagged(); + + $this->assertEquals('*', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('test', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' test', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(true, self::$propertyIsRaw->getValue($object)); + } + + public function testTagged() + { + $object = PimapCommandBuilder::instance()->raw('test')->build()->tagged('A1'); + + $this->assertEquals('A1', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('test', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' test', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(true, self::$propertyIsRaw->getValue($object)); + } + + public function testBuild() + { + $object = PimapCommandBuilder::instance(); + $validator = new \SalesAgility\Imap\CommandBuilder\CommandValidator\Command\NoopCommandValidator(); + $object->addCommandValidator( + $validator + ); + $object = $object->noop()->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('NOOP', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' NOOP', self::$propertyAsString->getValue($object)); + $this->assertEquals(true, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array('NOOP' => $validator), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testCommand() + { + $object = PimapCommandBuilder::instance()->raw('test')->build()->command(); + $this->assertEquals('test', $object); + } + + public function testCommandArguments() + { + $object = PimapCommandBuilder::instance()->login()->user('user')->password('password'); + $expect = array( + 'USER' => 'user', + 'PASSWORD' => 'password' + ); + $this->assertEquals($expect, $object->build()->commandArguments()); + } + + public function testAddCommandValidator() + { + $object = PimapCommandBuilder::instance(); + $validator = new \SalesAgility\Imap\CommandBuilder\CommandValidator\Command\NoopCommandValidator(); + $object->addCommandValidator( + $validator + ); + + $this->assertEquals(array('NOOP' => $validator), self::$propertyValidators->getValue($object)); + } + + public function testCommandPrefix() + { + $object = PimapCommandBuilder::instance()->raw('test')->build()->untagged(); + $this->assertEquals('*', $object->commandPrefix()); + } + + public function testAsString() + { + $object = PimapCommandBuilder::instance()->raw('test')->build()->untagged(); + $this->assertEquals('* test', $object->asString()); + } + + public function testAsArray() + { + $object = PimapCommandBuilder::instance()->raw('test')->build(); + $this->assertEquals(array( + 'command' => 'test', + 'argument' => array(), + 'validated' => false, + 'raw' => true, + ), $object->asArray()); + } + + // Commands && Arguments + public function testRaw() + { + $object = PimapCommandBuilder::instance()->raw('test'); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('test', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' test', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(true, self::$propertyIsRaw->getValue($object)); + } + + public function testNoop() + { + $object = PimapCommandBuilder::instance()->noop(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('NOOP', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' NOOP', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testUid() + { + $object = PimapCommandBuilder::instance()->uid()->fetch(1)->header()->body()->flags()->uids()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('UID', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'FETCH' => array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE', + 'BODY[TEXT]', + 'FLAGS', + 'UID' + ) + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' UID FETCH 1 (BODY[HEADER] BODYSTRUCTURE BODY[TEXT] FLAGS UID)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testFetch() + { + $object = PimapCommandBuilder::instance()->fetch(1)->header()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1 (BODY[HEADER] BODYSTRUCTURE)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testFetchRange() + { + $object = PimapCommandBuilder::instance()->fetchRange(1, 2)->header()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => '1:2', + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1:2 (BODY[HEADER] BODYSTRUCTURE)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testFetchSet() + { + $messageList = new \SalesAgility\Imap\Response\MessageList(); + + $message = new \SalesAgility\Imap\Response\Message(); + $message->offsetSet('number', '1'); + $message->offsetSet('uid', '1'); + $messageList[] = $message; + + $message = new \SalesAgility\Imap\Response\Message(); + $message->offsetSet('number', '3'); + $message->offsetSet('uid', '3'); + $messageList[] = $message; + + $object = PimapCommandBuilder::instance()->fetchSet($messageList)->header()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => '1,3', + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1,3 (BODY[HEADER] BODYSTRUCTURE)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + + + $object = PimapCommandBuilder::instance()->uid()->fetchSet($messageList)->header()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('UID', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'FETCH' => array( + 'MESSAGE' => '1,3', + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE' + ) + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' UID FETCH 1,3 (BODY[HEADER] BODYSTRUCTURE)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testUids() + { + $object = PimapCommandBuilder::instance()->fetch(1)->uids()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'UID' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1 (UID)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + + $object = PimapCommandBuilder::instance()->uid()->fetchRange(1,2)->header()->body()->flags()->uids()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('UID', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'FETCH' => array( + 'MESSAGE' => '1:2', + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE', + 'BODY[TEXT]', + 'FLAGS', + 'UID' + ) + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' UID FETCH 1:2 (BODY[HEADER] BODYSTRUCTURE BODY[TEXT] FLAGS UID)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + + public function testFlags() + { + $object = PimapCommandBuilder::instance()->fetch(1)->flags()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'FLAGS' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1 (FLAGS)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testHeader() + { + $object = PimapCommandBuilder::instance()->fetch(1)->header()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'BODY[HEADER] BODYSTRUCTURE' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1 (BODY[HEADER] BODYSTRUCTURE)', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testBody() + { + $object = PimapCommandBuilder::instance()->fetch(1)->body()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('FETCH', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MESSAGE' => 1, + 'FIELDS' => array( + 'BODY[TEXT]' + ) + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' FETCH 1 (BODY[TEXT])', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + + public function testX() + { + $object = PimapCommandBuilder::instance()->x(); + $this->assertInstanceOf(PimapCommandBuilder::class, $object); + } + + public function testSelect() + { + $object = PimapCommandBuilder::instance()->select('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('SELECT', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' SELECT "drafts"', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testExamine() + { + $object = PimapCommandBuilder::instance()->examine('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('EXAMINE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' EXAMINE drafts', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testCreate() + { + $object = PimapCommandBuilder::instance()->create('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('CREATE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' CREATE drafts', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testDelete() + { + $object = PimapCommandBuilder::instance()->delete('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('DELETE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' DELETE drafts', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testSubscribe() + { + $object = PimapCommandBuilder::instance()->subscribe('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('SUBSCRIBE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' SUBSCRIBE drafts', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testUnsubscribe() + { + $object = PimapCommandBuilder::instance()->unsubscribe('drafts')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('UNSUBSCRIBE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX' => 'drafts'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' UNSUBSCRIBE drafts', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testListMailbox() + { + $object = PimapCommandBuilder::instance()->listMailbox()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LIST', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('REFERENCE_NAME'=> '', 'MAILBOX' => '*'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LIST "" "*"', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testListSubsetMailbox() + { + $object = PimapCommandBuilder::instance()->listSubsetMailbox()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LSUB', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('REFERENCE_NAME'=> '', 'MAILBOX' => '*'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LSUB "" "*"', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testRename() + { + $object = PimapCommandBuilder::instance()->rename('INVOICES', 'RECEIPTS')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('RENAME', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MAILBOX'=> 'INVOICES', 'NEW_MAILBOX' => 'RECEIPTS'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' RENAME INVOICES RECEIPTS', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testCapability() + { + $object = PimapCommandBuilder::instance()->capability()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('CAPABILITY', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' CAPABILITY', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testClose() + { + $object = PimapCommandBuilder::instance()->close()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('CLOSE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' CLOSE', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testExpunge() + { + $object = PimapCommandBuilder::instance()->expunge()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('EXPUNGE', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' EXPUNGE', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testAppend() + { + $object = PimapCommandBuilder::instance()->append('IN')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('APPEND', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MAILBOX' => 'IN', + 'FLAGS' => array(), + 'DATE' => '', + 'MESSAGE' => '{0}'."\r\n\r\n" + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' APPEND '. '{0}'."\r\n\r\n", self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testWithFlag() + { + $object = PimapCommandBuilder::instance()->append('IN')->withFlag('Seen')->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('APPEND', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MAILBOX' => 'IN', + 'FLAGS' => array('Seen'), + 'DATE' => '', + 'MESSAGE' => '{0}'."\r\n\r\n" + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' APPEND '.'(Seen) {0}'."\r\n\r\n", self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + + public function testWithDate() + { + $date = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2018-01-01 1:00:00'); + $object = PimapCommandBuilder::instance()->append('IN')->withFlag('Seen')->withDateTime($date)->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('APPEND', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MAILBOX' => 'IN', + 'FLAGS' => array('Seen'), + 'DATE' => 'Mon, 01 Jan 2018 01:00:00 +0000', + 'MESSAGE' => '{0}'."\r\n\r\n" + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' APPEND (Seen) Mon, 01 Jan 2018 01:00:00 +0000 '.'{0}'."\r\n\r\n", self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testWithMessage() + { + $message = ''; + $message .= "{310}\r\n"; + $message .= "Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)\r\n"; + $message .= "From: Fred Foobar <foobar@Blurdybloop.COM>\r\n"; + $message .= "Subject: afternoon meeting\r\n"; + $message .= "To: mooch@owatagu.siam.edu\r\n"; + $message .= "Message-Id: <B27397-0100000@Blurdybloop.COM>\r\n"; + $message .= "MIME-Version: 1.0\r\n"; + $message .= "Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\r\n"; + $message .= "Hello Joe, do you think we can meet at 3:30 tomorrow?\r\n\r\n"; + + $date = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2018-01-01 1:00:00'); + $object = PimapCommandBuilder::instance()->append('IN')->withFlag('Seen')->withDateTime($date)->withMessage($message)->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('APPEND', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'MAILBOX' => 'IN', + 'FLAGS' => array('Seen'), + 'DATE' => 'Mon, 01 Jan 2018 01:00:00 +0000', + 'MESSAGE' => $message + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' APPEND (Seen) Mon, 01 Jan 2018 01:00:00 +0000 '.$message, self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testCheck() + { + $object = PimapCommandBuilder::instance()->check()->build(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('CHECK', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' CHECK', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testLogout() + { + $object = PimapCommandBuilder::instance()->logout()->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LOGOUT', self::$propertyCommand->getValue($object)); + $this->assertEquals(array(), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LOGOUT', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testCopy() + { + $object = PimapCommandBuilder::instance()->copy()->withRange(1, 2)->toMailbox('IN')->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('COPY', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MESSAGE' => '1:2', 'MAILBOX' => 'IN'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' COPY 1:2 IN', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + + $object = PimapCommandBuilder::instance()->uid()->copy()->withRange(1, 2)->toMailbox('IN')->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('UID', self::$propertyCommand->getValue($object)); + $this->assertEquals(array( + 'COPY' => array('MESSAGE' => '1:2', 'MAILBOX' => 'IN') + ), self::$propertyArguments->getValue($object)); + $this->assertEquals(' UID COPY 1:2 IN', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function withRange() + { + $object = PimapCommandBuilder::instance()->copy()->withRange(1, 2)->toMailbox('IN')->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('COPY', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MESSAGE' => '1:2', 'MAILBOX' => 'IN'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' COPY', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function toMailBox() + { + $object = PimapCommandBuilder::instance()->copy()->withRange(1, 2)->toMailbox('IN')->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('COPY', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('MESSAGE' => '1:2', 'MAILBOX' => 'IN'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' COPY', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + + public function testLogin() + { + $object = PimapCommandBuilder::instance()->login()->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LOGIN', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('USER' => '', 'PASSWORD' => ''), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LOGIN', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testUser() + { + $object = PimapCommandBuilder::instance()->login()->user('username')->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LOGIN', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('USER' => 'username', 'PASSWORD' => ''), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LOGIN username', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testPassword() + { + $object = PimapCommandBuilder::instance()->login()->user('username')->password('password')->build(); + + $this->assertEquals('', self::$propertyCommandPrefix->getValue($object)); + $this->assertEquals('LOGIN', self::$propertyCommand->getValue($object)); + $this->assertEquals(array('USER' => 'username', 'PASSWORD' => 'password'), self::$propertyArguments->getValue($object)); + $this->assertEquals(' LOGIN username password', self::$propertyAsString->getValue($object)); + $this->assertEquals(false, self::$propertyIsValidated->getValue($object)); + $this->assertEquals(array(), self::$propertyValidators->getValue($object)); + $this->assertEquals(false, self::$propertyIsRaw->getValue($object)); + } + + public function testSearch() + { + $date = new DateTimeImmutable(); + $date = $date->setDate(2018, 8, 29); + + $messageList = new \SalesAgility\Imap\Response\MessageList(); + $message1 = new \SalesAgility\Imap\Response\Message(); + $message1->offsetSet('number', '1'); + $message1->offsetSet('uid', '1'); + $message3 = new \SalesAgility\Imap\Response\Message(); + $message3->offsetSet('number', '3'); + $message3->offsetSet('uid', '3'); + $messageList[] = $message1; + $messageList[] = $message3; + + $searchCommand = PimapCommandBuilder::instance() + ->search() + ->withSequence($messageList) + ->withRange(1,2) + ->searchAll() + ->searchAnswered() + ->searchBcc('TestBcc') + ->searchBefore($date) + ->searchBody('TestBody') + ->searchCc('TestCc') + ->searchDeleted() + ->searchDraft() + ->searchFlagged() + ->searchFrom('TestFrom') + ->searchHeader('TestHeaderName', 'TestHeaderValue') + ->searchKeyword('test') + ->searchLarger(1000) + ->searchNew() + ->searchNot() + ->searchOld() + ->searchOn($date) + ->searchOr() + ->searchRecent() + ->searchSeen() + ->searchSentBefore($date) + ->searchSentOn($date) + ->searchSentSince($date) + ->searchSince($date) + ->searchSmaller(1000) + ->searchSubject('TestSubject') + ->searchText('TestText') + ->searchTo('TestTo') + ->searchUid('TestUid') + ->searchUnanswered() + ->searchUndeleted() + ->searchUnflagged() + ->searchUnkeyword('TestUnkeyword') + ->searchUnseen() + ->searchUndraft() + ->build(); + + + $expectedArguments = array ( + '1 3', + '1:2', + 'ALL', + 'ANSWERED', + 'BCC', + '"TestBcc"', + 'BEFORE', + '29-Aug-2018', + 'BODY', + '"TestBody"', + 'CC', + '"TestCc"', + 'DELETED', + 'DRAFT', + 'FLAGGED', + 'FROM', + '"TestFrom"', + 'HEADER', + 'TestHeaderName:"TestHeaderValue"', + 'KEYWORD', + '"test"', + 'LARGER', + 1000, + 'NEW', + 'NOT', + 'OLD', + 'ON', + '29-Aug-2018', + 'OR', + 'RECENT', + 'SEEN', + 'SENTBEFORE', + '29-Aug-2018', + 'SENTON', + '29-Aug-2018', + 'SENTSINCE', + '29-Aug-2018', + 'SINCE', + '29-Aug-2018', + 'SMALLER', + 1000, + 'SUBJECT', + '"TestSubject"', + 'TEXT', + '"TestText"', + 'TEXT', + '"TestTo"', + 'UID', + 'TestUid', + 'UNANSWERED', + 'UNDELETED', + 'UNFLAGGED', + 'UNKEYWORD', + '"TestUnkeyword"', + 'UNSEEN', + 'UNDRAFT', + + ); + + $actualArguments = $searchCommand->commandArguments(); + + $expectedString = ' SEARCH 1 3 1:2 ALL ANSWERED BCC "TestBcc" BEFORE 29-Aug-2018 BODY "TestBody" CC "TestCc" DELETED DRAFT FLAGGED FROM "TestFrom" HEADER TestHeaderName:"TestHeaderValue" KEYWORD "test" LARGER 1000 NEW NOT OLD ON 29-Aug-2018 OR RECENT SEEN SENTBEFORE 29-Aug-2018 SENTON 29-Aug-2018 SENTSINCE 29-Aug-2018 SINCE 29-Aug-2018 SMALLER 1000 SUBJECT "TestSubject" TEXT "TestText" TEXT "TestTo" UID TestUid UNANSWERED UNDELETED UNFLAGGED UNKEYWORD "TestUnkeyword" UNSEEN UNDRAFT'; + $actualstring = $searchCommand->asString(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($searchCommand)); + $this->assertEquals($expectedString, $actualstring); + $this->assertEquals($expectedArguments, $actualArguments); + + + $searchUidCommand = PimapCommandBuilder::instance() + ->uid() + ->search() + ->withSequence($messageList) + ->withRange(1,2) + ->searchAll() + ->searchAnswered() + ->searchBcc('TestBcc') + ->searchBefore($date) + ->searchBody('TestBody') + ->searchCc('TestCc') + ->searchDeleted() + ->searchDraft() + ->searchFlagged() + ->searchFrom('TestFrom') + ->searchHeader('TestHeaderName', 'TestHeaderValue') + ->searchKeyword('test') + ->searchLarger(1000) + ->searchNew() + ->searchNot() + ->searchOld() + ->searchOn($date) + ->searchOr() + ->searchRecent() + ->searchSeen() + ->searchSentBefore($date) + ->searchSentOn($date) + ->searchSentSince($date) + ->searchSince($date) + ->searchSmaller(1000) + ->searchSubject('TestSubject') + ->searchText('TestText') + ->searchTo('TestTo') + ->searchUid('TestUid') + ->searchUnanswered() + ->searchUndeleted() + ->searchUnflagged() + ->searchUnkeyword('TestUnkeyword') + ->searchUnseen() + ->searchUndraft() + ->build(); + + $expectedArguments = array ( + "SEARCH" => array( + '1 3', + '1:2', + 'ALL', + 'ANSWERED', + 'BCC', + '"TestBcc"', + 'BEFORE', + '29-Aug-2018', + 'BODY', + '"TestBody"', + 'CC', + '"TestCc"', + 'DELETED', + 'DRAFT', + 'FLAGGED', + 'FROM', + '"TestFrom"', + 'HEADER', + 'TestHeaderName:"TestHeaderValue"', + 'KEYWORD', + '"test"', + 'LARGER', + 1000, + 'NEW', + 'NOT', + 'OLD', + 'ON', + '29-Aug-2018', + 'OR', + 'RECENT', + 'SEEN', + 'SENTBEFORE', + '29-Aug-2018', + 'SENTON', + '29-Aug-2018', + 'SENTSINCE', + '29-Aug-2018', + 'SINCE', + '29-Aug-2018', + 'SMALLER', + 1000, + 'SUBJECT', + '"TestSubject"', + 'TEXT', + '"TestText"', + 'TEXT', + '"TestTo"', + 'UID', + 'TestUid', + 'UNANSWERED', + 'UNDELETED', + 'UNFLAGGED', + 'UNKEYWORD', + '"TestUnkeyword"', + 'UNSEEN', + 'UNDRAFT', + ) + ); + + $actualArguments = $searchUidCommand->commandArguments(); + $expectedString = ' UID SEARCH 1 3 1:2 ALL ANSWERED BCC "TestBcc" BEFORE 29-Aug-2018 BODY "TestBody" CC "TestCc" DELETED DRAFT FLAGGED FROM "TestFrom" HEADER TestHeaderName:"TestHeaderValue" KEYWORD "test" LARGER 1000 NEW NOT OLD ON 29-Aug-2018 OR RECENT SEEN SENTBEFORE 29-Aug-2018 SENTON 29-Aug-2018 SENTSINCE 29-Aug-2018 SINCE 29-Aug-2018 SMALLER 1000 SUBJECT "TestSubject" TEXT "TestText" TEXT "TestTo" UID TestUid UNANSWERED UNDELETED UNFLAGGED UNKEYWORD "TestUnkeyword" UNSEEN UNDRAFT'; + $actualstring = $searchUidCommand->asString(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($searchUidCommand)); + $this->assertEquals($expectedString, $actualstring); + $this->assertEquals($expectedArguments, $actualArguments); + } + + public function testStatus() + { + $statusCommand = PimapCommandBuilder::instance() + ->status('IN') + ->withRecent() + ->withUidNext() + ->withUidValidity() + ->withUnseen() + ->withMessages() + ->build(); + + $expectedArguments = array( + 'MAILBOX' => 'IN', + 'INCLUDE' => Array ( + "RECENT" , + "UIDNEXT" , + "UIDVALIDITY" , + "UNSEEN" , + "MESSAGES" + ) + ); + $actualArguments = $statusCommand->commandArguments(); + $expectedString = ' STATUS IN (RECENT UIDNEXT UIDVALIDITY UNSEEN MESSAGES)'; + $actualstring = $statusCommand->asString(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($statusCommand)); + $this->assertEquals($expectedString, $actualstring); + $this->assertEquals($expectedArguments, $actualArguments); + } + + public function testStore() + { + $statusCommand = PimapCommandBuilder::instance() + ->store() + ->withRange(1,2) + ->addFlag('Answered') + ->removeFlag('Seen') + ->replaceFlag('Custom') + ->build(); + + $expectedArguments = array ( + 'MESSAGE' => '1:2', + '+FLAGS' => + array ( + 0 => '\Answered', + ), + '-FLAGS' => + array ( + 0 => '\Seen', + ), + 'FLAGS' => + array ( + 0 => '\Custom', + ), + ); + + $actualArguments = $statusCommand->commandArguments(); + $expectedString = ' STORE 1:2 +FLAGS.SILENT (\Answered) -FLAGS.SILENT (\Seen) FLAGS.SILENT (\Custom)'; + $actualstring = $statusCommand->asString(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($statusCommand)); + $this->assertEquals($expectedString, $actualstring); + $this->assertEquals($expectedArguments, $actualArguments); + + $statusUidCommand = PimapCommandBuilder::instance() + ->uid() + ->store() + ->withRange(1,2) + ->addFlag('Answered') + ->removeFlag('Seen') + ->replaceFlag('Custom') + ->build(); + + $expectedArguments = array ( + 'STORE' => array( + 'MESSAGE' => '1:2', + '+FLAGS' => + array ( + 0 => '\Answered', + ), + '-FLAGS' => + array ( + 0 => '\Seen', + ), + 'FLAGS' => + array ( + 0 => '\Custom', + ), + ) + ); + $actualArguments = $statusUidCommand->commandArguments(); + $expectedString = ' UID STORE 1:2 +FLAGS.SILENT (\Answered) -FLAGS.SILENT (\Seen) FLAGS.SILENT (\Custom)'; + $actualstring = $statusUidCommand->asString(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($statusUidCommand)); + $this->assertEquals($expectedString, $actualstring); + $this->assertEquals($expectedArguments, $actualArguments); + } + + public function testIdle() + { + $idleCommand = PimapCommandBuilder::instance() + ->idle() + ->build(); + $actualArguments = $idleCommand->commandArguments(); + $expectedString = ' IDLE'; + $actualstring = $idleCommand->asString(); + $this->assertEquals('', self::$propertyCommandPrefix->getValue($idleCommand)); + $this->assertEquals($expectedString, $actualstring); + $this->assertEquals(array(), $actualArguments); + } +} diff --git a/tests/unit/SalesAgility/Imap/CommandBuilder/Validator/NoopCommandValidatorTest.php b/tests/unit/SalesAgility/Imap/CommandBuilder/Validator/NoopCommandValidatorTest.php new file mode 100755 index 0000000..5876794 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/CommandBuilder/Validator/NoopCommandValidatorTest.php @@ -0,0 +1,57 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ +/** + * Created by PhpStorm. + * User: user + * Date: 7/9/18 + * Time: 3:58 PM + */ + +use SalesAgility\Imap\CommandBuilder\CommandValidator\Command\NoopCommandValidator; +use SalesAgility\Imap\CommandBuilder\PimapCommandBuilder; + +class NoopCommandValidatorTest extends \Codeception\Test\Unit +{ + /** + * @var UnitTester + */ + protected $tester; + + public function testValidate() + { + $object = new NoopCommandValidator(); + $this->assertEquals('NOOP', $object->command()); + } + + public function testCommand() + { + $object = new NoopCommandValidator(); + $command = PimapCommandBuilder::instance()->noop(); + $this->assertTrue($object->validate($command)); + + $this->tester->expectException( + new \Exception('NOOP does not take any arguments.'), + function () { + $object = new NoopCommandValidator(); + $command = PimapCommandBuilder::instance()->noop()->user('username'); + $object->validate($command); + } + ); + } +} diff --git a/tests/unit/SalesAgility/Imap/ImapExceptionTest.php b/tests/unit/SalesAgility/Imap/ImapExceptionTest.php new file mode 100755 index 0000000..a78a150 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/ImapExceptionTest.php @@ -0,0 +1,45 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\ImapException; +use SalesAgility\Imap\Enumerator\ResponseCode; + +class ImapExceptionTest extends \Codeception\Test\Unit +{ + public function testNoResponse() + { + $object = ImapException::NoResponse(''); + $this->assertEquals(ResponseCode::NO_RESPONSE, $object->getCode()); + $this->assertEquals('No response: ', $object->getMessage()); + } + + public function testMissingCommandPrefix() + { + $object = ImapException::MissingCommandPrefix(''); + $this->assertEquals(ImapException::COMMAND_MISSING_PREFIX, $object->getCode()); + $this->assertEquals('Missing Command Prefix: ', $object->getMessage()); + } + + public function testBadResponse() + { + $object = ImapException::BadResponse(''); + $this->assertEquals(ResponseCode::BAD_RESPONSE, $object->getCode()); + $this->assertEquals('Bad response: ', $object->getMessage()); + } +} diff --git a/tests/unit/SalesAgility/Imap/ImapManagerFactoryTest.php b/tests/unit/SalesAgility/Imap/ImapManagerFactoryTest.php new file mode 100755 index 0000000..5b0ffa3 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/ImapManagerFactoryTest.php @@ -0,0 +1,66 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\ManagerFactory; +use SalesAgility\Imap\Manager\PhpImapExtensionManager; +use SalesAgility\Imap\Manager\PimapManager; +use SalesAgility\Imap\Stream\MessageTransporter; +use SalesAgility\Imap\Stream\PhpImapExtensionMessageTransporter; +use SalesAgility\Stream\Connection; +use SalesAgility\Imap\Pipeline\Pipeline; +use SalesAgility\Imap\Stream\PhpImpExtensionConnection; + +class ImapManagerFactoryTest extends \Codeception\Test\Unit +{ + public function testInstance() + { + $object = ManagerFactory::instance(); + $this->assertTrue($object instanceof ManagerFactory); + } + + public function testHas() + { + $object = ManagerFactory::instance(); + $this->assertTrue($object->has(PimapManager::class)); + $this->assertTrue($object->has(Pipeline::class)); + $this->assertTrue($object->has(Connection::class)); + $this->assertTrue($object->has(MessageTransporter::class)); + $this->assertTrue($object->has(PhpImapExtensionManager::class)); + $this->assertTrue($object->has(PhpImpExtensionConnection::class)); + $this->assertTrue($object->has(PhpImapExtensionMessageTransporter::class)); + } + + public function testGet() + { + $object = ManagerFactory::instance(); + $this->assertTrue($object->get(PimapManager::class) instanceof PimapManager); + } + + public function testPhpManager() + { + $object = ManagerFactory::instance()->PimapManager(); + $this->assertTrue($object instanceof PimapManager); + } + + public function testNativePhpManager() + { + $object = ManagerFactory::instance()->PhpImapExtensionManager(); + $this->assertTrue($object instanceof PhpImapExtensionManager); + } +} diff --git a/tests/unit/SalesAgility/Imap/Interpreter/CapabilityInterpreterTest.php b/tests/unit/SalesAgility/Imap/Interpreter/CapabilityInterpreterTest.php new file mode 100755 index 0000000..7221d15 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Interpreter/CapabilityInterpreterTest.php @@ -0,0 +1,45 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Interpreter\CapabilityInterpreter; + +class CapabilityInterpreterTest extends \Codeception\Test\Unit +{ + + public function testParse() + { + $object = new CapabilityInterpreter(); + $file = file_get_contents(codecept_data_dir().'CAPABILITY.txt'); + $response = \SalesAgility\Iteration\StringIterator::withLiteral($file); + $actual = $object->parse($response); + $this->assertTrue($actual->offsetExists('IMAP4rev1')); + $this->assertTrue($actual->offsetExists('IDLE')); + $this->assertTrue($actual->offsetExists('AUTH=PLAIN')); + $this->assertFalse($actual->offsetExists('unsupported')); + + + $file = file_get_contents(codecept_data_dir().'PASSIVE_CAPABILITY.txt'); + $response = \SalesAgility\Iteration\StringIterator::withLiteral($file); + $actual = $object->parse($response); + $this->assertTrue($actual->offsetExists('IMAP4rev1')); + $this->assertTrue($actual->offsetExists('IDLE')); + $this->assertTrue($actual->offsetExists('AUTH=PLAIN')); + $this->assertFalse($actual->offsetExists('unsupported')); + } +} diff --git a/tests/unit/SalesAgility/Imap/Interpreter/LexemeInterpreterTest.php b/tests/unit/SalesAgility/Imap/Interpreter/LexemeInterpreterTest.php new file mode 100644 index 0000000..6c86a8f --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Interpreter/LexemeInterpreterTest.php @@ -0,0 +1,36 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Interpreter\LexemeInterpreter; + +class LexemeInterpreterTest extends \Codeception\Test\Unit +{ + public function testLastInGroup() + { + // test cases which are not covered by other tests + $response = \SalesAgility\Iteration\StringIterator::withLiteral('("test" ("test))'); + $tokenizer = new \SalesAgility\Imap\Token\Tokenizer(); + $leximizer = new \SalesAgility\Imap\Lexeme\Lexemizer(); + $interpreter = new LexemeInterpreter(); + $tokens = $tokenizer->parse($response); + $lexemes = $leximizer->parse($tokens); + $parsed = $interpreter->lastInGroup($lexemes); + $this->assertEquals(5, $parsed); + } +} diff --git a/tests/unit/SalesAgility/Imap/Interpreter/MailboxInterpreterTest.php b/tests/unit/SalesAgility/Imap/Interpreter/MailboxInterpreterTest.php new file mode 100755 index 0000000..1ea7a31 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Interpreter/MailboxInterpreterTest.php @@ -0,0 +1,46 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Interpreter\MailboxInterpreter; + +class MailboxInterpreterTest extends \Codeception\Test\Unit +{ + + public function testParse() + { + $object = new MailboxInterpreter(); + $file = file_get_contents(codecept_data_dir('SELECT_INBOX.txt')); + $response = \SalesAgility\Iteration\StringIterator::withLiteral($file); + $actual = $object->parse($response); + $expectedFlags = array( + "Answered", + "Flagged", + "Deleted", + "Seen", + "Draft", + ); + + $this->assertEquals($expectedFlags, $actual->flags()); + $this->assertEquals(74090, $actual->exists()); + $this->assertEquals(0, $actual->recent()); + $this->assertEquals(22, $actual->unseen()); + $this->assertEquals(1528185994, $actual->uidValidity()); + $this->assertEquals(74091, $actual->uidNext()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Interpreter/MailboxListInterpreterTest.php b/tests/unit/SalesAgility/Imap/Interpreter/MailboxListInterpreterTest.php new file mode 100755 index 0000000..5a214ed --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Interpreter/MailboxListInterpreterTest.php @@ -0,0 +1,41 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Interpreter\MailboxListInterpreter; + +class MailboxListInterpreterTest extends \Codeception\Test\Unit +{ + + public function testParse() + { + $object = new MailboxListInterpreter(); + $response = \SalesAgility\Iteration\StringIterator::withLiteral(file_get_contents(codecept_data_dir('LIST.txt'))); + $actual = $object->parse($response); + $this->assertCount(9, $actual); + $this->assertEquals('INBOX', $actual->offsetGet(0)->name()); + $this->assertEquals('[Gmail]', $actual->offsetGet(1)->name()); + $this->assertEquals('[Gmail]/All Mail', $actual->offsetGet(2)->name()); + $this->assertEquals('[Gmail]/Drafts', $actual->offsetGet(3)->name()); + $this->assertEquals('[Gmail]/Important', $actual->offsetGet(4)->name()); + $this->assertEquals('[Gmail]/Sent Mail', $actual->offsetGet(5)->name()); + $this->assertEquals('[Gmail]/Spam', $actual->offsetGet(6)->name()); + $this->assertEquals('[Gmail]/Sent Mail', $actual->offsetGet(7)->name()); + $this->assertEquals('[Gmail]/Trash', $actual->offsetGet(8)->name()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Interpreter/MessageInterpreterTest.php b/tests/unit/SalesAgility/Imap/Interpreter/MessageInterpreterTest.php new file mode 100755 index 0000000..20c3df0 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Interpreter/MessageInterpreterTest.php @@ -0,0 +1,227 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Interpreter\MessageInterpreter; +use SalesAgility\Iteration\StringIterator; + +class MessageInterpreterTest extends \Codeception\Test\Unit +{ + public function testParse() + { + // FETCH 1 (UID)\r\n + $object = new MessageInterpreter(); + $response = StringIterator::withLiteral("\x0D\x0A* 1 FETCH (UID 1000)\x0D\x0A"); + $actual = $object->parse($response); + $this->assertEquals('1000', $actual->offsetGet(0)->uid()); + $this->assertEquals('1', $actual->offsetGet(0)->number()); + // + $response = StringIterator::withLiteral("* 1 FETCH (UID 1000)\x0D\x0A"); + $actual = $object->parse($response); + $this->assertEquals('1000', $actual->offsetGet(0)->uid()); + $this->assertEquals('1', $actual->offsetGet(0)->number()); + + // Multiple Messages + $response = StringIterator::withLiteral("* 1 FETCH (UID 1000)\x0D\x0A* 2 FETCH (UID 2000)\x0D\x0A"); + $actual = $object->parse($response); + $this->assertEquals('1000', $actual->offsetGet(0)->uid()); + $this->assertEquals('1', $actual->offsetGet(0)->number()); + $this->assertEquals('2000', $actual->offsetGet(1)->uid()); + $this->assertEquals('2', $actual->offsetGet(1)->number()); + + // FETCH 1 (UID FLAGS) + $response = StringIterator::withLiteral("* 1 FETCH (UID 1000 FLAGS (\Seen \Answered))\x0D\x0A"); + $actual = $object->parse($response); + $this->assertEquals('1000', $actual->offsetGet(0)->uid()); + $this->assertEquals('1', $actual->offsetGet(0)->number()); + $this->assertTrue($actual->offsetGet(0)->flags()->isAnswered()); + $this->assertTrue($actual->offsetGet(0)->flags()->isSeen()); + $this->assertFalse($actual->offsetGet(0)->flags()->isDeleted()); + + // test mixing the order + $response = StringIterator::withLiteral("* 1 FETCH (FLAGS (\Seen \Answered) UID 1000)\x0D\x0A"); + $actual = $object->parse($response); + $this->assertEquals('1000', $actual->offsetGet(0)->uid()); + $this->assertEquals('1', $actual->offsetGet(0)->number()); + $this->assertTrue($actual->offsetGet(0)->flags()->isAnswered()); + $this->assertTrue($actual->offsetGet(0)->flags()->isSeen()); + $this->assertFalse($actual->offsetGet(0)->flags()->isDeleted()); + + + // FETCH 1 (BODYSTRUCTURE) + $response = StringIterator::withLiteral("* 1 FETCH (BODYSTRUCTURE (\"TEXT\" \"PLAIN\" (\"CHARSET\" \"UTF-8\") NIL NIL \"7BIT\" 38 1 NIL NIL NIL))\x0D\x0A"); + $actual = $object->parse($response); + $this->assertEquals('1', $actual->offsetGet(0)->number()); + $this->assertTrue($actual->offsetGet(0)->body()->structure()->plainTextBodyExists()); + $this->assertFalse($actual->offsetGet(0)->body()->structure()->htmlBodyExists()); + $this->assertFalse($actual->offsetGet(0)->body()->structure()->attachmentsExists()); + + $response = StringIterator::withLiteral('* 1 FETCH (BODYSTRUCTURE (("text" "plain" ("charset" "utf-8" "Imap" "flowed") NIL NIL "7bit" 1399 47 NIL NIL NIL NIL)("text" "html" ("charset" "utf-8") NIL NIL "7bit" 4570 116 NIL NIL NIL NIL)"alternative" ("boundary" "------------A89A745D2D07E229D39052C8") NIL NIL NIL))'."\x0D\x0A"); + $actual = $object->parse($response); + $this->assertEquals('1', $actual->offsetGet(0)->number()); + $this->assertTrue($actual->offsetGet(0)->body()->structure()->plainTextBodyExists()); + $this->assertTrue($actual->offsetGet(0)->body()->structure()->htmlBodyExists()); + $this->assertFalse($actual->offsetGet(0)->body()->structure()->attachmentsExists()); + + $response = StringIterator::withLiteral('* 1 FETCH (BODYSTRUCTURE ((("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 58 1 NIL NIL NIL NIL)("text" "html" ("charset" "us-ascii") NIL NIL "7bit" 49 2 NIL NIL NIL NIL) "alternative" ("boundary" "b2_C5VDszZ1ehFtRLkuszAURx9g9bPNLBC7V6l7ellTA") NIL NIL NIL)("text" "plain" ("name" "Plaintext" "charset" "us-ascii") "<Plaintext>" NIL "base64" 18 1 NIL ("attachment" ("filename" "Plaintext")) NIL NIL) "mixed" ("boundary" "b1_C5VDszZ1ehFtRLkuszAURx9g9bPNLBC7V6l7ellTA") NIL NIL NIL)))'."\x0D\x0A"); + $actual = $object->parse($response); + $this->assertEquals('1', $actual->offsetGet(0)->number()); + $this->assertTrue($actual->offsetGet(0)->body()->structure()->plainTextBodyExists()); + $this->assertTrue($actual->offsetGet(0)->body()->structure()->htmlBodyExists()); + $this->assertTrue($actual->offsetGet(0)->body()->structure()->attachmentsExists()); + + // Plain text with attachments + $response = StringIterator::withLiteral('* 1 FETCH (BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "7BIT" 86 2 NIL NIL NIL)("IMAGE" "PNG" ("NAME" "zoltan-api-errors.png") NIL NIL "BASE64" 327336 NIL ("ATTACHMENT" ("FILENAME" "zoltan-api-errors.png")) NIL) "MIXED" ("BOUNDARY" "00000000000088a9bc05712e52c2") NIL NIL))'."\x0D\x0A"); + $actual = $object->parse($response); + $this->assertEquals('1', $actual->offsetGet(0)->number()); + $this->assertTrue($actual->offsetGet(0)->body()->structure()->plainTextBodyExists()); + $this->assertFalse($actual->offsetGet(0)->body()->structure()->htmlBodyExists()); + $this->assertTrue($actual->offsetGet(0)->body()->structure()->attachmentsExists()); + + // Plain text email with html attachment + // The interpreter must not confuse the html attachment with the html body + $response = StringIterator::withLiteral('* 1 FETCH (BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "7BIT" 66 2 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8" "NAME" "Trick.html") NIL NIL "BASE64" 47952 960 NIL ("ATTACHMENT" ("FILENAME" "Trick.html")) NIL) "MIXED" ("BOUNDARY" "0000000000001b837c05712e6685") NIL NIL))'."\x0D\x0A"); + $actual = $object->parse($response); + $this->assertEquals('1', $actual->offsetGet(0)->number()); + $this->assertTrue($actual->offsetGet(0)->body()->structure()->plainTextBodyExists()); + $this->assertFalse($actual->offsetGet(0)->body()->structure()->htmlBodyExists()); + $this->assertTrue($actual->offsetGet(0)->body()->structure()->attachmentsExists()); + + // FETCH 1 (BODY[HEADER]) + $response = StringIterator::withLiteral(file_get_contents(codecept_data_dir().'FETCH_BODY_HEADER.txt')); + $actual = $object->parse($response); + $this->assertTrue($actual->offsetGet(0)->hasHeader()); + $this->assertNotEmpty($actual->offsetGet(0)->header()->date()); + $this->assertEquals(1528185590, $actual->offsetGet(0)->header()->date()->getTimestamp()); + $this->assertEquals(array('Daniel <user@localhost.localdomain>'), $actual->offsetGet(0)->header()->to()); + $this->assertEquals(array('Mailer <root@localhost.localdomain>'), $actual->offsetGet(0)->header()->from()); + $this->assertEquals(array('Information <user@localhost.localdomain>'), $actual->offsetGet(0)->header()->replyTo()); + $this->assertEquals('Test Email: Baron Hartmann', $actual->offsetGet(0)->header()->subject()); + $this->assertEmpty($actual->offsetGet(0)->header()->cc()); + $this->assertEmpty($actual->offsetGet(0)->header()->bcc()); + + $response = StringIterator::withLiteral(file_get_contents(codecept_data_dir().'FETCH_BODY_HEADER.txt')); + $actual = $object->parse($response); + $this->assertTrue($actual->offsetGet(0)->hasHeader()); + $this->assertNotEmpty($actual->offsetGet(0)->header()->date()); + $this->assertEquals(1528185590, $actual->offsetGet(0)->header()->date()->getTimestamp()); + $this->assertEquals(array('Daniel <user@localhost.localdomain>'), $actual->offsetGet(0)->header()->to()); + $this->assertEquals(array('Mailer <root@localhost.localdomain>'), $actual->offsetGet(0)->header()->from()); + $this->assertEquals(array('Information <user@localhost.localdomain>'), $actual->offsetGet(0)->header()->replyTo()); + $this->assertEquals('Test Email: Baron Hartmann', $actual->offsetGet(0)->header()->subject()); + $this->assertEmpty($actual->offsetGet(0)->header()->cc()); + $this->assertEmpty($actual->offsetGet(0)->header()->bcc()); + + $response = StringIterator::withLiteral(file_get_contents(codecept_data_dir().'FETCH_UID_FLAGS_BODY_HEADER_BODYSTRUCTURE.txt')); + $actual = $object->parse($response); + $this->assertTrue($actual->offsetGet(0)->hasHeader()); + $this->assertNotEmpty($actual->offsetGet(0)->header()->date()); + $this->assertEquals(1528185590, $actual->offsetGet(0)->header()->date()->getTimestamp()); + $this->assertEquals(array('Daniel <user@localhost.localdomain>'), $actual->offsetGet(0)->header()->to()); + $this->assertEquals(array('Mailer <root@localhost.localdomain>'), $actual->offsetGet(0)->header()->from()); + $this->assertEquals(array('Information <user@localhost.localdomain>'), $actual->offsetGet(0)->header()->replyTo()); + $this->assertEquals('Test Email: Baron Hartmann', $actual->offsetGet(0)->header()->subject()); + $this->assertEmpty($actual->offsetGet(0)->header()->cc()); + $this->assertEmpty($actual->offsetGet(0)->header()->bcc()); + + // Test charset encoding like UTF-8 + // Test multiple email addresses + // Test when keywords do not appear the correct order + $response = StringIterator::withLiteral(file_get_contents(codecept_data_dir().'FETCH_FLAGS_BODY_HEADER_BODYSTRUCTURE_UTF8.txt')); + $actual = $object->parse($response); + $this->assertTrue($actual->offsetGet(0)->hasHeader()); + $this->assertNotEmpty($actual->offsetGet(0)->header()->date()); + $this->assertEquals(1531235225, $actual->offsetGet(0)->header()->date()->getTimestamp()); + $to = array( + 'Daniel <daniel@example.com>', + 'Ashley <ashley@example.com>', + ); + $this->assertEquals($to, $actual->offsetGet(0)->header()->to()); + $this->assertEquals(array('Joe <joe@example.com>'), $actual->offsetGet(0)->header()->from()); + $this->assertEquals(array('Joe <joe@example.com>'), $actual->offsetGet(0)->header()->replyTo()); + $this->assertEquals('RE: Test Email: Please Ignore', $actual->offsetGet(0)->header()->subject()); + $this->assertEquals('<kcim.5b44cb99.2f81.6604c5126d8c32d7@hoza15.fra2.bytemine.net>', $actual->offsetGet(0)->header()->messageId()); + $this->assertEmpty($actual->offsetGet(0)->header()->cc()); + $this->assertEmpty($actual->offsetGet(0)->header()->bcc()); + + // TODO: test cc and bcc + // TODO: 100% code coverage + + // Test body plain text email + $response = StringIterator::withLiteral(file_get_contents(codecept_data_dir().'PLAIN_FETCH_UID_BODY_HEADER_BODYSTUCTURE_BODY.txt')); + $actual = $object->parse($response); + $this->assertTrue($actual->offsetGet(0)->hasHeader()); + $this->assertNotEmpty($actual->offsetGet(0)->header()->date()); + $this->assertEquals(1529494340, $actual->offsetGet(0)->header()->date()->getTimestamp()); + $to = array( + 'qweqwe <xxxxxxxxx@gmail.com>', + ); + $this->assertEquals($to, $actual->offsetGet(0)->header()->to()); + $this->assertEquals(array('Daniel xxxxxx <xxxxxxxxx@gmail.com>'), $actual->offsetGet(0)->header()->from()); + $this->assertEquals(array('Daniel xxxxxx <xxxxxxxxx@gmail.com>'), $actual->offsetGet(0)->header()->replyTo()); + $this->assertEquals('Plain text email', $actual->offsetGet(0)->header()->subject()); + $this->assertEmpty($actual->offsetGet(0)->header()->cc()); + $this->assertEmpty($actual->offsetGet(0)->header()->bcc()); + $expectedBody = 'Hi this should only be in plain text'; + $actualBody = $actual->offsetGet(0)->body()->text(); + $this->assertEquals($expectedBody, $actualBody); + // Test body plain text email with attachments + // Test body html/plain text email + // Test body html/plain text email with attachments + + + $response = StringIterator::withLiteral(file_get_contents(codecept_data_dir().'FLAGS_UID_BODY_HEADER_BODYSTRUCTURE_BODY_TEXT.txt')); + $actual = $object->parse($response); + + $this->assertTrue($actual->offsetGet(0)->hasHeader()); + $this->assertNotEmpty($actual->offsetGet(0)->header()->date()); + $this->assertEquals(1516806498, $actual->offsetGet(0)->header()->date()->getTimestamp()); + $to = array( + 'Administrator <xxxxxxxxx@gmail.com>', + ); + $this->assertEquals($to, $actual->offsetGet(0)->header()->to()); + $this->assertEquals(array('SuiteCRM <xxxxxxxxx@gmail.com>'), $actual->offsetGet(0)->header()->from()); + $this->assertEquals(array('SuiteCRM <xxxxxxxxx@gmail.com>'), $actual->offsetGet(0)->header()->replyTo()); + $this->assertEquals('', $actual->offsetGet(0)->header()->subject()); + $this->assertEmpty($actual->offsetGet(0)->header()->cc()); + $this->assertEmpty($actual->offsetGet(0)->header()->bcc()); + $this->assertNotEmpty($actual->offsetGet(0)->body()->text()); + $this->assertNotEmpty($actual->offsetGet(0)->body()->html()); + + + $response = StringIterator::withLiteral(file_get_contents(codecept_data_dir().'FETCH_RANGE.txt')); + $actual = $object->parse($response); + + $this->assertTrue($actual->offsetGet(0)->hasHeader()); + $this->assertNotEmpty($actual->offsetGet(0)->header()->date()); + $this->assertEquals(1515494001, $actual->offsetGet(0)->header()->date()->getTimestamp()); + $to = array( + 'UuUuUu UuUuUu <UuUuUuUuU@gmail.com>', + ); + $this->assertEquals($to, $actual->offsetGet(0)->header()->to()); + $this->assertEquals(array('Gmail Team <mail-noreply@google.com>'), $actual->offsetGet(0)->header()->from()); + $this->assertEquals(array('Gmail Team <mail-noreply@google.com>'), $actual->offsetGet(0)->header()->replyTo()); + $this->assertEquals('Three tips to get the most out of Gmail', $actual->offsetGet(0)->header()->subject()); + $this->assertEmpty($actual->offsetGet(0)->header()->cc()); + $this->assertEmpty($actual->offsetGet(0)->header()->bcc()); + $this->assertNotEmpty($actual->offsetGet(0)->body()->text()); + $this->assertTrue(\SalesAgility\Utility\StringValue::startsWith($actual->offsetGet(0)->body()->text(), 'Three tips to get the most out of Gmail')); + $this->assertNotEmpty($actual->offsetGet(0)->body()->html()); + $this->assertTrue(\SalesAgility\Utility\StringValue::startsWith($actual->offsetGet(0)->body()->html(), "\r\n".'<!DOCTYPE html>')); + } +} diff --git a/tests/unit/SalesAgility/Imap/Interpreter/SearchInterpreterTest.php b/tests/unit/SalesAgility/Imap/Interpreter/SearchInterpreterTest.php new file mode 100755 index 0000000..bd8aac3 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Interpreter/SearchInterpreterTest.php @@ -0,0 +1,46 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Interpreter\SearchInterpreter; +use \SalesAgility\Imap\Response\MessageFactory; +class SearchInterpreterTest extends \Codeception\Test\Unit +{ + + public function testParse() + { + $object= new SearchInterpreter(); + $response = \SalesAgility\Iteration\StringIterator::withLiteral('* SEARCH 2677 2678 2679'."\r\n"); + $messageList= new \SalesAgility\Imap\Response\MessageList(); + + $message = MessageFactory::instance(); + $message->offsetSet('number', '2677'); + $messageList[] = $message; + + $message = MessageFactory::instance(); + $message->offsetSet('number', '2678'); + $messageList[] = $message; + + $message = MessageFactory::instance(); + $message->offsetSet('number', '2679'); + $messageList[] = $message; + + $actual = $object->parse($response); + $this->assertEquals($messageList, $actual); + } +} diff --git a/tests/unit/SalesAgility/Imap/Lexeme/LexemeListTest.php b/tests/unit/SalesAgility/Imap/Lexeme/LexemeListTest.php new file mode 100755 index 0000000..a5c7399 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Lexeme/LexemeListTest.php @@ -0,0 +1,229 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Lexeme\LexemeList; +use SalesAgility\Imap\Lexeme\Lexeme; +use SalesAgility\Imap\Lexeme\LexemeType; +use SalesAgility\Imap\Token\Token; +use SalesAgility\Imap\Token\TokenType; +use SalesAgility\Iteration\StringIterator; + +class LexemeListTest extends \Codeception\Test\Unit +{ + /** @var UnitTester $tester */ + protected $tester; + + public function testKey() + { + $object = new LexemeList(); + + $l1 = new Lexeme(); + $l1->addToken(new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l1; + + $l2 = new Lexeme(); + $l2->addToken(new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l2; + + $l3 = new Lexeme(); + $l3->addToken(new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l3; + + $object->next(); + $object->next(); + $this->assertEquals(2, $object->key()); + } + + public function testCurrent() + { + $object = new LexemeList(); + + $l1 = new Lexeme(); + $l1->addToken(new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l1; + + $l2 = new Lexeme(); + $l2->addToken(new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l2; + + $l3 = new Lexeme(); + $l3->addToken(new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l3; + + $this->assertEquals('a', $object->current()->offsetGet(0)->toString()); + $object->next(); + $this->assertEquals('b', $object->current()->offsetGet(0)->toString()); + $object->next(); + $this->assertEquals('c', $object->current()->offsetGet(0)->toString()); + } + + public function testRewind() + { + $object = new LexemeList(); + + $l1 = new Lexeme(); + $l1->addToken(new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l1; + + $l2 = new Lexeme(); + $l2->addToken(new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l2; + + $l3 = new Lexeme(); + $l3->addToken(new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l3; + + $object->next(); + $object->next(); + $object->rewind(); + $this->assertEquals('a', $object->current()->offsetGet(0)->toString()); + } + + public function testOffsetUnset() + { + $object = new LexemeList(); + + $l1 = new Lexeme(); + $l1->addToken(new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l1; + + $l2 = new Lexeme(); + $l2->addToken(new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l2; + + $l3 = new Lexeme(); + $l3->addToken(new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l3; + + $object->offsetUnset(2); + $this->assertEquals('b', $object->offsetGet(1)->offsetGet(0)->toString()); + } + + public function testValid() + { + $object = new LexemeList(); + + $l1 = new Lexeme(); + $l1->addToken(new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l1; + + $l2 = new Lexeme(); + $l2->addToken(new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l2; + + $l3 = new Lexeme(); + $l3->addToken(new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l3; + + $this->assertTrue($object->valid()); + $object->next(); + $this->assertTrue($object->valid()); + $object->next(); + $this->assertTrue($object->valid()); + $object->next(); + $this->assertFalse($object->valid()); + } + + public function testOffsetExists() + { + $object = new LexemeList(); + + $l1 = new Lexeme(); + $l1->addToken(new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l1; + + $l2 = new Lexeme(); + $l2->addToken(new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l2; + + $l3 = new Lexeme(); + $l3->addToken(new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l3; + + $this->assertTrue($object->offsetExists(0)); + $this->assertTrue($object->offsetExists(1)); + $this->assertTrue($object->offsetExists(2)); + $this->assertFalse($object->offsetExists(3)); + } + + public function testOffsetGet() + { + $object = new LexemeList(); + + $l1 = new Lexeme(); + $l1->addToken(new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l1; + + $l2 = new Lexeme(); + $l2->addToken(new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l2; + + $l3 = new Lexeme(); + $l3->addToken(new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l3; + + $this->assertEquals('b', $object->offsetGet(1)->offsetGet(0)->toString()); + } + + public function testOffsetSet() + { + $object = new LexemeList(); + + $l1 = new Lexeme(); + $l1->addToken(new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l1; + + $l2 = new Lexeme(); + $l2->addToken(new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl())); + $object[0] = $l2; + + $this->assertEquals('b', $object->offsetGet(0)->offsetGet(0)->toString()); + + + $this->tester->expectException( + new \InvalidArgumentException('Lexeme List can only store integer key values'), + function () { + $object = new LexemeList(); + $l2 = new Lexeme(); + $l2->addToken(new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl())); + $object['string'] = $l2; + } + ); + + $this->tester->expectException( + new \InvalidArgumentException('Lexeme List can only store values which derive from a Lexeme'), + function () { + $object = new LexemeList(); + $object[] = 'hello lexeme'; + } + ); + } + + public function testNext() + { + $object = new LexemeList(); + + $l1 = new Lexeme(); + $l1->addToken(new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl())); + $object[] = $l1; + $this->assertTrue($object->valid()); + $object->next(); + $this->assertFalse($object->valid()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Lexeme/LexemeTest.php b/tests/unit/SalesAgility/Imap/Lexeme/LexemeTest.php new file mode 100755 index 0000000..1e12660 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Lexeme/LexemeTest.php @@ -0,0 +1,226 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Lexeme\Lexeme; +use SalesAgility\Imap\Lexeme\LexemeType; +use SalesAgility\Imap\Token\Token; +use SalesAgility\Imap\Token\TokenType; +use SalesAgility\Iteration\StringIterator; + +class LexemeTest extends \Codeception\Test\Unit +{ + /** @var \ReflectionClass $reflection */ + private static $reflection; + /**@var \ReflectionProperty $propertyFirst */ + private static $propertyFirst; + /**@var \ReflectionProperty $propertyCurrent */ + private static $propertyCurrent; + /**@var \ReflectionProperty $propertyLast */ + private static $propertyLast; + /**@var \ReflectionProperty $propertyLength */ + private static $propertyLength; + /**@var \ReflectionProperty $propertyTokenList */ + private static $propertyTokenList; + /**@var \ReflectionProperty $propertyTypes */ + private static $propertyTypes; + + protected function _before() + { + if(self::$reflection === null) { + self::$reflection = new ReflectionClass(Lexeme::class); + self::$propertyFirst = self::$reflection->getProperty('first'); + self::$propertyFirst->setAccessible(true); + self::$propertyCurrent = self::$reflection->getProperty('current'); + self::$propertyCurrent->setAccessible(true); + self::$propertyLast = self::$reflection->getProperty('last'); + self::$propertyLast->setAccessible(true); + self::$propertyLength = self::$reflection->getProperty('length'); + self::$propertyLength->setAccessible(true); + self::$propertyTokenList = self::$reflection->getProperty('tokenList'); + self::$propertyTokenList->setAccessible(true); + self::$propertyTypes = self::$reflection->getProperty('types'); + self::$propertyTypes->setAccessible(true); + } + } + + + public function test__construct() + { + $object = new Lexeme(); + $this->assertEquals(null, self::$propertyFirst->getValue($object)); + $this->assertEquals(null, self::$propertyCurrent->getValue($object)); + $this->assertEquals(null, self::$propertyLast->getValue($object)); + $this->assertEquals(null, self::$propertyLength->getValue($object)); + $this->assertEquals(array(), self::$propertyTokenList->getValue($object)); + $this->assertEquals(array(), self::$propertyTypes->getValue($object)); + } + + public function testAddType() + { + $object = new Lexeme(); + $object->addType(LexemeType::whitespace()); + $expect = array(LexemeType::whitespace()); + $actual = self::$propertyTypes->getValue($object); + $this->assertEquals($expect, $actual); + } + + public function testAddToken() + { + $object = new Lexeme(); + $token = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token); + $this->assertEquals('a', $object->offsetGet(0)->toString()); + } + + public function testSeek() + { + $object = new Lexeme(); + $token1 = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token1); + $token2 = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token2); + $token3 = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token3); + + $object->seek(2); + $this->assertEquals($token3, $object->current()); + + $this->assertFalse($object->seek(3)); + } + + public function testKey() + { + $object = new Lexeme(); + $token1 = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token1); + $token2 = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token2); + $token3 = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token3); + + $this->assertEquals(0, $object->key()); + $object->next(); + $this->assertEquals(1, $object->key()); + $object->next(); + $this->assertEquals(2, $object->key()); + } + + public function testHasType() + { + $object = new Lexeme(); + $object->addType(LexemeType::whitespace()); + $expect = array(LexemeType::whitespace()); + $actual = self::$propertyTypes->getValue($object); + $this->assertTrue($object->hasType(LexemeType::whitespace())); + $this->assertFalse($object->hasType(LexemeType::utext())); + } + + public function testToString() + { + $object = new Lexeme(); + $token = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token); + $this->assertEquals('a', $object->toString()); + } + + public function testFastForward() + { + $object = new Lexeme(); + $token1 = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token1); + $token2 = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token2); + $token3 = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token3); + $object->fastForward(); + $this->assertEquals(2, $object->key()); + } + + public function testOffsetGet() + { + $object = new Lexeme(); + $token1 = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token1); + $this->assertSame($token1, $object->offsetGet(0)); + } + + + + public function testNext() + { + $object = new Lexeme(); + $token1 = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token1); + $token2 = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token2); + $token3 = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token3); + $object->rewind(); + $object->next(); + $object->next(); + $this->assertSame($token3, $object->current()); + } + + public function testValid() + { + $object = new Lexeme(); + $token1 = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token1); + $token2 = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token2); + $token3 = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token3); + $object->rewind(); + $this->assertTrue($object->valid()); + $object->next(); + $this->assertTrue($object->valid()); + $object->next(); + $this->assertTrue($object->valid()); + $object->next(); + $this->assertFalse($object->valid()); + } + + + + public function testRewind() + { + $object = new Lexeme(); + $token1 = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token1); + $token2 = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token2); + $token3 = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token3); + + $object->next(); + $object->next(); + $object->next(); + + $object->rewind(); + $this->assertEquals(0, $object->key()); + } + + public function testCurrent() + { + $object = new Lexeme(); + $token1 = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object->addToken($token1); + $this->assertSame($token1, $object->current()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Lexeme/LexemeTypeTest.php b/tests/unit/SalesAgility/Imap/Lexeme/LexemeTypeTest.php new file mode 100755 index 0000000..61da267 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Lexeme/LexemeTypeTest.php @@ -0,0 +1,988 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ +/** + * Created by PhpStorm. + * User: user + * Date: 6/27/18 + * Time: 12:08 PM + */ + +use SalesAgility\Imap\Lexeme\LexemeType; + +class LexemeTypeTest extends \Codeception\Test\Unit +{ + /** @var UnitTester */ + protected $tester; + + public function testAllCapitals() + { + $object = LexemeType::allCapitals(); + $this->assertTrue($object->isAllCapitals()); + } + + public function testIsAllCapitals() + { + $object = LexemeType::allCapitals(); + $this->assertTrue($object->isAllCapitals()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isAllCapitals()); + } + + public function testAllNumbers() + { + $object = LexemeType::allNumbers(); + $this->assertTrue($object->isAllNumbers()); + } + + public function testIsAllNumbers() + { + $object = LexemeType::allNumbers(); + $this->assertTrue($object->isAllNumbers()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isAllNumbers()); + } + + public function testCapitalsNumbers() + { + $object = LexemeType::allNumbers(); + $this->assertTrue($object->isAllNumbers()); + } + + public function testIsCapitalsNumbers() + { + $object = LexemeType::capitalsNumbers(); + $this->assertTrue($object->isCapitalsNumbers()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isCapitalsNumbers()); + } + + public function testCtext() + { + $object = LexemeType::ctext(); + $this->assertTrue($object->isCtext()); + } + + public function testIsCtext() + { + $object = LexemeType::ctext(); + $this->assertTrue($object->isCtext()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isCtext()); + } + + public function testCcontent() + { + $object = LexemeType::ccontent(); + $this->assertTrue($object->isCcontent()); + } + + public function testIsCcontent() + { + $object = LexemeType::ccontent(); + $this->assertTrue($object->isCcontent()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isCcontent()); + } + + + public function testComment() + { + $object = LexemeType::comment(); + $this->assertTrue($object->isComment()); + } + + public function testIsComment() + { + $object = LexemeType::comment(); + $this->assertTrue($object->isComment()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isComment()); + } + + public function testCfws() + { + $object = LexemeType::cfws(); + $this->assertTrue($object->isCfws()); + } + + public function testIsCfws() + { + $object = LexemeType::cfws(); + $this->assertTrue($object->isCfws()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isCfws()); + } + + public function testAtext() + { + $object = LexemeType::atext(); + $this->assertTrue($object->isAtext()); + } + + public function testIsAtext() + { + $object = LexemeType::atext(); + $this->assertTrue($object->isAtext()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isAtext()); + } + + + public function testAtom() + { + $object = LexemeType::atom(); + $this->assertTrue($object->isAtom()); + } + + public function testisAtom() + { + $object = LexemeType::atom(); + $this->assertTrue($object->isAtom()); + + $object = LexemeType::comment(); + $this->assertFalse($object->isAtom()); + } + + public function testDotAtomText() + { + $object = LexemeType::dotAtomText(); + $this->assertTrue($object->isDotAtomText()); + } + + public function testIsDotAtomText() + { + $object = LexemeType::dotAtomText(); + $this->assertTrue($object->isDotAtomText()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isDotAtomText()); + } + + public function testQtext() + { + $object = LexemeType::qtext(); + $this->assertTrue($object->isQtext()); + } + + public function testIsQtext() + { + $object = LexemeType::qtext(); + $this->assertTrue($object->isQtext()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isQtext()); + } + + public function testQcontent() + { + $object = LexemeType::qcontent(); + $this->assertTrue($object->isQcontent()); + } + + public function testIsQContent() + { + $object = LexemeType::qcontent(); + $this->assertTrue($object->isQcontent()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isQcontent()); + } + + + public function testQuotedString() + { + $object = LexemeType::quotedString(); + $this->assertTrue($object->isQuotedString()); + } + + public function testIsQuotedString() + { + $object = LexemeType::quotedString(); + $this->assertTrue($object->isQuotedString()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isQuotedString()); + } + + + public function testWord() + { + $object = LexemeType::word(); + $this->assertTrue($object->isWord()); + } + + public function testIsWord() + { + $object = LexemeType::word(); + $this->assertTrue($object->isWord()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isWord()); + } + + public function testPhrase() + { + $object = LexemeType::phrase(); + $this->assertTrue($object->isPhrase()); + } + + public function testIsPhrase() + { + $object = LexemeType::phrase(); + $this->assertTrue($object->isPhrase()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isPhrase()); + } + + public function testUtext() + { + $object = LexemeType::utext(); + $this->assertTrue($object->isUtext()); + } + + public function testIsUtext() + { + $object = LexemeType::utext(); + $this->assertTrue($object->isUtext()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isUtext()); + } + + public function testDateTime() + { + $object = LexemeType::dateTime(); + $this->assertTrue($object->isDateTime()); + } + + public function testIsDateTime() + { + $object = LexemeType::dateTime(); + $this->assertTrue($object->isDateTime()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isDateTime()); + } + + public function testDayOfWeek() + { + $object = LexemeType::dayOfWeek(); + $this->assertTrue($object->isDayOfWeek()); + } + + public function testIsDayOfWeek() + { + $object = LexemeType::dayOfWeek(); + $this->assertTrue($object->isDayOfWeek()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isDayOfWeek()); + } + + public function testDayName() + { + $object = LexemeType::dayName(); + $this->assertTrue($object->isDayName()); + } + + public function testIsDayName() + { + $object = LexemeType::dayName(); + $this->assertTrue($object->isDayName()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isDayName()); + } + + public function testDate() + { + $object = LexemeType::date(); + $this->assertTrue($object->isDate()); + } + + public function testIsDate() + { + $object = LexemeType::date(); + $this->assertTrue($object->isDate()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isDate()); + } + + public function testYear() + { + $object = LexemeType::year(); + $this->assertTrue($object->isYear()); + } + + public function testIsYear() + { + $object = LexemeType::year(); + $this->assertTrue($object->isYear()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isYear()); + } + + + public function testMonth() + { + $object = LexemeType::month(); + $this->assertTrue($object->isMonth()); + } + + public function testIsMonth() + { + $object = LexemeType::month(); + $this->assertTrue($object->isMonth()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isMonth()); + } + + public function testMonthName() + { + $object = LexemeType::monthName(); + $this->assertTrue($object->isMonthName()); + } + + public function testIsMonthName() + { + $object = LexemeType::monthName(); + $this->assertTrue($object->isMonthName()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isMonthName()); + } + + public function testDay() + { + $object = LexemeType::day(); + $this->assertTrue($object->isDay()); + } + + public function testIsDay() + { + $object = LexemeType::day(); + $this->assertTrue($object->isDay()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isDay()); + } + + + public function testTime() + { + $object = LexemeType::time(); + $this->assertTrue($object->isTime()); + } + + public function testIsTime() + { + $object = LexemeType::time(); + $this->assertTrue($object->isTime()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isTime()); + } + + public function testTimeOfDay() + { + $object = LexemeType::timeOfDay(); + $this->assertTrue($object->isTimeOfDay()); + } + + public function testIsTimeOfDat() + { + $object = LexemeType::timeOfDay(); + $this->assertTrue($object->isTimeOfDay()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isTimeOfDay()); + } + + public function testHour() + { + $object = LexemeType::hour(); + $this->assertTrue($object->isHour()); + } + + public function testIsHour() + { + $object = LexemeType::hour(); + $this->assertTrue($object->isHour()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isHour()); + } + + + public function testMinute() + { + $object = LexemeType::minute(); + $this->assertTrue($object->isMinute()); + } + + public function testIsMinute() + { + $object = LexemeType::minute(); + $this->assertTrue($object->isMinute()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isMinute()); + } + + public function testSecond() + { + $object = LexemeType::second(); + $this->assertTrue($object->isSecond()); + } + + public function testIsSecond() + { + $object = LexemeType::second(); + $this->assertTrue($object->isSecond()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isSecond()); + } + + public function testZone() + { + $object = LexemeType::zone(); + $this->assertTrue($object->isZone()); + } + + public function testIsZone() + { + $object = LexemeType::zone(); + $this->assertTrue($object->isZone()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isZone()); + } + + public function testAddress() + { + $object = LexemeType::address(); + $this->assertTrue($object->isAddress()); + } + + public function testIsAddress() + { + $object = LexemeType::address(); + $this->assertTrue($object->isAddress()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isAddress()); + } + + public function testMailbox() + { + $object = LexemeType::mailbox(); + $this->assertTrue($object->isMailbox()); + } + + public function testIsMailbox() + { + $object = LexemeType::mailbox(); + $this->assertTrue($object->isMailbox()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isMailbox()); + } + + public function testNameAddress() + { + $object = LexemeType::nameAddress(); + $this->assertTrue($object->isNameAddress()); + } + + public function testIsNameAddress() + { + $object = LexemeType::nameAddress(); + $this->assertTrue($object->isNameAddress()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isNameAddress()); + } + + public function testAngleAddressPair() + { + $object = LexemeType::angleAddressPair(); + $this->assertTrue($object->isAngleAddressPair()); + } + + public function testIsAngleAddressPair() + { + $object = LexemeType::angleAddressPair(); + $this->assertTrue($object->isAngleAddressPair()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isAngleAddressPair()); + } + + public function testDisplayName() + { + $object = LexemeType::displayName(); + $this->assertTrue($object->isDisplayName()); + } + + public function testIsDisplayName() + { + $object = LexemeType::displayName(); + $this->assertTrue($object->isDisplayName()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isDisplayName()); + } + + public function testMailboxList() + { + $object = LexemeType::mailboxList(); + $this->assertTrue($object->isMailboxList()); + } + + public function testIsMailBoxList() + { + $object = LexemeType::mailboxList(); + $this->assertTrue($object->isMailboxList()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isMailboxList()); + } + + + public function testGroupAddress() + { + $object = LexemeType::groupAddress(); + $this->assertTrue($object->isGroupAddress()); + } + + public function testIsGroupAddress() + { + $object = LexemeType::groupAddress(); + $this->assertTrue($object->isGroupAddress()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isGroupAddress()); + } + + public function testAddressList() + { + $object = LexemeType::addressList(); + $this->assertTrue($object->isAddressList()); + } + + public function testIsAddressList() + { + $object = LexemeType::addressList(); + $this->assertTrue($object->isAddressList()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isAddressList()); + } + + public function testAddressSpec() + { + $object = LexemeType::addressSpec(); + $this->assertTrue($object->isAddressSpec()); + } + + public function testIsAddressSpec() + { + $object = LexemeType::addressSpec(); + $this->assertTrue($object->isAddressSpec()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isAddressSpec()); + } + + public function testLocalPart() + { + $object = LexemeType::localPart(); + $this->assertTrue($object->isLocalPart()); + } + + public function testIsLocalPart() + { + $object = LexemeType::localPart(); + $this->assertTrue($object->isLocalPart()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isLocalPart()); + } + + public function testDomain() + { + $object = LexemeType::domain(); + $this->assertTrue($object->isDomain()); + } + + public function testIsDomain() + { + $object = LexemeType::domain(); + $this->assertTrue($object->isDomain()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isDomain()); + } + + public function testDomainLiteral() + { + $object = LexemeType::domainLiteral(); + $this->assertTrue($object->isDomainLiteral()); + } + + public function testIsDomainLiteral() + { + $object = LexemeType::domainLiteral(); + $this->assertTrue($object->isDomainLiteral()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isDomainLiteral()); + } + + public function testDcontent() + { + $object = LexemeType::dcontent(); + $this->assertTrue($object->isDcontent()); + } + + public function testIsDContent() + { + $object = LexemeType::dcontent(); + $this->assertTrue($object->isDcontent()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isDcontent()); + } + + public function testDtext() + { + $object = LexemeType::dtext(); + $this->assertTrue($object->isDtext()); + } + + public function testIsDtext() + { + $object = LexemeType::dtext(); + $this->assertTrue($object->isDtext()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isDtext()); + } + + public function testMessage() + { + $object = LexemeType::message(); + $this->assertTrue($object->isMessage()); + } + + public function testIsMessage() + { + $object = LexemeType::message(); + $this->assertTrue($object->isMessage()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isMessage()); + } + + public function testField() + { + $object = LexemeType::fieldHeader(); + $this->assertTrue($object->isFieldHeader()); + } + + public function testIsFieldHeader() + { + $object = LexemeType::fieldHeader(); + $this->assertTrue($object->isFieldHeader()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isFieldHeader()); + } + + public function testFieldHeader() + { + $object = LexemeType::fieldHeader(); + $this->assertTrue($object->isFieldHeader()); + } + + public function testIsFieldBody() + { + $object = LexemeType::fieldBody(); + $this->assertTrue($object->isFieldBody()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isFieldBody()); + } + + public function testSystemFlag() + { + $object = LexemeType::systemFlag(); + $this->assertTrue($object->isSystemFlag()); + } + + public function testIsSystemFlag() + { + + $object = LexemeType::systemFlag(); + $this->assertTrue($object->isSystemFlag()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isFlagClass()); + } + + public function testFlagClass() + { + $object = LexemeType::flagClass(); + $this->assertTrue($object->isFlagClass()); + } + + public function testIsFlagClass() + { + $object = LexemeType::flagClass(); + $this->assertTrue($object->isFlagClass()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isFlagClass()); + } + + public function testFlagType() + { + $object = LexemeType::flagType(); + $this->assertTrue($object->isFlagType()); + } + + public function testIsFlatType() + { + $object = LexemeType::flagType(); + $this->assertTrue($object->isFlagType()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isFlagType()); + } + + public function testSystemResponse() + { + $object = LexemeType::systemResponse(); + $this->assertTrue($object->isSystemResponse()); + } + + public function testIsSystemResponse() + { + $object = LexemeType::systemResponse(); + $this->assertTrue($object->isSystemResponse()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isSystemResponse()); + } + + public function testCommand() + { + $object = LexemeType::command(); + $this->assertTrue($object->isCommand()); + } + + public function testIsCommand() + { + $object = LexemeType::command(); + $this->assertTrue($object->isCommand()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isCommand()); + } + + public function testSystemCode() + { + $object = LexemeType::systemCode(); + $this->assertTrue($object->isSystemCode()); + } + + public function testIsSystemCode() + { + $object = LexemeType::systemCode(); + $this->assertTrue($object->isSystemCode()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isSystemCode()); + } + + public function testAttribute() + { + $object = LexemeType::attribute(); + $this->assertTrue($object->isAttribute()); + } + + public function testIsAttribute() + { + $object = LexemeType::attribute(); + $this->assertTrue($object->isAttribute()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isAttribute()); + } + + public function testMailboxNameAttribute() + { + $object = LexemeType::mailboxNameAttribute(); + $this->assertTrue($object->isMailboxNameAttribute()); + } + + public function testIsMailboxNameAttribute() + { + $object = LexemeType::mailboxNameAttribute(); + $this->assertTrue($object->isMailboxNameAttribute()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isMailboxNameAttribute()); + } + + + public function testSpecificationRequirementTerm() + { + $object = LexemeType::specificationRequirementTerm(); + $this->assertTrue($object->isSpecificationRequirementTerm()); + } + + public function testIsSpecificationRequirementTerm() + { + $object = LexemeType::specificationRequirementTerm(); + $this->assertTrue($object->isSpecificationRequirementTerm()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isSpecificationRequirementTerm()); + } + + public function testMessageField() + { + $object = LexemeType::messageField(); + $this->assertTrue($object->isMessageField()); + } + + public function testIsMessageField() + { + $object = LexemeType::messageField(); + $this->assertTrue($object->isMessageField()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isMessageField()); + } + + public function testPartSpecifier() + { + $object = LexemeType::partSpecifier(); + $this->assertTrue($object->isPartSpecifier()); + } + + public function testIsPartSpecifier() + { + $object = LexemeType::partSpecifier(); + $this->assertTrue($object->isPartSpecifier()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isMessageField()); + } + + public function testStatusItem() + { + $object = LexemeType::statusItem(); + $this->assertTrue($object->isStatusItem()); + } + + public function testIsStatusItem() + { + $object = LexemeType::statusItem(); + $this->assertTrue($object->isStatusItem()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isStatusItem()); + } + + public function testGroup() + { + $object = LexemeType::group(); + $this->assertTrue($object->isGroup()); + } + + public function testIsGroup() + { + $object = LexemeType::group(); + $this->assertTrue($object->isGroup()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isGroup()); + } + + public function testOptional() + { + $object = LexemeType::optional(); + $this->assertTrue($object->isOptional()); + } + + public function testIsOptional() + { + $object = LexemeType::optional(); + $this->assertTrue($object->isOptional()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isOptional()); + } + + + public function testNewLine() + { + $object = LexemeType::newLine(); + $this->assertTrue($object->isNewLine()); + } + + public function testIsNewLine() + { + $object = LexemeType::newLine(); + $this->assertTrue($object->isNewLine()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isNewLine()); + } + + public function testWhitespace() + { + $object = LexemeType::whitespace(); + $this->assertTrue($object->isWhitespace()); + } + + public function testIsWhitespace() + { + $object = LexemeType::whitespace(); + $this->assertTrue($object->isWhitespace()); + + $object = LexemeType::atom(); + $this->assertFalse($object->isWhitespace()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Lexeme/LexemizerTest.php b/tests/unit/SalesAgility/Imap/Lexeme/LexemizerTest.php new file mode 100755 index 0000000..e63928c --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Lexeme/LexemizerTest.php @@ -0,0 +1,324 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Lexeme\Lexemizer; +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Imap\Token\Tokenizer; +use \SalesAgility\Imap\Lexeme\LexemeList; +use \SalesAgility\Imap\Lexeme\LexemeType; + +class LexemizerTest extends \Codeception\Test\Unit +{ + private function octetCount(LexemeList $lexemeList) { + $octetCount = 0; + foreach ($lexemeList as $lexeme){ + $octetCount += $lexeme->octetCount(); + }; + return $octetCount; + } + + public function testParse() + { + $object = new Lexemizer(); + $tokenizer = new Tokenizer(); + + $keywordTest = StringIterator::withLiteral('*'); + $tokenlist = $tokenizer->parse($keywordTest); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::atom())); + $this->assertEquals(1, $this->octetCount($lexemeList)); + + $keywordTest = StringIterator::withLiteral('1234'); + $tokenlist = $tokenizer->parse($keywordTest); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::atom())); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::allNumbers())); + $this->assertEquals(4, $this->octetCount($lexemeList)); + + $keywordTest = StringIterator::withLiteral('FETCH'); + $tokenlist = $tokenizer->parse($keywordTest); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::atom())); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::allCapitals())); + $this->assertEquals(5, $this->octetCount($lexemeList)); + + $keywordTest = StringIterator::withLiteral('RFC822'); + $tokenlist = $tokenizer->parse($keywordTest); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::atom())); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::capitalsNumbers())); + $this->assertEquals(6, $this->octetCount($lexemeList)); + + $keywordTest = StringIterator::withLiteral('"a"'); + $tokenlist = $tokenizer->parse($keywordTest); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::atom())); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::quotedString())); + $this->assertEquals(3, $this->octetCount($lexemeList)); + + $keywordTest = StringIterator::withLiteral('b'); + $tokenlist = $tokenizer->parse($keywordTest); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::atom())); + $this->assertFalse($lexemeList[0]->hasType(LexemeType::allCapitals())); + $this->assertFalse($lexemeList[0]->hasType(LexemeType::allNumbers())); + $this->assertFalse($lexemeList[0]->hasType(LexemeType::capitalsNumbers())); + $this->assertEquals(1, $this->octetCount($lexemeList)); + + + $keywordTest = StringIterator::withLiteral('b15'); + $tokenlist = $tokenizer->parse($keywordTest); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::atom())); + $this->assertFalse($lexemeList[0]->hasType(LexemeType::allCapitals())); + $this->assertFalse($lexemeList[0]->hasType(LexemeType::allNumbers())); + $this->assertFalse($lexemeList[0]->hasType(LexemeType::capitalsNumbers())); + $this->assertEquals(3, $this->octetCount($lexemeList)); + + $keywordTest = StringIterator::withLiteral('B15a'); + $tokenlist = $tokenizer->parse($keywordTest); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::atom())); + $this->assertFalse($lexemeList[0]->hasType(LexemeType::allCapitals())); + $this->assertFalse($lexemeList[0]->hasType(LexemeType::allNumbers())); + $this->assertFalse($lexemeList[0]->hasType(LexemeType::capitalsNumbers())); + $this->assertEquals(4, $this->octetCount($lexemeList)); + + + $notGroupTest = StringIterator::withLiteral(' this is not a group :('); + $tokenlist = $tokenizer->parse($notGroupTest); + $lexemeList = $object->parse($tokenlist); + $this->assertEquals(23, $this->octetCount($lexemeList)); + + $groupTest1Level = StringIterator::withLiteral('("charset" "us-ascii")'); + $tokenlist = $tokenizer->parse($groupTest1Level); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::atom())); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::group())); + // Tokens inside + $this->assertTrue($lexemeList->offsetGet(0)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(1)->offsetGet(0)->type()->isQuoted()); + $this->assertTrue($lexemeList->offsetGet(2)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(3)->offsetGet(0)->type()->isQuoted()); + $this->assertEquals(22, $this->octetCount($lexemeList)); + + + $groupTest2Level = StringIterator::withLiteral('("text" ("charset" "us-ascii"))'); + $tokenlist = $tokenizer->parse($groupTest2Level); + $lexemeList = $object->parse($tokenlist); + + $this->assertTrue($lexemeList[0]->hasType(LexemeType::atom())); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::group())); + // Tokens inside + $this->assertTrue($lexemeList->offsetGet(0)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(1)->offsetGet(0)->type()->isQuoted()); + $this->assertTrue($lexemeList->offsetGet(2)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(3)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(4)->offsetGet(0)->type()->isQuoted()); + $this->assertTrue($lexemeList->offsetGet(5)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(6)->offsetGet(0)->type()->isQuoted()); + $this->assertEquals(31, $this->octetCount($lexemeList)); + + + $groupTest2Level = StringIterator::withLiteral('("text" ("charset" "us-ascii")("charset" "us-ascii"))'); + $tokenlist = $tokenizer->parse($groupTest2Level); + $lexemeList = $object->parse($tokenlist); + + $this->assertTrue($lexemeList[0]->hasType(LexemeType::atom())); + $this->assertTrue($lexemeList[0]->hasType(LexemeType::group())); + // Tokens inside + // it should flatten the structure to just one level + // Interpreters can read the group tokens to ensure workout where the last character + $this->assertTrue($lexemeList->offsetGet(0)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(1)->offsetGet(0)->type()->isQuoted()); + $this->assertTrue($lexemeList->offsetGet(2)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(3)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(4)->offsetGet(0)->type()->isQuoted()); + $this->assertTrue($lexemeList->offsetGet(5)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(6)->offsetGet(0)->type()->isQuoted()); + $this->assertTrue($lexemeList->offsetGet(7)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(8)->offsetGet(0)->type()->isQuoted()); + $this->assertTrue($lexemeList->offsetGet(9)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(10)->offsetGet(0)->type()->isQuoted()); + $this->assertEquals(53, $this->octetCount($lexemeList)); + + $groupTest3Level = StringIterator::withLiteral('((("charset")))'); + $tokenlist = $tokenizer->parse($groupTest3Level); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList->offsetGet(0)->hasType(LexemeType::atom())); + $this->assertTrue($lexemeList->offsetGet(0)->hasType(LexemeType::group())); + + $this->assertTrue($lexemeList->offsetGet(0)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(1)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(2)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(3)->offsetGet(0)->type()->isQuoted()); + $this->assertEquals(15, $this->octetCount($lexemeList)); + + $groupTest3Level = StringIterator::withLiteral('(("a" ("b"))("c" ("d")) "e" ("f"))'); + $tokenlist = $tokenizer->parse($groupTest3Level); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList->offsetGet(0)->hasType(LexemeType::atom())); + $this->assertTrue($lexemeList->offsetGet(0)->hasType(LexemeType::group())); + // + $this->assertTrue($lexemeList->offsetGet(0)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(1)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(2)->offsetGet(0)->type()->isQuoted()); // a + $this->assertTrue($lexemeList->offsetGet(3)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(4)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(5)->offsetGet(0)->type()->isQuoted()); // b + $this->assertTrue($lexemeList->offsetGet(6)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(7)->offsetGet(0)->type()->isQuoted()); // c + $this->assertTrue($lexemeList->offsetGet(8)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(9)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(10)->offsetGet(0)->type()->isQuoted()); // d + $this->assertTrue($lexemeList->offsetGet(11)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(12)->offsetGet(0)->type()->isQuoted()); // e + $this->assertTrue($lexemeList->offsetGet(13)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(14)->offsetGet(0)->type()->isGroup()); + $this->assertTrue($lexemeList->offsetGet(15)->offsetGet(0)->type()->isQuoted()); // f + $this->assertEquals(34, $this->octetCount($lexemeList));; + + // Tokens inside + // it should flatten the structure to just one level + // Interpreters can read the group tokens to work out where the last character for a group is + + // test optional + $optionalTest = StringIterator::withLiteral('* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted.'."\x0d\x0A"); + $tokenlist = $tokenizer->parse($optionalTest); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList->offsetGet(0)->offsetGet(0)->type()->isNotWhiteSpaceOrControl()); + $this->assertTrue($lexemeList->offsetGet(1)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(2)->offsetGet(0)->type()->isNotWhiteSpaceOrControl()); + $this->assertTrue($lexemeList->offsetGet(3)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(4)->hasType(LexemeType::optional())); + $this->assertTrue($lexemeList->offsetGet(4)->offsetGet(0)->type()->isNonFoldedLiteral()); + $this->assertTrue($lexemeList->offsetGet(5)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(6)->offsetGet(0)->type()->isNotWhiteSpaceOrControl()); + $this->assertTrue($lexemeList->offsetGet(7)->offsetGet(0)->type()->isWhiteSpace()); + $this->assertTrue($lexemeList->offsetGet(8)->offsetGet(0)->type()->isNotWhiteSpaceOrControl()); + $this->assertTrue($lexemeList->offsetGet(9)->offsetGet(0)->type()->isDot()); + $this->assertTrue($lexemeList->offsetGet(10)->offsetGet(0)->type()->isEndOfLine()); + $this->assertEquals(86, $this->octetCount($lexemeList)); + + // test field + $fieldTest = StringIterator::withLiteral("\x0D\x0A".'MIME-Version: 1.0'."\x0D\x0A"); + $tokenlist = $tokenizer->parse($fieldTest); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList->offsetGet(0)->hasType(LexemeType::newLine())); + $this->assertTrue($lexemeList->offsetGet(1)->hasType(LexemeType::fieldHeader())); + $this->assertTrue($lexemeList->offsetGet(2)->hasType(LexemeType::fieldBody())); + $this->assertTrue($lexemeList->offsetGet(3)->hasType(LexemeType::newLine())); + $this->assertEquals(21, $this->octetCount($lexemeList)); + + $fieldTest = StringIterator::withLiteral( + "\x0d\x0A".'MIME-Version: 1.0'."\x0d\x0A" + .'Subject: Test Email: Annalise Luettgen'."\x0d\x0A" + ); + + $tokenlist = $tokenizer->parse($fieldTest); + $lexemeList = $object->parse($tokenlist); + $this->assertTrue($lexemeList->offsetGet(0)->hasType(LexemeType::newLine())); + $this->assertTrue($lexemeList->offsetGet(1)->hasType(LexemeType::fieldHeader())); + $this->assertTrue($lexemeList->offsetGet(2)->hasType(LexemeType::fieldBody())); + $this->assertTrue($lexemeList->offsetGet(3)->hasType(LexemeType::newLine())); + $this->assertTrue($lexemeList->offsetGet(4)->hasType(LexemeType::fieldHeader())); + $this->assertTrue($lexemeList->offsetGet(5)->hasType(LexemeType::fieldBody())); + $this->assertTrue($lexemeList->offsetGet(6)->hasType(LexemeType::newLine())); + $this->assertEquals(61, $this->octetCount($lexemeList)); + + $notFieldTest = StringIterator::withLiteral( + 'MIME-Version: 1.0'."\x0d\x0A" + ); + $tokenlist = $tokenizer->parse($notFieldTest); + $lexemeList = $object->parse($tokenlist); + $this->assertEquals(19, $this->octetCount($lexemeList)); + + + $notFieldTest = StringIterator::withLiteral( + ' Version: 1.0'."\x0d\x0A" + ); + $tokenlist = $tokenizer->parse($notFieldTest); + $lexemeList = $object->parse($tokenlist); + $this->assertEquals(15, $this->octetCount($lexemeList)); + + + + $notFieldTest = StringIterator::withLiteral( + "\x0d\x0A".'a : 1.0'."\x0d\x0A" + ); + $tokenlist = $tokenizer->parse($notFieldTest); + $lexemeList = $object->parse($tokenlist); + $this->assertEquals(11, $this->octetCount($lexemeList)); + + $notFieldTest = StringIterator::withLiteral( + "\x0d\x0A".':' + ); + $tokenlist = $tokenizer->parse($notFieldTest); + $lexemeList = $object->parse($tokenlist); + $this->assertEquals(3, $this->octetCount($lexemeList)); + + $notGroupFieldTest = StringIterator::withLiteral( + "\x0d\x0A".'X-Mailer: PHPMailer 6.0.5 (https://github.com/PHPMailer/PHPMailer)'."\x0D\x0A" + ); + $tokenlist = $tokenizer->parse($notGroupFieldTest); + $lexemeList = $object->parse($tokenlist); + $this->assertCount(4, $lexemeList); + $this->assertEquals(70, $this->octetCount($lexemeList)); + + // test noise reduction + $noise = StringIterator::withLiteral('"a"'."\x0D\x0A\x20\x20\x20\x20\x20\x20\x20\x20".'"b"'); + $tokenlist = $tokenizer->parse($noise); + $lexemeList = $object->parse($tokenlist); + $this->assertCount(2, $lexemeList); + $this->assertEquals(16, $this->octetCount($lexemeList)); + + $a = "\x0D\x0AContent-Type: multipart/mixed;\x0D\x0A boundary=\"b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s\"\x0D\x0A"; + $stringIterator = StringIterator::withLiteral($a); + $tokenlist = $tokenizer->parse($stringIterator); + $lexemeList = $object->parse($tokenlist); + $this->assertCount(4, $lexemeList); + $this->assertTrue($lexemeList->offsetGet(0)->hasType(LexemeType::newLine())); + $this->assertEquals("Content-Type", $lexemeList->offsetGet(1)->toString()); + $this->assertEquals(" multipart/mixed;boundary=\"b1_DJdjz17adcO1Vkuk6O7ioGY6JIiljLQAWVYV3MWgC7s\"", $lexemeList->offsetGet(2)->toString()); + $this->assertTrue($lexemeList->offsetGet(3)->hasType(LexemeType::newLine())); + $this->assertEquals(101, $this->octetCount($lexemeList)); + + // Line Length + // Lexemizer should strip out the line length tokens + $justRight = StringIterator::withLiteral(str_repeat("a", 998) . "\x0D\x0A"); + $tokenlist = $tokenizer->parse($justRight); + $lexemeList = $object->parse($tokenlist); + $this->assertCount(2, $lexemeList); + $this->assertCount(1, $lexemeList->offsetGet(0)); + $this->assertEquals(1000, $this->octetCount($lexemeList)); + + $justRecommended = StringIterator::withLiteral(str_repeat("a", 78) . "\x0D\x0A"); + $tokenlist = $tokenizer->parse($justRecommended); + $lexemeList = $object->parse($tokenlist); + $this->assertCount(2, $lexemeList); + $this->assertCount(1, $lexemeList->offsetGet(0)); + $this->assertEquals(80, $this->octetCount($lexemeList)); + + // test octet count + $response = StringIterator::withLiteral(file_get_contents(codecept_data_dir().'FETCH_BODY_HEADER.txt')); + $tokenlist = $tokenizer->parse($response); + $lexemeList = $object->parse($tokenlist); + $this->assertEquals(816, $this->octetCount($lexemeList)); + } +} diff --git a/tests/unit/SalesAgility/Imap/Manager/PhpImapExtensionManagerTest.php b/tests/unit/SalesAgility/Imap/Manager/PhpImapExtensionManagerTest.php new file mode 100755 index 0000000..8c6da1d --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Manager/PhpImapExtensionManagerTest.php @@ -0,0 +1,111 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Manager\PhpImapExtensionManager; +use \SalesAgility\Imap\ManagerFactory; + +class NativePhpImapManagerTest extends \Codeception\Test\Unit +{ + + public function test__construct() + { + $factory = \SalesAgility\Imap\ManagerFactory::instance(); + $object = new PhpImapExtensionManager($factory); + $this->assertInstanceOf(\SalesAgility\Imap\Manager\ManagerInterface::class, $object); + $this->assertInstanceOf(\SalesAgility\Pattern\ContainerAwareInterface::class, $object); + $this->assertInstanceOf(\SalesAgility\Imap\Pipeline\PipeLineAwareInterface::class, $object); + $this->assertInstanceOf(\Psr\Log\LoggerAwareInterface::class, $object); + } + + public function testSetTransporter() + { + $transporter = new \SalesAgility\Imap\Stream\PhpImapExtensionMessageTransporter(ManagerFactory::instance()); + $object = ManagerFactory::instance()->PhpImapExtensionManager(); + $object->setTransporter($transporter); + $reflection = new ReflectionClass(PhpImapExtensionManager::class); + $property = $reflection->getProperty('transporter'); + $property->setAccessible(true); + $this->assertEquals($transporter, $property->getValue($object)); + } + + public function testTransporter() + { + $transporter = new \SalesAgility\Imap\Stream\PhpImapExtensionMessageTransporter(ManagerFactory::instance()); + $object = ManagerFactory::instance()->PhpImapExtensionManager(); + $object->setTransporter($transporter); + $this->assertEquals($transporter, $object->transporter()); + } + + public function testCommand() + { + $object = ManagerFactory::instance()->PhpImapExtensionManager(); + $this->assertInstanceOf(\SalesAgility\Imap\CommandBuilder\PhpImapExtensionSupportedCommandsInterface::class, $object->command()); + } + + public function testRun() + { + $connection = new \SalesAgility\Imap\Stream\PhpImpExtensionConnection(ManagerFactory::instance()); + $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); + $property = $reflection->getProperty('messageReceived'); + $property->setAccessible(true); + $property->setValue( + $connection, + array('A1 OK nothing happened'."\x0A\x0D") + ); + + $object = ManagerFactory::instance()->PhpImapExtensionManager(); + $object->transporter()->setConnection($connection); + /** @var \SalesAgility\Imap\Response\Response $actual */ + $actual = $object->run($object->command()->logout()); + $this->assertEquals('OK', $actual->status()); + $this->assertEquals('A1 OK Logout completed.', $actual->message()->toString()); + $actualIncluded = $actual->included()->toString(); + $this->assertEquals('* BYE Logging out', $actualIncluded); + } + + public function testSetLogger() + { + $log = new \SalesAgility\Utility\PimapLogger(); + $manager = ManagerFactory::instance()->PhpImapExtensionManager(); + $manager->setLogger($log); + $reflection = new \ReflectionClass(PhpImapExtensionManager::class); + $property = $reflection->getProperty('log'); + $property->setAccessible(true); + $this->assertEquals($log, $property->getValue($manager)); + } + + public function testSetPipeLine() + { + $pipeline = new \SalesAgility\Imap\Pipeline\Pipeline(ManagerFactory::instance()); + $manager = ManagerFactory::instance()->PhpImapExtensionManager(); + $manager->setPipeLine($pipeline); + $reflection = new \ReflectionClass(PhpImapExtensionManager::class); + $property = $reflection->getProperty('pipeline'); + $property->setAccessible(true); + $this->assertEquals($pipeline, $property->getValue($manager)); + } + + public function testPipeline() + { + $pipeline = new \SalesAgility\Imap\Pipeline\Pipeline(ManagerFactory::instance()); + $manager = ManagerFactory::instance()->PhpImapExtensionManager(); + $manager->setPipeLine($pipeline); + $this->assertEquals($pipeline, $manager->pipeline()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Manager/PimapManagerTest.php b/tests/unit/SalesAgility/Imap/Manager/PimapManagerTest.php new file mode 100755 index 0000000..f662a87 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Manager/PimapManagerTest.php @@ -0,0 +1,380 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Manager\PimapManager; +use \SalesAgility\Imap\ManagerFactory; + +class PhpImapManagerTest extends \Codeception\Test\Unit +{ + + public function test__construct() + { + $factory = \SalesAgility\Imap\ManagerFactory::instance(); + $object = new PimapManager($factory); + $this->assertInstanceOf(\SalesAgility\Imap\Manager\ManagerInterface::class, $object); + $this->assertInstanceOf(\SalesAgility\Pattern\ContainerAwareInterface::class, $object); + $this->assertInstanceOf(\SalesAgility\Imap\Pipeline\PipeLineAwareInterface::class, $object); + $this->assertInstanceOf(\Psr\Log\LoggerAwareInterface::class, $object); + } + + public function testSetTransporter() + { + $transporter = new \SalesAgility\Imap\Stream\PhpImapExtensionMessageTransporter(ManagerFactory::instance()); + $object = ManagerFactory::instance()->PimapManager(); + $object->setTransporter($transporter); + $reflection = new ReflectionClass(PimapManager::class); + $property = $reflection->getProperty('transporter'); + $property->setAccessible(true); + $this->assertEquals($transporter, $property->getValue($object)); + } + + public function testTransporter() + { + $transporter = new \SalesAgility\Imap\Stream\PhpImapExtensionMessageTransporter(ManagerFactory::instance()); + $object = ManagerFactory::instance()->PimapManager(); + $object->setTransporter($transporter); + $this->assertEquals($transporter, $object->transporter()); + } + + public function testCommand() + { + $object = ManagerFactory::instance()->PimapManager(); + $this->assertInstanceOf(\SalesAgility\Imap\CommandBuilder\PimapSupportedCommandsInterface::class, $object->command()); + } + + public function testRun() + { + $connection = new \SalesAgility\Stream\MockConnection(); + $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); + $property = $reflection->getProperty('messageReceived'); + $property->setAccessible(true); + $property->setValue( + $connection, + array('A1 OK nothing happened'."\x0D\x0A") + ); + + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + /** @var \SalesAgility\Imap\Response\Response $actual */ + $actual = $object->run($object->command()->logout()); + $this->assertEquals('OK', $actual->status()); + $actualMessage = $actual->message()->toString(); + $this->assertEquals('A1 OK nothing happened', $actualMessage); + $this->assertEmpty($actual->included()->toString()); + } + + public function testSetLogger() + { + $log = new \SalesAgility\Utility\PimapLogger(); + $manager = ManagerFactory::instance()->PimapManager(); + $manager->setLogger($log); + $reflection = new \ReflectionClass(PimapManager::class); + $property = $reflection->getProperty('log'); + $property->setAccessible(true); + $this->assertEquals($log, $property->getValue($manager)); + } + + public function testSetPipeLine() + { + $pipeline = new \SalesAgility\Imap\Pipeline\Pipeline(ManagerFactory::instance()); + $manager = ManagerFactory::instance()->PimapManager(); + $manager->setPipeLine($pipeline); + $reflection = new \ReflectionClass(PimapManager::class); + $property = $reflection->getProperty('pipeline'); + $property->setAccessible(true); + $this->assertEquals($pipeline, $property->getValue($manager)); + } + + public function testPipeline() + { + $pipeline = new \SalesAgility\Imap\Pipeline\Pipeline(ManagerFactory::instance()); + $manager = ManagerFactory::instance()->PimapManager(); + $manager->setPipeLine($pipeline); + $this->assertEquals($pipeline, $manager->pipeline()); + } + + public function testIdle() + { + $connection = new \SalesAgility\Stream\MockConnection(); + $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); + $property = $reflection->getProperty('messageReceived'); + $property->setAccessible(true); + $property->setValue( + $connection, + array( + '+ idling'."\x0D\x0A", + '* 8 EXISTS'."\x0D\x0A", + 'A1 OK '."\x0D\x0A", + ) + ); + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + $idle = $object->command()->idle(); + $response = $object->run($idle); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Mailbox::class, $response); + } + + public function testSearch() + { + $connection = new \SalesAgility\Stream\MockConnection(); + $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); + $messagePosition = $reflection->getProperty('messagePosition'); + $messagePosition->setAccessible(true); + + $messageReceived = $reflection->getProperty('messageReceived'); + $messageReceived->setAccessible(true); + $searchResultFile = file_get_contents(codecept_data_dir('SEARCH_1_5.txt')); + $searchResultArray = explode("\r\n", $searchResultFile); + // remove last empty array element + array_pop($searchResultArray); + foreach ($searchResultArray as $item => $searchResult) { + $searchResultArray[$item] .= "\r\n"; + } + + $messagePosition->setValue($connection, 0); + $messageReceived->setValue( + $connection, + $searchResultArray + ); + + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + + $searchCommand = $object->command()->search()->withRange(1, 5)->build(); + $actual = $object->run($searchCommand); + $this->assertInstanceOf(\SalesAgility\Imap\Response\MessageList::class, $actual); + $this->assertCount(5, $actual); + + // Since the file only as A1 + // we need to reset these objects + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + $messagePosition->setValue($connection, 0); + $messageReceived->setValue( + $connection, + $searchResultArray + ); + + $searchCommand2 = $object->command()->uid()->search()->withRange(1, 5)->build(); + $actual = $object->run($searchCommand2); + $this->assertInstanceOf(\SalesAgility\Imap\Response\MessageList::class, $actual); + $this->assertCount(5, $actual); + } + + public function testFetch() + { + $connection = new \SalesAgility\Stream\MockConnection(); + $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); + $messagePosition = $reflection->getProperty('messagePosition'); + $messagePosition->setAccessible(true); + + $messageReceived = $reflection->getProperty('messageReceived'); + $messageReceived->setAccessible(true); + $fetchResultFile = file_get_contents(codecept_data_dir('FETCH_BODY_HEADER_WITH_RESPONSE.txt')); + $fetchResultArray = explode("\r\n", $fetchResultFile); + // remove last empty array element + array_pop($fetchResultArray); + foreach ($fetchResultArray as $item => $searchResult) { + $fetchResultArray[$item] .= "\r\n"; + } + + $messagePosition->setValue($connection, 0); + $messageReceived->setValue( + $connection, + $fetchResultArray + ); + + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + + $fetchCommand = $object->command()->fetch(1)->header()->build(); + $actual = $object->run($fetchCommand); + $this->assertInstanceOf(\SalesAgility\Imap\Response\MessageList::class, $actual); + $this->assertCount(1, $actual); + + // Since the file only as A1 + // we need to reset these objects + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + $messagePosition->setValue($connection, 0); + $messageReceived->setValue( + $connection, + $fetchResultArray + ); + + $fetchCommand2 = $object->command()->uid()->fetch(1)->header()->build(); + $actual = $object->run($fetchCommand2); + $this->assertInstanceOf(\SalesAgility\Imap\Response\MessageList::class, $actual); + $this->assertCount(1, $actual); + } + + public function testCopy() + { + $connection = new \SalesAgility\Stream\MockConnection(); + $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); + $messagePosition = $reflection->getProperty('messagePosition'); + $messagePosition->setAccessible(true); + + $messageReceived = $reflection->getProperty('messageReceived'); + $messageReceived->setAccessible(true); + + $messagePosition->setValue($connection, 0); + $messageReceived->setValue( + $connection, + array('A1 OK' . "\r\n") + ); + + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + + $command = $object->command()->copy()->withRange(1, 5)->toMailbox('INVOICES')->build(); + $actual = $object->run($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + + $messagePosition->setValue($connection, 0); + $messageReceived->setValue( + $connection, + array('A2 OK' . "\r\n") + ); + + $command2 = $object->command()->uid()->copy()->withRange(1, 5)->toMailbox('INVOICES')->build(); + $actual = $object->run($command2); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + } + + public function testStatus() + { + $connection = new \SalesAgility\Stream\MockConnection(); + $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); + $messagePosition = $reflection->getProperty('messagePosition'); + $messagePosition->setAccessible(true); + + $messageReceived = $reflection->getProperty('messageReceived'); + $messageReceived->setAccessible(true); + + $messagePosition->setValue($connection, 0); + $messageReceived->setValue( + $connection, + array('A1 OK' . "\r\n") + ); + + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + + $command = $object->command()->status('INVOICES')->withRecent()->build(); + $actual = $object->run($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Mailbox::class, $actual); + } + + public function testSelect() + { + $connection = new \SalesAgility\Stream\MockConnection(); + $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); + $messagePosition = $reflection->getProperty('messagePosition'); + $messagePosition->setAccessible(true); + + $messageReceived = $reflection->getProperty('messageReceived'); + $messageReceived->setAccessible(true); + + $messagePosition->setValue($connection, 0); + $messageReceived->setValue( + $connection, + array('A1 OK' . "\r\n") + ); + + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + + $command = $object->command()->select('INVOICES')->build(); + $actual = $object->run($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Mailbox::class, $actual); + } + + public function testExamine() + { + $connection = new \SalesAgility\Stream\MockConnection(); + $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); + $messagePosition = $reflection->getProperty('messagePosition'); + $messagePosition->setAccessible(true); + + $messageReceived = $reflection->getProperty('messageReceived'); + $messageReceived->setAccessible(true); + + $messagePosition->setValue($connection, 0); + $messageReceived->setValue( + $connection, + array('A1 OK' . "\r\n") + ); + + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + + $command = $object->command()->examine('INVOICES')->build(); + $actual = $object->run($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Mailbox::class, $actual); + } + + public function testList() + { + $connection = new \SalesAgility\Stream\MockConnection(); + $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); + $messagePosition = $reflection->getProperty('messagePosition'); + $messagePosition->setAccessible(true); + + $messageReceived = $reflection->getProperty('messageReceived'); + $messageReceived->setAccessible(true); + + $messagePosition->setValue($connection, 0); + $messageReceived->setValue( + $connection, + array('A1 OK' . "\r\n") + ); + + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + + $command = $object->command()->listMailbox()->build(); + $actual = $object->run($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\MailboxList::class, $actual); + } + + public function testSubList() + { + $connection = new \SalesAgility\Stream\MockConnection(); + $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); + $messagePosition = $reflection->getProperty('messagePosition'); + $messagePosition->setAccessible(true); + + $messageReceived = $reflection->getProperty('messageReceived'); + $messageReceived->setAccessible(true); + + $messagePosition->setValue($connection, 0); + $messageReceived->setValue( + $connection, + array('A1 OK' . "\r\n") + ); + + $object = ManagerFactory::instance()->PimapManager(); + $object->transporter()->setConnection($connection); + + $command = $object->command()->listSubsetMailbox()->build(); + $actual = $object->run($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\MailboxList::class, $actual); + } +} diff --git a/tests/unit/SalesAgility/Imap/Pipeline/PipeTest.php b/tests/unit/SalesAgility/Imap/Pipeline/PipeTest.php new file mode 100755 index 0000000..b1b8e8a --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Pipeline/PipeTest.php @@ -0,0 +1,257 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Pipeline\Pipe; +use SalesAgility\Imap\CommandBuilder\PimapCommandBuilder; + + +class PipeTest extends \Codeception\Test\Unit +{ + /** @var UnitTester $tester */ + protected $tester; + + /** + * @throws ReflectionException + */ + public function test__construct() + { + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + $reflection = new \ReflectionClass(Pipe::class); + $propertyTag = $reflection->getProperty('tag'); + $propertyTag->setAccessible(true); + $propertyPipe = $reflection->getProperty('command'); + $propertyPipe->setAccessible(true); + $propertyResponse = $reflection->getProperty('response'); + $propertyResponse->setAccessible(true); + + $this->assertEquals( + 'A1', + $propertyTag->getValue($class), + 'Missing Requirement: Each command must prefixed with a tag.' + . ' A Pipe in a Pipeline must record the tag, as commands can may be processed in a batch' + ); + + $this->assertEquals( + $command, + $propertyPipe->getValue($class), + 'Missing Requirement: A Pipe in a Pipeline must record the raw/full command sent' + ); + + $this->assertEquals( + 'array', + gettype($propertyResponse->getValue($class)), + 'Missing Requirement: A Pipe in a Pipeline must be able to store multiple raw responses from a command' + ); + + $this->assertEmpty( + $propertyResponse->getValue($class), + 'Missing Requirement: The responses must initially be a empty array' + ); + + // Test instantiations + $this->tester->expectException( + new \Exception('$tag must be a string eg A1'), + function () use ($command) { + new Pipe(array(), $command); + }); + + $this->tester->expectException( + new \Exception('$tag must not be empty'), + function () use ($command) { + new Pipe('', $command); + }); + } + + /** + * @throws ReflectionException + */ + public function testAddResponse() + { + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + $reflection = new \ReflectionClass(Pipe::class); + $propertyTag = $reflection->getProperty('tag'); + $propertyTag->setAccessible(true); + $propertyPipe = $reflection->getProperty('command'); + $propertyPipe->setAccessible(true); + $propertyResponse = $reflection->getProperty('response'); + $propertyResponse->setAccessible(true); + + $this->tester->expectException( + new \Exception('$response must be a string eg A1 OK'), + function () use ($class) { + $class->addResponse(1); + }); + + $class->addResponse('A1 OK'); + $this->assertEquals('A1 OK', $propertyResponse->getValue($class)[0]); + } + + public function testGetResponse() + { + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + $reflection = new \ReflectionClass(Pipe::class); + $propertyTag = $reflection->getProperty('tag'); + $propertyTag->setAccessible(true); + $propertyPipe = $reflection->getProperty('command'); + $propertyPipe->setAccessible(true); + $propertyResponse = $reflection->getProperty('response'); + $propertyResponse->setAccessible(true); + + $class->addResponse('A1 OK'); + $this->assertEquals('A1 OK', $class->getResponse()); + } + + /** + * @throws ReflectionException + */ + public function testGetTag() + { + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + $reflection = new \ReflectionClass(Pipe::class); + $propertyTag = $reflection->getProperty('tag'); + $propertyTag->setAccessible(true); + $propertyPipe = $reflection->getProperty('command'); + $propertyPipe->setAccessible(true); + $propertyResponse = $reflection->getProperty('response'); + $propertyResponse->setAccessible(true); + $this->assertEquals("A1", $class->getTag()); + } + + public function testBuildCommand() + { + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + $reflection = new \ReflectionClass(Pipe::class); + $propertyTag = $reflection->getProperty('tag'); + $propertyTag->setAccessible(true); + $propertyPipe = $reflection->getProperty('command'); + $propertyPipe->setAccessible(true); + $propertyResponse = $reflection->getProperty('response'); + $propertyResponse->setAccessible(true); + $this->assertEquals("A1 NOOP", $class->buildCommand()); + } + + + public function testAddTokenList() + { + $tokenList = new \SalesAgility\Imap\Token\TokenList(); + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + $reflection = new \ReflectionClass(Pipe::class); + $property = $reflection->getProperty('tokenList'); + $property->setAccessible(true); + $class->addTokenList($tokenList); + $this->assertEquals($tokenList, $property->getValue($class)); + } + + public function testTokenList() + { + $tokenList = new \SalesAgility\Imap\Token\TokenList(); + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + + $class->addTokenList($tokenList); + $this->assertEquals($tokenList, $class->tokenList()); + } + + public function testIsTokenized() + { + $tokenList = new \SalesAgility\Imap\Token\TokenList(); + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + $this->assertFalse($class->isTokenized()); + $class->addTokenList($tokenList); + $this->assertTrue($class->isTokenized()); + } + + + public function testAddLexemeList() + { + $lexemeList = new \SalesAgility\Imap\Lexeme\LexemeList(); + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + $reflection = new \ReflectionClass(Pipe::class); + $property = $reflection->getProperty('lexemeList'); + $property->setAccessible(true); + $class->addLexemeList($lexemeList); + $this->assertEquals($lexemeList, $property->getValue($class)); + } + + public function testLexemeList() + { + $lexemeList = new \SalesAgility\Imap\Lexeme\LexemeList(); + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + + $class->addLexemeList($lexemeList); + $this->assertEquals($lexemeList, $class->lexemeList()); + } + + public function testIsLexemized() + { + $lexemeList = new \SalesAgility\Imap\Lexeme\LexemeList(); + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + $this->assertFalse($class->isLexemized()); + $class->addLexemeList($lexemeList); + $this->assertTrue($class->isLexemized()); + } + + public function testGetCommand() + { + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipe('A1', $command); + $this->assertEquals($command, $class->getCommand()); + } + + public function testAddParsed() + { + $reflection = new \ReflectionClass(Pipe::class); + /** @var ReflectionProperty $property */ + $property = $reflection->getProperty('parsed'); + $property->setAccessible(true); + + $command = PimapCommandBuilder::instance()->noop(); + $includedMessage = \SalesAgility\Iteration\StringIterator::withLiteral('', 0, 0); + $responseMessage = \SalesAgility\Iteration\StringIterator::withLiteral( 'A1 OK '."\r\n"); + $response = new SalesAgility\Imap\Response\Response('OK', $responseMessage, $includedMessage); + $pipe = new Pipe('A1', $command); + + + $pipe->addParsed($response); + $expected = $property->getValue($pipe); + $this->assertEquals($response, $expected); + } + + public function testParsed() + { + $command = PimapCommandBuilder::instance()->noop(); + $includedMessage = \SalesAgility\Iteration\StringIterator::withLiteral('', 0, 0); + $responseMessage = \SalesAgility\Iteration\StringIterator::withLiteral( 'A1 OK '."\r\n"); + $response = new SalesAgility\Imap\Response\Response('OK', $responseMessage, $includedMessage); + $pipe = new Pipe('A1', $command); + $pipe->addParsed($response); + $this->assertEquals($response, $pipe->parsed()); + } + +} diff --git a/tests/unit/SalesAgility/Imap/Pipeline/PipelineTest.php b/tests/unit/SalesAgility/Imap/Pipeline/PipelineTest.php new file mode 100755 index 0000000..6492797 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Pipeline/PipelineTest.php @@ -0,0 +1,172 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Pipeline\Pipeline; +use SalesAgility\Imap\Pipeline\Pipe; +use \SalesAgility\Imap\ManagerFactory; +use SalesAgility\Imap\CommandBuilder\PimapCommandBuilder; +class PipelineTest extends \Codeception\Test\Unit +{ + /** + * @throws ReflectionException + */ + public function test__construct() + { + $class = new Pipeline(\SalesAgility\Imap\ManagerFactory::instance()); + $reflection = new \ReflectionClass(Pipeline::class); + $propertyPipes = $reflection->getProperty('pipes'); + $propertyPipes->setAccessible(true); + $this->assertEmpty(array(), $propertyPipes->getValue($class)); + } + + /** + * + */ + public function testAdd() + { + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipeline(\SalesAgility\Imap\ManagerFactory::instance()); + $class->add($command); + $c1 = $class->pipes()[0]; + $this->assertEquals('A1', $c1->getTag()); + } + + /** + * @throws ReflectionException + */ + public function testGetPipes() + { + $noop = PimapCommandBuilder::instance()->noop(); + $logout = PimapCommandBuilder::instance()->logout(); + $class = new Pipeline(ManagerFactory::instance()); + $reflection = new \ReflectionClass(Pipeline::class); + $propertyPipes = $reflection->getProperty('pipes'); + $propertyPipes->setAccessible(true); + $class->add($noop); + $class->add($logout); + + $c1 = $class->pipes()[0]; + $c2 = $class->pipes()[1]; + $this->assertEquals('A1', $c1->getTag()); + $this->assertEquals('A2', $c2->getTag()); + } + + public function testCurrent() { + + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipeline(ManagerFactory::instance()); + $class->add($command); + $c1 = $class->pipes()[0]; + $this->assertEquals($c1, $class->current()); + } + + public function testNext() + { + $command1 = PimapCommandBuilder::instance()->noop(); + $command2 = PimapCommandBuilder::instance()->logout(); + $class = new Pipeline(ManagerFactory::instance()); + $class->add($command1); + $class->add($command2); + $c1 = $class->pipes()[0]; + $c2 = $class->pipes()[1]; + $this->assertEquals($c1, $class->current()); + $class->next(); + $this->assertEquals($c2, $class->current()); + } + + public function testKey() + { + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipeline(ManagerFactory::instance()); + $class->add($command); + $c1 = $class->pipes()[0]; + $this->assertEquals(0, $class->key()); + } + + public function testValid() + { + $command = PimapCommandBuilder::instance()->noop(); + $class = new Pipeline(ManagerFactory::instance()); + $class->add($command); + $c1 = $class->pipes()[0]; + $this->assertTrue($class->valid()); + $class->next(); + $this->assertFalse($class->valid()); + } + + public function testRewind() + { + $command1 = PimapCommandBuilder::instance()->noop(); + $command2 = PimapCommandBuilder::instance()->logout(); + $class = new Pipeline(ManagerFactory::instance()); + $class->add($command1); + $class->add($command2); + $c1 = $class->pipes()[0]; + $c2 = $class->pipes()[1]; + $class->next(); + $class->rewind(); + $this->assertEquals($c1, $class->current()); + } + + public function testGetLastPipe() + { + $command1 = PimapCommandBuilder::instance()->noop(); + $command2 = PimapCommandBuilder::instance()->logout(); + $class = new Pipeline(ManagerFactory::instance()); + $class->add($command1); + $class->add($command2); + + $actual = $class->getLastPipe(); + $this->assertInstanceOf(Pipe::class, $actual); + $this->assertEquals('A2 LOGOUT', $actual->buildCommand()); + } + + public function testPipeByCommand() + { + $command1 = PimapCommandBuilder::instance()->noop()->build(); + $command2 = PimapCommandBuilder::instance()->logout()->build(); + $class = new Pipeline(ManagerFactory::instance()); + $class->add($command1); + $class->add($command2); + + $actual = $class->pipeByCommand($command2); + $this->assertInstanceOf(Pipe::class, $actual); + + $command3 = PimapCommandBuilder::instance()->login()->user('user')->password('secret')->build(); + $actual = $class->pipeByCommand($command3); + $this->assertNull($actual); + } + + public function testMergePipeline() + { + $command1 = PimapCommandBuilder::instance()->delete('INVOICES')->build(); + $pipeline1 = new Pipeline(ManagerFactory::instance()); + $pipeline1->add($command1); + + $command2 = PimapCommandBuilder::instance()->delete('QUOTES')->build(); + $pipeline2 = new Pipeline(ManagerFactory::instance()); + $pipeline2->add($command2); + + $pipeline1->mergePipeline($pipeline2); + $lastPipe = $pipeline1->pipeByCommand($command2); + $this->assertInstanceOf(Pipe::class, $lastPipe); + $this->assertEquals('DELETE', $lastPipe->getCommand()->command()); + $this->assertEquals(array('MAILBOX' => 'QUOTES'), $lastPipe->getCommand()->commandArguments()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Response/CapabilityTest.php b/tests/unit/SalesAgility/Imap/Response/CapabilityTest.php new file mode 100644 index 0000000..b6f8bd4 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Response/CapabilityTest.php @@ -0,0 +1,55 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Response\Capability; + +class CapabilityTest extends \Codeception\Test\Unit +{ + + public function testOffsetGet() + { + $object = new Capability(); + $object['imap'] = true; + self::assertEquals(true, $object->offsetGet('imap')); + } + + public function testOffsetExists() + { + $object = new Capability(); + $object['imap'] = true; + self::assertTrue($object->offsetExists('imap')); + self::assertFalse($object->offsetExists('test')); + } + + public function testOffsetSet() + { + $object = new Capability(); + $object->offsetSet('imap', true); + self::assertTrue($object->offsetExists('imap')); + self::assertFalse($object->offsetExists('test')); + } + + public function testOffsetUnset() + { + $object = new Capability(); + $object->offsetSet('imap', true); + $object->offsetUnset('imap'); + self::assertFalse($object->offsetExists('imap')); + } +} diff --git a/tests/unit/SalesAgility/Imap/Response/MailboxListTest.php b/tests/unit/SalesAgility/Imap/Response/MailboxListTest.php new file mode 100644 index 0000000..5abe67b --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Response/MailboxListTest.php @@ -0,0 +1,153 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Response\MailboxList; + +class MailboxListTest extends \Codeception\Test\Unit +{ + + /** @var UnitTester $tester */ + protected $tester; + public function testRewind() + { + $object = new MailboxList(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object->next(); + $object->next(); + $object->next(); + $object->rewind(); + $this->assertEquals(0, $object->key()); + } + + public function testOffsetSet() + { + $object = new MailboxList(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object->next(); + $object->next(); + $this->assertFalse($object->valid()); + + + $this->tester->expectException( + new \InvalidArgumentException('Mailbox List can only store integer key values'), + function () { + $object = new MailboxList(); + $object['foo'] = 1; + } + ); + + $this->tester->expectException( + new \InvalidArgumentException('Mailbox List can only store values which derive from a Mailbox'), + function () { + $object = new MailboxList(); + $object[] = new \InvalidArgumentException('Mailbox List can only store values which derive from a Mailbox'); + } + ); + } + + public function testOffsetUnset() + { + $object = new MailboxList(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + + $object->offsetUnset(0); + $this->assertFalse($object->offsetExists(2)); + } + + public function testKey() + { + $object = new MailboxList(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $this->assertEquals(0, $object->key()); + $object->next(); + $this->assertEquals(1, $object->key()); + $object->next(); + $this->assertEquals(2, $object->key()); + $object->next(); + } + + public function testCurrent() + { + $object = new MailboxList(); + + $mailbox = new \SalesAgility\Imap\Response\Mailbox(); + $mailbox->offsetSet('name', 'Invoices'); + + $object[] =$mailbox; + + $this->assertEquals($mailbox, $object->current()); + } + + public function testNext() + { + $object = new MailboxList(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $this->assertEquals(0, $object->key()); + $object->next(); + $this->assertEquals(1, $object->key()); + } + + public function testOffsetGet() + { + $object = new MailboxList(); + + $mailbox = new \SalesAgility\Imap\Response\Mailbox(); + $mailbox->offsetSet('name', 'Invoices'); + + $object[] =$mailbox; + + $this->assertEquals($mailbox, $object->offsetGet(0)); + } + + public function testValid() + { + $object = new MailboxList(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $this->assertTrue($object->valid()); + $object->next(); + $this->assertTrue($object->valid()); + $object->next(); + $this->assertTrue($object->valid()); + $object->next(); + $this->assertFalse($object->valid()); + } + + public function testOffsetExists() + { + $object = new MailboxList(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $object[] = new \SalesAgility\Imap\Response\Mailbox(); + $this->assertTrue($object->offsetExists(0)); + $this->assertTrue($object->offsetExists(1)); + $this->assertTrue($object->offsetExists(2)); + $this->assertFalse($object->offsetExists(3)); + } +} diff --git a/tests/unit/SalesAgility/Imap/Response/MailboxTest.php b/tests/unit/SalesAgility/Imap/Response/MailboxTest.php new file mode 100644 index 0000000..ba7bc8d --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Response/MailboxTest.php @@ -0,0 +1,164 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Response\Mailbox; + +class MailboxTest extends \Codeception\Test\Unit +{ + + public function testAttributes() + { + $object = new Mailbox(); + $this->assertEquals(array(), $object->attributes()); + } + + public function testName() + { + $object = new Mailbox(); + $this->assertEquals('', $object->name()); + } + + public function testOffsetExists() + { + $object = new Mailbox(); + + $this->assertTrue($object->offsetExists('flags')); + $this->assertTrue($object->offsetExists('attributes')); + $this->assertTrue($object->offsetExists('hierarchy')); + $this->assertTrue($object->offsetExists('name')); + $this->assertTrue($object->offsetExists('exists')); + $this->assertTrue($object->offsetExists('recent')); + $this->assertTrue($object->offsetExists('unseen')); + $this->assertTrue($object->offsetExists('uidvalidity')); + $this->assertTrue($object->offsetExists('uidnext')); + $this->assertFalse($object->offsetExists('test')); + } + + public function testOffsetSet() + { + $object = new Mailbox(); + + $object->offsetSet('flags', 'Answered'); + $object->offsetSet('attributes', 'Noselect'); + $object->offsetSet( 'hierarchy', '/'); + $object->offsetSet('name', 'Invoices'); + $object->offsetSet('exists', '1'); + $object->offsetSet('recent', '1'); + $object->offsetSet('unseen', '1'); + $object->offsetSet('uidvalidity', '1528185994'); + $object->offsetSet('uidnext', '2'); + + $this->assertEquals(array('Answered'), $object->offsetGet('flags')); + $this->assertEquals(array('Noselect'), $object->offsetGet('attributes')); + $this->assertEquals('/', $object->offsetGet('hierarchy')); + $this->assertEquals('Invoices', $object->offsetGet('name')); + $this->assertEquals('1', $object->offsetGet('exists')); + $this->assertEquals('1', $object->offsetGet('recent')); + $this->assertEquals('1', $object->offsetGet('unseen')); + $this->assertEquals('1528185994', $object->offsetGet('uidvalidity')); + $this->assertEquals('2', $object->offsetGet('uidnext')); + } + + public function testUidNext() + { + $object = new Mailbox(); + $this->assertEquals('', $object->uidNext()); + } + + public function testFlags() + { + $object = new Mailbox(); + $this->assertEquals(array(), $object->flags()); + } + + public function testRecent() + { + $object = new Mailbox(); + $this->assertEquals('', $object->name()); + } + + public function testOffsetGet() + { + $object = new Mailbox(); + + $object->offsetSet('uidnext', '2'); + $this->assertEquals('2', $object->offsetGet('uidnext')); + + } + + public function testUnseen() + { + $object = new Mailbox(); + $this->assertEquals('', $object->unseen()); + } + + public function testUidValidity() + { + $object = new Mailbox(); + $this->assertEquals('', $object->uidValidity()); + } + + public function testHierarchy() + { + $object = new Mailbox(); + $this->assertEquals('', $object->hierarchy()); + } + + public function testExists() + { + $object = new Mailbox(); + $this->assertEquals('', $object->exists()); + } + + public function testOffsetUnset() + { + $object = new Mailbox(); + + $object->offsetSet('flags', 'Answered'); + $object->offsetSet('attributes', 'Noselect'); + $object->offsetSet( 'hierarchy', '/'); + $object->offsetSet('name', 'Invoices'); + $object->offsetSet('exists', '1'); + $object->offsetSet('recent', '1'); + $object->offsetSet('unseen', '1'); + $object->offsetSet('uidvalidity', '1528185994'); + $object->offsetSet('uidnext', '2'); + + $object->offsetUnset('flags'); + $object->offsetUnset('attributes'); + $object->offsetUnset( 'hierarchy'); + $object->offsetUnset('name'); + $object->offsetUnset('exists'); + $object->offsetUnset('recent'); + $object->offsetUnset('unseen'); + $object->offsetUnset('uidvalidity'); + $object->offsetUnset('uidnext'); + $object->offsetUnset('test'); + + $this->assertEquals(array(), $object->offsetGet('flags')); + $this->assertEquals(array(), $object->offsetGet('attributes')); + $this->assertEquals('', $object->offsetGet('hierarchy')); + $this->assertEquals('', $object->offsetGet('name')); + $this->assertEquals('', $object->offsetGet('exists')); + $this->assertEquals('', $object->offsetGet('recent')); + $this->assertEquals('', $object->offsetGet('unseen')); + $this->assertEquals('', $object->offsetGet('uidvalidity')); + $this->assertEquals('', $object->offsetGet('uidnext')); + } +} diff --git a/tests/unit/SalesAgility/Imap/Response/MessageAttachmentStructureTest.php b/tests/unit/SalesAgility/Imap/Response/MessageAttachmentStructureTest.php new file mode 100755 index 0000000..8f6d197 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Response/MessageAttachmentStructureTest.php @@ -0,0 +1,115 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Response\MessageAttachmentStructure; + +class MessageAttachmentStructureTest extends \Codeception\Test\Unit +{ + /** + * @var UnitTester + */ + protected $tester; + + public function testOffsetExists() + { + $class = new MessageAttachmentStructure(); + $this->assertTrue($class->offsetExists('type')); + $this->assertTrue($class->offsetExists('name')); + $this->assertTrue($class->offsetExists('size')); + $this->assertFalse($class->offsetExists('test')); + } + + public function testOffsetGet() + { + $class = new MessageAttachmentStructure(); + $this->assertEquals('', $class->offsetGet('type')); + $this->assertEquals('', $class->offsetGet('name')); + $this->assertEquals('', $class->offsetGet('size')); + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function() { + $class = new MessageAttachmentStructure(); + $class->offsetGet('test'); + } + ); + } + + public function testOffsetSet() + { + $class = new MessageAttachmentStructure(); + $class->offsetSet('type', 'text/plain'); + $this->assertEquals('text/plain', $class->offsetGet('type')); + $class->offsetSet('name', 'example.txt'); + $this->assertEquals('example.txt', $class->offsetGet('name')); + $class->offsetSet('size', 2567); + $this->assertEquals(2567, $class->offsetGet('size')); + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function() { + $class = new MessageAttachmentStructure(); + $class->offsetSet('test', 1); + } + ); + } + + public function testOffsetUnset() + { + $class = new MessageAttachmentStructure(); + $class->offsetSet('type', 'text/plain'); + $class->offsetSet('name', 'example.txt'); + $class->offsetSet('size', 2567); + + $class->offsetUnset('type'); + $class->offsetUnset('name'); + $class->offsetUnset('size'); + + $this->assertEquals('', $class->offsetGet('type')); + $this->assertEquals('', $class->offsetGet('name')); + $this->assertEquals('', $class->offsetGet('size')); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function() { + $class = new MessageAttachmentStructure(); + $class->offsetUnset('test'); + } + ); + } + + public function testType() + { + $class = new MessageAttachmentStructure(); + $class->offsetSet('type', 'text/plain'); + $this->assertEquals('text/plain', $class->type()); + } + + public function testSize() + { + $class = new MessageAttachmentStructure(); + $class->offsetSet('size', 2567); + $this->assertEquals(2567, $class->size()); + } + + public function testName() + { + $class = new MessageAttachmentStructure(); + $class->offsetSet('name', 'example.txt'); + $this->assertEquals('example.txt', $class->name()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Response/MessageAttachmentTest.php b/tests/unit/SalesAgility/Imap/Response/MessageAttachmentTest.php new file mode 100755 index 0000000..7a2b811 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Response/MessageAttachmentTest.php @@ -0,0 +1,150 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Response\MessageAttachment; + +class MessageAttachmentTest extends \Codeception\Test\Unit +{ + /** + * @var UnitTester + */ + protected $tester; + + public function testOffsetExists() + { + $class = new MessageAttachment(); + $this->assertTrue($class->offsetExists('structure')); + $this->assertTrue($class->offsetExists('hasContent')); + $this->assertTrue($class->offsetExists('content')); + $this->assertTrue($class->offsetExists('isInline')); + $this->assertTrue($class->offsetExists('contentId')); + $this->assertFalse($class->offsetExists('test')); + } + + public function testOffsetGet() + { + $class = new MessageAttachment(); + + $this->assertEquals(null, $class->offsetGet('structure')); + $this->assertEquals(false, $class->offsetGet('hasContent')); + $this->assertEquals('', $class->offsetGet('content')); + $this->assertEquals(false, $class->offsetGet('isInline')); + $this->assertEquals('', $class->offsetGet('contentId')); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageAttachment(); + $class->offsetGet('test'); + } + ); + } + + public function testOffsetSet() + { + $class = new MessageAttachment(); + + $value = new \SalesAgility\Imap\Response\MessageAttachmentStructure(); + $class->offsetSet('structure', $value); + $this->assertEquals($value, $class->offsetGet('structure')); + + $class->offsetSet('hasContent', true); + $this->assertEquals(true, $class->offsetGet('hasContent')); + + $class->offsetSet('content', 'plain'); + $this->assertEquals('plain', $class->offsetGet('content')); + + $class->offsetSet('isInline', true); + $this->assertEquals(true, $class->offsetGet('isInline')); + + $class->offsetSet('contentId', '118d0d188bc73ba6dbbd265f1980a359'); + $this->assertEquals('118d0d188bc73ba6dbbd265f1980a359', $class->offsetGet('contentId')); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageAttachment(); + $class->offsetSet('test', 1); + } + ); + } + + public function testOffsetUnset() + { + $class = new MessageAttachment(); + + $value = new \SalesAgility\Imap\Response\MessageAttachmentStructure(); + $class->offsetSet('structure', $value); + $class->offsetSet('hasContent', true); + $class->offsetSet('content', 'plain'); + $class->offsetSet('isInline', true); + $class->offsetSet('contentId', '118d0d188bc73ba6dbbd265f1980a359'); + + + $class->offsetUnset('structure'); + $class->offsetUnset('hasContent'); + $class->offsetUnset('content'); + $class->offsetUnset('isInline'); + $class->offsetUnset('contentId'); + + $this->assertEquals(null, $class->offsetGet('structure')); + $this->assertEquals(false, $class->offsetGet('hasContent')); + $this->assertEquals('', $class->offsetGet('content')); + $this->assertEquals(false, $class->offsetGet('isInline')); + $this->assertEquals('', $class->offsetGet('contentId')); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageAttachment(); + $class->offsetUnset('test'); + } + ); + } + + public function testStructure() + { + $class = new MessageAttachment(); + $this->assertEquals(null, $class->structure()); + } + + public function testHasContent() + { + $class = new MessageAttachment(); + $this->assertEquals(false, $class->hasContent()); + } + + public function testContent() + { + $class = new MessageAttachment(); + $this->assertEquals('', $class->content()); + } + + public function testIsInline() + { + $class = new MessageAttachment(); + $this->assertEquals(false, $class->isInline()); + } + + public function testConentId() + { + $class = new MessageAttachment(); + $this->assertEquals('', $class->contentId()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Response/MessageBodyStructureTest.php b/tests/unit/SalesAgility/Imap/Response/MessageBodyStructureTest.php new file mode 100755 index 0000000..cc2039e --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Response/MessageBodyStructureTest.php @@ -0,0 +1,139 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Response\MessageBodyStructure; + +class MessageBodyStructureTest extends \Codeception\Test\Unit +{ + /** + * @var UnitTester + */ + protected $tester; + + public function testOffsetExists() + { + $class = new MessageBodyStructure(); + $this->assertTrue($class->offsetExists('plain')); + $this->assertTrue($class->offsetExists('html')); + $this->assertTrue($class->offsetExists('attachments')); + $this->assertTrue($class->offsetExists('mimeVersion')); + $this->assertTrue($class->offsetExists('contentType')); + $this->assertTrue($class->offsetExists('contentTransferEncoding')); + $this->assertFalse($class->offsetExists('test')); + } + + public function testOffsetGet() + { + $class = new MessageBodyStructure(); + $this->assertEquals(false, $class->offsetGet('plain')); + $this->assertEquals(false, $class->offsetGet('html')); + $this->assertEquals(false, $class->offsetGet('attachments')); + $this->assertEquals('', $class->offsetGet('mimeVersion')); + $this->assertEquals('', $class->offsetGet('contentType')); + $this->assertEquals('', $class->offsetGet('contentTransferEncoding')); + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageBodyStructure(); + $class->offsetGet('test'); + } + ); + } + + public function testOffsetSet() + { + $class = new MessageBodyStructure(); + $class->offsetSet('plain', true); + $this->assertEquals(true, $class->offsetGet('plain')); + $class->offsetSet('html', true); + $this->assertEquals(true, $class->offsetGet('html')); + $class->offsetSet('attachments', true); + $this->assertEquals(true, $class->offsetGet('attachments')); + $class->offsetSet('mimeVersion', '1.0'); + $this->assertEquals('1.0', $class->offsetGet('mimeVersion')); + $class->offsetSet('contentType', 'text/plain'); + $this->assertEquals('text/plain', $class->offsetGet('contentType')); + $class->offsetSet('contentTransferEncoding', 'base64'); + $this->assertEquals('base64', $class->offsetGet('contentTransferEncoding')); + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageBodyStructure(); + $class->offsetSet('test', 1); + } + ); + } + + public function testOffsetUnset() + { + $class = new MessageBodyStructure(); + $class->offsetSet('plain', true); + $class->offsetSet('html', true); + $class->offsetSet('attachments', true); + $class->offsetSet('mimeVersion', '1.0'); + $class->offsetSet('contentType', 'text/plain'); + $class->offsetSet('contentTransferEncoding', 'base64'); + + $class->offsetUnset('plain'); + $class->offsetUnset('html'); + $class->offsetUnset('attachments'); + $class->offsetUnset('mimeVersion'); + $class->offsetUnset('contentType'); + $class->offsetUnset('contentTransferEncoding'); + + $this->assertEquals(false, $class->offsetGet('plain')); + $this->assertEquals(false, $class->offsetGet('html')); + $this->assertEquals(false, $class->offsetGet('attachments')); + $this->assertEquals('', $class->offsetGet('mimeVersion')); + $this->assertEquals('', $class->offsetGet('contentType')); + $this->assertEquals('', $class->offsetGet('contentTransferEncoding')); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageBodyStructure(); + $class->offsetUnset('test'); + } + ); + } + + public function testHtmlBodyExists() + { + $class = new MessageBodyStructure(); + $this->assertFalse($class->htmlBodyExists()); + $class->offsetSet('html', true); + $this->assertTrue($class->htmlBodyExists()); + } + + public function testPlainTextBodyExists() + { + $class = new MessageBodyStructure(); + $this->assertFalse($class->plainTextBodyExists()); + $class->offsetSet('plain', true); + $this->assertTrue($class->plainTextBodyExists()); + } + + public function testAttachmentsExists() + { + $class = new MessageBodyStructure(); + $this->assertFalse($class->attachmentsExists()); + $class->offsetSet('attachments', true); + $this->assertTrue($class->attachmentsExists()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Response/MessageBodyTest.php b/tests/unit/SalesAgility/Imap/Response/MessageBodyTest.php new file mode 100755 index 0000000..e3d8802 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Response/MessageBodyTest.php @@ -0,0 +1,137 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Response\MessageBody; + +class MessageBodyTest extends \Codeception\Test\Unit +{ + /** + * @var UnitTester + */ + protected $tester; + + public function testOffsetExists() + { + $class = new MessageBody(); + $this->assertTrue($class->offsetExists('structure')); + $this->assertTrue($class->offsetExists('html')); + $this->assertTrue($class->offsetExists('text')); + $this->assertTrue($class->offsetExists('attachments')); + $this->assertFalse($class->offsetExists('test')); + } + + public function testOffsetGet() + { + $class = new MessageBody(); + $this->assertEquals(null, $class->offsetGet('structure')); + $this->assertEquals('', $class->offsetGet('html')); + $this->assertEquals('', $class->offsetGet('text')); + $this->assertEquals(array(), $class->offsetGet('attachments')); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageBody(); + $class->offsetGet('test'); + } + ); + } + + public function testOffsetSet() + { + $class = new MessageBody(); + $value = new \SalesAgility\Imap\Response\MessageBodyStructure(); + $class->offsetSet('structure', $value); + $this->assertEquals($value, $class->offsetGet('structure')); + + $class->offsetSet('html', '<html>'); + $this->assertEquals('<html>', $class->offsetGet('html')); + + $class->offsetSet('text', 'plain'); + $this->assertEquals('plain', $class->offsetGet('text')); + + $value = new \SalesAgility\Imap\Response\MessageAttachment(); + $class->offsetSet('attachments', $value); + $this->assertEquals(array($value), $class->offsetGet('attachments')); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageBody(); + $class->offsetSet('test', 1); + } + ); + } + + public function testOffsetUnset() + { + $class = new MessageBody(); + $value = new \SalesAgility\Imap\Response\MessageBodyStructure(); + $class->offsetSet('structure', $value); + + $class->offsetSet('html', '<html>'); + + $class->offsetSet('text', 'plain'); + + $value = new \SalesAgility\Imap\Response\MessageAttachment(); + $class->offsetSet('attachments', $value); + + $class->offsetUnset('structure'); + $class->offsetUnset('html'); + $class->offsetUnset('text'); + $class->offsetUnset('attachments'); + + $this->assertEquals(null, $class->offsetGet('structure')); + $this->assertEquals('', $class->offsetGet('html')); + $this->assertEquals('', $class->offsetGet('text')); + $this->assertEquals(array(), $class->offsetGet('attachments')); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageBody(); + $class->offsetUnset('test'); + } + ); + } + + public function testStructure() + { + $class = new MessageBody(); + $this->assertEquals(null, $class->structure()); + } + + public function testHtml() + { + $class = new MessageBody(); + $this->assertEquals('', $class->html()); + } + + public function testAttachments() + { + $class = new MessageBody(); + $this->assertEquals(array(), $class->attachments()); + } + + public function testText() + { + $class = new MessageBody(); + $this->assertEquals('', $class->text()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Response/MessageFlagsTest.php b/tests/unit/SalesAgility/Imap/Response/MessageFlagsTest.php new file mode 100755 index 0000000..f0cd73f --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Response/MessageFlagsTest.php @@ -0,0 +1,155 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Response\MessageFlags; + +class MessageFlagsTest extends \Codeception\Test\Unit +{ + /** @var UnitTester */ + protected $tester; + /** @var ReflectionProperty */ + private static $flags; + + protected function _before() + { + if (self::$flags === null) { + $reflection = new ReflectionClass(MessageFlags::class); + self::$flags = $reflection->getProperty('flags'); + self::$flags->setAccessible(true); + } + } + + public function test__construct() + { + $object = new MessageFlags(); + $expected = array( + 'Answered' => false, + 'Deleted' => false, + 'Draft' => false, + 'Flagged' => false, + 'Recent' => false, + 'Seen' => false, + ); + + $this->assertEquals($expected, self::$flags->getValue($object)); + } + + public function testOffsetExists() + { + $object = new MessageFlags(); + + $this->assertTrue($object->offsetExists('Answered')); + $this->assertTrue($object->offsetExists('Deleted')); + $this->assertTrue($object->offsetExists('Draft')); + $this->assertTrue($object->offsetExists('Flagged')); + $this->assertTrue($object->offsetExists('Recent')); + $this->assertTrue($object->offsetExists('Seen')); + $this->assertFalse($object->offsetExists('test')); + } + + public function testOffsetUnset() + { + $this->tester->expectException( + new \Exception('Unset is not supported'), + function() { + $object = new MessageFlags(); + unset($object['Answered']); + } + ); + } + + public function testOffsetSet() + { + $object = new MessageFlags(); + $object['Answered'] = true; + $expected = array( + 'Answered' => true, + 'Deleted' => false, + 'Draft' => false, + 'Flagged' => false, + 'Recent' => false, + 'Seen' => false, + ); + + $this->assertEquals($expected, self::$flags->getValue($object)); + } + + public function testOffsetGet() + { + $object = new MessageFlags(); + $object['Answered'] = true; + $this->assertEquals(true,$object->offsetGet('Answered')); + } + + public function testIsAnswered() + { + $object = new MessageFlags(); + $object['Answered'] = false; + $this->assertFalse($object->isAnswered()); + $object['Answered'] = true; + $this->assertTrue($object->isAnswered()); + } + + public function testIsDeleted() + { + $object = new MessageFlags(); + $object['Deleted'] = false; + $this->assertFalse($object->isDeleted()); + $object['Deleted'] = true; + $this->assertTrue($object->isDeleted()); + } + + public function testIsSeen() + { + $object = new MessageFlags(); + $object['Seen'] = false; + $this->assertFalse($object->isSeen()); + $object['Seen'] = true; + $this->assertTrue($object->isSeen()); + } + + public function testIsRecent() + { + $object = new MessageFlags(); + $object['Recent'] = false; + $this->assertFalse($object->isRecent()); + $object['Recent'] = true; + $this->assertTrue($object->isRecent()); + } + + public function testIsDraft() + { + $object = new MessageFlags(); + $object['Draft'] = false; + $this->assertFalse($object->isDraft()); + $object['Draft'] = true; + $this->assertTrue($object->isDraft()); + } + + public function testIsFlagged() + { + $object = new MessageFlags(); + $object['Flagged'] = false; + $this->assertFalse($object->isFlagged()); + $object['Flagged'] = true; + $this->assertTrue($object->isFlagged()); + } + + +} diff --git a/tests/unit/SalesAgility/Imap/Response/MessageHeaderTest.php b/tests/unit/SalesAgility/Imap/Response/MessageHeaderTest.php new file mode 100755 index 0000000..75e3def --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Response/MessageHeaderTest.php @@ -0,0 +1,270 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Response\MessageHeader; + +class MessageHeaderTest extends \Codeception\Test\Unit +{ + /** + * @var UnitTester + */ + protected $tester; + + public function testOffsetExists() + { + $class = new MessageHeader(); + + $this->assertTrue($class->offsetExists('date')); + $this->assertTrue($class->offsetExists('to')); + $this->assertTrue($class->offsetExists('from')); + $this->assertTrue($class->offsetExists('replyTo')); + $this->assertTrue($class->offsetExists('cc')); + $this->assertTrue($class->offsetExists('bcc')); + $this->assertTrue($class->offsetExists('subject')); + $this->assertTrue($class->offsetExists('messageId')); + $this->assertFalse($class->offsetExists('test')); + } + + public function testOffsetGet() + { + $class = new MessageHeader(); + $this->assertEquals(null, $class->offsetGet('date')); + $this->assertEquals(array(), $class->offsetGet('to')); + $this->assertEquals(array(), $class->offsetGet('from')); + $this->assertEquals(array(), $class->offsetGet('replyTo')); + $this->assertEquals(array(), $class->offsetGet('cc')); + $this->assertEquals(array(), $class->offsetGet('bcc')); + $this->assertEquals('', $class->offsetGet('subject')); + $this->assertEquals('', $class->offsetGet('messageId')); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageHeader(); + $class->offsetGet('test'); + } + ); + } + + public function testOffsetSet() + { + $class = new MessageHeader(); + $date = new \DateTimeImmutable(); + $class->offsetSet('date', $date); + $this->assertSame($date, $class->offsetGet('date')); + $class->offsetSet('to', 'name@example.com'); + $this->assertSame(array('name@example.com'), $class->offsetGet('to')); + $class->offsetSet('from', 'name@example.com'); + $this->assertSame(array('name@example.com'), $class->offsetGet('from')); + $class->offsetSet('replyTo', 'name@example.com'); + $this->assertSame(array('name@example.com'), $class->offsetGet('replyTo')); + $class->offsetSet('cc', 'name@example.com'); + $this->assertSame(array('name@example.com'), $class->offsetGet('cc')); + $class->offsetSet('bcc', 'name@example.com'); + $this->assertSame(array('name@example.com'), $class->offsetGet('bcc')); + $class->offsetSet('subject', 'Re: Topic'); + $this->assertSame('Re: Topic', $class->offsetGet('subject')); + $class->offsetSet('messageId', 'd1fc274555f34745a47430fb3e9cdf56'); + $this->assertSame('d1fc274555f34745a47430fb3e9cdf56', $class->offsetGet('messageId')); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageHeader(); + $class->offsetSet('test', 1); + } + ); + + + $this->tester->expectException( + new \Exception('"date" must be a \DateTimeImmutable'), + function () { + $class = new MessageHeader(); + $class->offsetSet('date', 1); + } + ); + + $this->tester->expectException( + new \Exception('"to" must be a string'), + function () { + $class = new MessageHeader(); + $class->offsetSet('to', 1); + } + ); + + $this->tester->expectException( + new \Exception('"from" must be a string'), + function () { + $class = new MessageHeader(); + $class->offsetSet('from', 1); + } + ); + + $this->tester->expectException( + new \Exception('"replyTo" must be a string'), + function () { + $class = new MessageHeader(); + $class->offsetSet('replyTo', 1); + } + ); + + $this->tester->expectException( + new \Exception('"cc" must be a string'), + function () { + $class = new MessageHeader(); + $class->offsetSet('cc', 1); + } + ); + + $this->tester->expectException( + new \Exception('"bcc" must be a string'), + function () { + $class = new MessageHeader(); + $class->offsetSet('bcc', 1); + } + ); + + $this->tester->expectException( + new \Exception('"subject" must be a string'), + function () { + $class = new MessageHeader(); + $class->offsetSet('subject', 1); + } + ); + + + $this->tester->expectException( + new \Exception('"messageId" must be a string'), + function () { + $class = new MessageHeader(); + $class->offsetSet('messageId', 1); + } + ); + } + + public function testOffsetUnset() + { + $class = new MessageHeader(); + $date = new \DateTimeImmutable(); + $class->offsetSet('date', $date); + $class->offsetSet('to', 'name@example.com'); + $class->offsetSet('from', 'name@example.com'); + $class->offsetSet('replyTo', 'name@example.com'); + $class->offsetSet('cc', 'name@example.com'); + $class->offsetSet('bcc', 'name@example.com'); + $class->offsetSet('subject', 'Re: Topic'); + $class->offsetSet('messageId', 'd1fc274555f34745a47430fb3e9cdf56'); + + + $reflection = new ReflectionClass(MessageHeader::class); + $date = $reflection->getProperty('date'); + $date->setAccessible(true); + $to = $reflection->getProperty('to'); + $to->setAccessible(true); + $from = $reflection->getProperty('from'); + $from->setAccessible(true); + $replyTo = $reflection->getProperty('replyTo'); + $replyTo->setAccessible(true); + $cc = $reflection->getProperty('cc'); + $cc->setAccessible(true); + $bcc = $reflection->getProperty('bcc'); + $bcc->setAccessible(true); + $subject = $reflection->getProperty('subject'); + $subject->setAccessible(true); + $messageId = $reflection->getProperty('messageId'); + $messageId->setAccessible(true); + + $class->offsetUnset('date'); + $class->offsetUnset('to'); + $class->offsetUnset('from'); + $class->offsetUnset('replyTo'); + $class->offsetUnset('cc'); + $class->offsetUnset('bcc'); + $class->offsetUnset('subject'); + $class->offsetUnset('messageId'); + + $this->assertEquals(null, $date->getValue($class)); + $this->assertEquals(array(), $to->getValue($class)); + $this->assertEquals(array(), $from->getValue($class)); + $this->assertEquals(array(), $replyTo->getValue($class)); + $this->assertEquals(array(), $cc->getValue($class)); + $this->assertEquals(array(), $bcc->getValue($class)); + $this->assertEquals('', $subject->getValue($class)); + $this->assertEquals('', $messageId->getValue($class)); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new MessageHeader(); + $class->offsetUnset('test'); + } + ); + + + } + + public function testCc() + { + $class = new MessageHeader(); + $this->assertEquals(array(), $class->cc()); + } + + public function testTo() + { + $class = new MessageHeader(); + $this->assertEquals(array(), $class->to()); + } + + public function testDate() + { + $class = new MessageHeader(); + $this->assertEquals(null, $class->date()); + } + + public function testSubject() + { + $class = new MessageHeader(); + $this->assertEquals('', $class->subject()); + } + + public function testBcc() + { + $class = new MessageHeader(); + $this->assertEquals(array(), $class->bcc()); + } + + public function testFrom() + { + $class = new MessageHeader(); + $this->assertEquals(array(), $class->from()); + } + + + public function testReplyTo() + { + $class = new MessageHeader(); + $this->assertEquals(array(), $class->replyTo()); + } + + public function testMessageId() + { + $class = new MessageHeader(); + $this->assertEquals('', $class->messageId()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Response/MessageListTest.php b/tests/unit/SalesAgility/Imap/Response/MessageListTest.php new file mode 100755 index 0000000..adc8b9f --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Response/MessageListTest.php @@ -0,0 +1,261 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Response\MessageList; +use SalesAgility\Imap\Response\Message; + +class MessageListTest extends \Codeception\Test\Unit +{ + /** @var UnitTester */ + protected $tester; + public function testKey() + { + $object = new MessageList(); + $message1 = new Message(); + $message2 = new Message(); + $message3 = new Message(); + + $object[] = $message1; + $object[] = $message2; + $object[] = $message3; + + $this->assertEquals(0, $object->key()); + $object->next(); + $this->assertEquals(1, $object->key()); + $object->next(); + $this->assertEquals(2, $object->key()); + } + + public function testCurrent() + { + $object = new MessageList(); + $message1 = new Message(); + $message2 = new Message(); + $message3 = new Message(); + + $object[] = $message1; + $object[] = $message2; + $object[] = $message3; + + $this->assertSame($message1, $object->current()); + $object->next(); + $this->assertSame($message2, $object->current()); + $object->next(); + $this->assertSame($message3, $object->current()); + } + + public function testOffsetExists() + { + $object = new MessageList(); + $message1 = new Message(); + $message2 = new Message(); + $message3 = new Message(); + + $object[] = $message1; + $object[] = $message2; + $object[] = $message3; + + $this->assertTrue($object->offsetExists(0)); + $this->assertTrue($object->offsetExists(1)); + $this->assertTrue($object->offsetExists(2)); + $this->assertFalse($object->offsetExists(3)); + } + + public function testNext() + { + $object = new MessageList(); + $message1 = new Message(); + $message2 = new Message(); + $message3 = new Message(); + + $object[] = $message1; + $object[] = $message2; + $object[] = $message3; + $this->assertTrue($object->valid()); + $object->next(); + $this->assertEquals(1, $object->key()); + } + + public function testOffsetSet() + { + $object = new MessageList(); + $message1 = new Message(); + $message2 = new Message(); + $message3 = new Message(); + + $object[0] = $message1; + $object[1] = $message2; + $object[2] = $message3; + $this->assertTrue($object->valid()); + $object->next(); + $this->assertEquals(1, $object->key()); + + + $this->tester->expectException( + new \InvalidArgumentException('Message List can only store integer key values'), + function () { + $object = new MessageList(); + $message1 = new Message(); + $object['v'] = $message1; + } + ); + + $this->tester->expectException( + new \InvalidArgumentException('Message List can only store values which derive from a Message'), + function () { + $object = new MessageList(); + $object[] = "string"; + } + ); + } + + public function testOffsetUnset() + { + $object = new MessageList(); + $message1 = new Message(); + $message2 = new Message(); + $message3 = new Message(); + + $object[] = $message1; + $object[] = $message2; + $object[] = $message3; + $object->offsetUnset(1); + $this->assertTrue($object->offsetExists(2)); + $this->assertFalse($object->offsetExists(1)); + } + + public function testOffsetGet() + { + $object = new MessageList(); + $message1 = new Message(); + $message2 = new Message(); + $message3 = new Message(); + + $object[] = $message1; + $object[] = $message2; + $object[] = $message3; + + $this->assertSame($message1, $object->offsetGet(0)); + $this->assertSame($message2, $object->offsetGet(1)); + $this->assertSame($message3, $object->offsetGet(2)); + } + + public function testRewind() + { + $object = new MessageList(); + $message1 = new Message(); + $message2 = new Message(); + $message3 = new Message(); + + $object[] = $message1; + $object[] = $message2; + $object[] = $message3; + + $object->next(); + $object->next(); + $object->next(); + $object->rewind(); + $this->assertEquals(0, $object->key()); + } + + public function testValid() + { + $object = new MessageList(); + $message1 = new Message(); + $message2 = new Message(); + $message3 = new Message(); + + $object[] = $message1; + $object[] = $message2; + $object[] = $message3; + $this->assertTrue($object->valid()); + $object->next(); + $this->assertTrue($object->valid()); + $object->next(); + $this->assertTrue($object->valid()); + $object->next(); + $this->assertFalse($object->valid()); + } + + public function testReverseOrder() + { + $object = new MessageList(); + $message1 = new Message(); + $message1->offsetSet('number', '1'); + $message2 = new Message(); + $message2->offsetSet('number', '2'); + $message3 = new Message(); + $message3->offsetSet('number', '3'); + + $object[] = $message1; + $object[] = $message2; + $object[] = $message3; + + // is ascending order + $this->assertTrue($object->direction() === 1); + $this->assertEquals('1',$object->offsetGet(0)->number()); + $this->assertEquals('2',$object->offsetGet(1)->number()); + $this->assertEquals('3',$object->offsetGet(2)->number()); + + $object->reverseOrder(); + + // is descending order + $this->assertTrue($object->direction() === -1); + $this->assertEquals('1',$object->offsetGet(2)->number()); + $this->assertEquals('2',$object->offsetGet(1)->number()); + $this->assertEquals('3',$object->offsetGet(0)->number()); + } + + public function testCount() + { + $object = new MessageList(); + $message1 = new Message(); + $message2 = new Message(); + $message3 = new Message(); + + $object[] = $message1; + $object[] = $message2; + $object[] = $message3; + + // When size === 0, we should recieve an empty list + $expectedList = new MessageList(); + $actualList = $object->page(0, 0); + $this->assertEquals($expectedList, $actualList); + + // Expect to see singe page + $expectedList = $object; + $actualList = $object->page(0, 3); + $this->assertEquals($expectedList, $actualList); + + // Expect to see singe page + $actualList = $object->page(0, 1); + $this->assertEquals(1, $actualList->count()); + + $actualList = $object->page(1, 1); + $this->assertEquals(1, $actualList->count()); + + $actualList = $object->page(2, 1); + $this->assertEquals(1, $actualList->count()); + } + + public function testPage() + { + + } +} diff --git a/tests/unit/SalesAgility/Imap/Response/MessageTest.php b/tests/unit/SalesAgility/Imap/Response/MessageTest.php new file mode 100755 index 0000000..6a400c7 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Response/MessageTest.php @@ -0,0 +1,245 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Response\Message; + +class MessageTest extends \Codeception\Test\Unit +{ + /** @var UnitTester $tester */ + protected $tester; + + public function testOffsetExists() + { + $class = new Message(); + $this->assertTrue($class->offsetExists('header')); + $this->assertTrue($class->offsetExists('body')); + $this->assertTrue($class->offsetExists('number')); + $this->assertTrue($class->offsetExists('flags')); + $this->assertTrue($class->offsetExists('uid')); + $this->assertFalse($class->offsetExists('test')); + } + + public function testOffsetGet() + { + $class = new Message(); + $this->assertEquals(null, $class->offsetGet('header')); + $this->assertEquals(null, $class->offsetGet('body')); + $this->assertEquals(null, $class->offsetGet('number')); + $this->assertEquals(null, $class->offsetGet('uid')); + $this->assertEquals(null, $class->offsetGet('flags')); + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new Message(); + $class->offsetGet('test'); + } + ); + } + + public function testOffsetSet() + { + $class = new Message(); + $reflection = new ReflectionClass(Message::class); + + $propertyHeader = $reflection->getProperty('header'); + $propertyHeader->setAccessible(true); + + $propertyBody = $reflection->getProperty('body'); + $propertyBody->setAccessible(true); + + $propertyNumber = $reflection->getProperty('number'); + $propertyNumber->setAccessible(true); + + $propertyUid = $reflection->getProperty('uid'); + $propertyUid->setAccessible(true); + + $header = new \SalesAgility\Imap\Response\MessageHeader(); + $class['header'] = $header; + $this->assertEquals($header, $propertyHeader->getValue($class)); + + $body = new \SalesAgility\Imap\Response\MessageBody(); + $class['body'] = $body; + $this->assertEquals($body, $propertyBody->getValue($class)); + + $class['number'] = '1'; + $this->assertEquals('1', $propertyNumber->getValue($class)); + + $class['uid'] = '1'; + $this->assertEquals('1', $propertyUid->getValue($class)); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new Message(); + $class::offsetSet('test', 1); + } + ); + + $this->tester->expectException( + new \InvalidArgumentException('header $value must implement: MessageHeaderInterface'), + function () { + $class = new Message(); + $class['header'] = 1; + } + ); + + $this->tester->expectException( + new \InvalidArgumentException('body $value must implement: MessageBodyInterface'), + function () { + $class = new Message(); + $class['body'] = 1; + } + ); + + $this->tester->expectException( + new \InvalidArgumentException('number $value must be a string'), + function () { + $class = new Message(); + $class['number'] = 1; + } + ); + + $this->tester->expectException( + new \InvalidArgumentException('uid $value must be a string'), + function () { + $class = new Message(); + $class['uid'] = 1; + } + ); + + $this->tester->expectException( + new \InvalidArgumentException('flags $value must implement: MessageFlagsInterface'), + function () { + $class = new Message(); + $class['flags'] = 1; + } + ); + } + + public function testOffsetUnset() + { + $class = new Message(); + $reflection = new ReflectionClass(Message::class); + + $propertyHeader = $reflection->getProperty('header'); + $propertyHeader->setAccessible(true); + + $propertyBody = $reflection->getProperty('body'); + $propertyBody->setAccessible(true); + + $propertyNumber = $reflection->getProperty('number'); + $propertyNumber->setAccessible(true); + + $propertyUid = $reflection->getProperty('uid'); + $propertyUid->setAccessible(true); + + $propertyFlags = $reflection->getProperty('flags'); + $propertyFlags->setAccessible(true); + + + $header = new \SalesAgility\Imap\Response\MessageHeader(); + $class['header'] = $header; + $class->offsetUnset('header'); + $this->assertEquals(null, $propertyHeader->getValue($class)); + + $body = new \SalesAgility\Imap\Response\MessageBody(); + $class['body'] = $body; + $class->offsetUnset('body'); + $this->assertEquals(null, $propertyBody->getValue($class)); + + $body = new \SalesAgility\Imap\Response\MessageBody(); + $class['number'] = '1'; + $class->offsetUnset('number'); + $this->assertEquals(null, $propertyNumber->getValue($class)); + + $body = new \SalesAgility\Imap\Response\MessageBody(); + $class['uid'] = '1'; + $class->offsetUnset('uid'); + $this->assertEquals(null, $propertyUid->getValue($class)); + + $body = new \SalesAgility\Imap\Response\MessageBody(); + $class['flags'] = new \SalesAgility\Imap\Response\MessageFlags(); + $class->offsetUnset('flags'); + $this->assertEquals(null, $propertyFlags->getValue($class)); + + $this->tester->expectException( + new \InvalidArgumentException('$offset does not exist: test'), + function () { + $class = new Message(); + $class->offsetUnset('test'); + } + ); + } + + public function testHasHeader() + { + $class = new Message(); + $this->assertFalse($class->hasHeader()); + $header = new \SalesAgility\Imap\Response\MessageHeader(); + $class['header'] = $header; + $this->assertTrue($class->hasHeader()); + } + + public function testHeader() + { + $class = new Message(); + $header = new \SalesAgility\Imap\Response\MessageHeader(); + $class['header'] = $header; + $this->assertEquals($header, $class->header()); + } + + public function testHasBody() + { + $class = new Message(); + $this->assertFalse($class->hasBody()); + $body = new \SalesAgility\Imap\Response\MessageBody(); + $class['body'] = $body; + $this->assertTrue($class->hasBody()); + } + + public function testBody() + { + $class = new Message(); + $body = new \SalesAgility\Imap\Response\MessageBody(); + $class['body'] = $body; + $this->assertEquals($body, $class->body()); + } + + public function testNumber() + { + $class = new Message(); + $class['number'] = '1'; + $this->assertEquals('1', $class->number()); + } + + public function testUid() + { + $class = new Message(); + $class['uid'] = '1'; + $this->assertEquals('1', $class->uid()); + } + + public function testFlags() + { + $class = new Message(); + $flags = new \SalesAgility\Imap\Response\MessageFlags(); + $class['flags'] = $flags; + $this->assertEquals($flags, $class->flags()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Stream/MessageTransporterTest.php b/tests/unit/SalesAgility/Imap/Stream/MessageTransporterTest.php new file mode 100755 index 0000000..7c83900 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Stream/MessageTransporterTest.php @@ -0,0 +1,144 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +namespace SalesAgility\Imap\Stream; + +use SalesAgility\Imap\ImapException; +use SalesAgility\Imap\CommandBuilder\PimapCommandBuilder; +use SalesAgility\Imap\Stream\MessageTransporter; +use SalesAgility\Stream\MockConnection as Connection; +use SalesAgility\Imap\Pipeline\Pipeline; +use SalesAgility\Imap\ManagerFactory; + +class MessageTransporterTest extends \Codeception\Test\Unit +{ + + /** + * @var \UnitTester + */ + protected $tester; + + public function testSetConnection() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $pipeline = new Pipeline(ManagerFactory::instance()); + $object->setPipeLine($pipeline); + $reflection = new \ReflectionClass(MessageTransporter::class); + $propertyConnection = $reflection->getProperty('connection'); + $propertyConnection->setAccessible(true); + + $connection = new Connection(); + $object->setConnection($connection); + $this->assertEquals($connection, $propertyConnection->getValue($object)); + } + + public function testConnection() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $pipeline = new Pipeline(ManagerFactory::instance()); + $object->setPipeLine($pipeline); + $connection = new Connection(); + $object->setConnection($connection); + $this->assertEquals($connection, $object->connection()); + } + + public function testTransmit() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $pipeline = new Pipeline(ManagerFactory::instance()); + $object->setPipeLine($pipeline); + $connection = new Connection(); + $reflection = new \ReflectionClass(Connection::class); + $property = $reflection->getProperty('messageSent'); + $property->setAccessible(true); + $object->setConnection($connection); + $object->transmit('hello connection'); + $this->assertEquals('hello connection', $property->getValue($connection)); + } + + public function testReceive() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $pipeline = new Pipeline(ManagerFactory::instance()); + $object->setPipeLine($pipeline); + $connection = new Connection(); + + $object->setConnection($connection); + $this->assertEquals('* OK mock server v1.0some headersome body', $object->receive()); + } + + public function testIsEndOfFile() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $pipeline = new Pipeline(ManagerFactory::instance()); + $pipeline->add(PimapCommandBuilder::instance()->raw('test')); + $object->setPipeLine($pipeline); + $connection = new Connection(); + $object->setConnection($connection); + $object->receive(); + $this->assertTrue($object->isEndOfFile(null)); + $this->assertTrue($object->isEndOfFile('A1 OK')); + $this->tester->expectException( + ImapException::BadResponse('A1 BAD'), + function () use ($object, $pipeline, $connection) { + $object->isEndOfFile('A1 BAD'); + } + ); + + $this->tester->expectException( + ImapException::NoResponse('A1 NO'), + function () use ($object, $pipeline, $connection) { + $object->isEndOfFile('A1 NO'); + } + ); + } + + public function testIsConfigured() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $pipeline = new Pipeline(ManagerFactory::instance()); + $object->setPipeLine($pipeline); + $reflection = new \ReflectionClass(MessageTransporter::class); + $method = $reflection->getMethod('isConfigured'); + $method->setAccessible(true); + + $this->tester->expectException( + new \Exception('Connection must be set'), + function () use ($object, $method) { + $method->invoke($object); + } + ); + + $connection = new Connection(); + $object->setConnection($connection); + $this->assertTrue($method->invoke($object)); + } + + public function testTransmitCommand() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $pipeline = new Pipeline(ManagerFactory::instance()); + $object->setPipeLine($pipeline); + $connection = new Connection(); + $object->setConnection($connection); + $command = PimapCommandBuilder::instance()->raw('test'); + $object->transmitCommand($command); + $this->assertEquals('A1 test'."\x0D\x0A", $connection->messageSent); + } +} diff --git a/tests/unit/SalesAgility/Imap/Stream/PhpImapExtensionConnectionTest.php b/tests/unit/SalesAgility/Imap/Stream/PhpImapExtensionConnectionTest.php new file mode 100755 index 0000000..64ca066 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Stream/PhpImapExtensionConnectionTest.php @@ -0,0 +1,147 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +require_once codecept_root_dir() . 'tests/mock/imap-ext.php'; + +use SalesAgility\Imap\Stream\PhpImpExtensionConnection; +use \SalesAgility\Imap\ManagerFactory; + +class PhpImapExtensionConnectionTest extends \Codeception\Test\Unit +{ + /** @var UnitTester */ + protected $tester; + + public function testSetConnectionString() + { + $object = new PhpImpExtensionConnection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(PhpImpExtensionConnection::class); + $server = $reflection->getProperty('server'); + $server->setAccessible(true); + $port = $reflection->getProperty('port'); + $port->setAccessible(true); + $object->setConnectionString('tcp://localhost:143'); + $this->assertEquals('localhost', $server->getValue($object)); + $this->assertEquals('143', $port->getValue($object)); + + // negative tests + $this->tester->expectException( + new \Exception('Expected tcp://hostname:portnumber'), + function () use ($object) { + $object->setConnectionString('{localhost:143}'); + } + ); + } + + public function testReadMessage() + { + $object =new PhpImpExtensionConnection(ManagerFactory::instance()); + $this->tester->expectException( + new \Exception('Not supported'), + function () use ($object) { + $object->readMessage(); + } + ); + } + + public function testTransmitMessage() + { + $object =new PhpImpExtensionConnection(ManagerFactory::instance()); + $this->tester->expectException( + new \Exception('Not supported'), + function () use ($object) { + $object->transmitMessage(''); + } + ); + } + + public function testIsEndOfFile() + { + $object =new PhpImpExtensionConnection(ManagerFactory::instance()); + $this->tester->expectException( + new \Exception('Not supported'), + function () use ($object) { + $object->isEndOfFile(); + } + ); + } + + public function testEnableEncryption() + { + $object =new PhpImpExtensionConnection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(PhpImpExtensionConnection::class); + $security = $reflection->getProperty('security'); + $security->setAccessible(true); + $object->enableEncryption(); + $this->assertEquals('/imap/ssl/novalidate-cert', $security->getValue($object)); + } + + public function testDisableEncryption() + { + $object =new PhpImpExtensionConnection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(PhpImpExtensionConnection::class); + $security = $reflection->getProperty('security'); + $security->setAccessible(true); + $object->enableEncryption(); + $object->disableEncryption(); + $this->assertEquals('', $security->getValue($object)); + } + + public function testConnect() + { + $object =new PhpImpExtensionConnection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(PhpImpExtensionConnection::class); + $connection = $reflection->getProperty('connection'); + $connection->setAccessible(true); + $object->connect(); + $this->assertEquals(1, $connection->getValue($object)); + } + + public function testDisconnect() + { + $object =new PhpImpExtensionConnection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(PhpImpExtensionConnection::class); + $connection = $reflection->getProperty('connection'); + $connection->setAccessible(true); + $object->connect(); + $object->disconnect(); + $this->assertEquals(null, $connection->getValue($object)); + + $object->username = 'user'; + $object->password = ''; + $object->connect(); + $object->disconnect(); + $this->assertEquals(null, $connection->getValue($object)); + } + + public function testIsConnected() + { + $object =new PhpImpExtensionConnection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(PhpImpExtensionConnection::class); + $connection = $reflection->getProperty('connection'); + $connection->setAccessible(true); + $object->connect(); + $this->assertTrue($object->isConnected()); + $object->disconnect(); + $this->assertFalse($object->isConnected()); + + $object->username = 'user'; + $object->password = ''; + $object->connect(); + } +} diff --git a/tests/unit/SalesAgility/Imap/Stream/PhpImapExtensionMessageTransporterTest.php b/tests/unit/SalesAgility/Imap/Stream/PhpImapExtensionMessageTransporterTest.php new file mode 100755 index 0000000..5d9d544 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Stream/PhpImapExtensionMessageTransporterTest.php @@ -0,0 +1,409 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +require_once codecept_root_dir() . 'tests/mock/imap-ext.php'; + +use SalesAgility\Imap\Stream\PhpImapExtensionMessageTransporter; +use SalesAgility\Imap\Stream\PhpImpExtensionConnection; +use SalesAgility\Imap\CommandBuilder\PhpImapExtensionCommandBuilder; +use SalesAgility\Imap\Response\Response; +use SalesAgility\Imap\ImapException; +use \SalesAgility\Imap\ManagerFactory; +use SalesAgility\Iteration\StringIterator; + +class PhpImapExtensionMessageTransporterTest extends \Codeception\Test\Unit +{ + + /** @var \UnitTester $tester */ + protected $tester; + + /** @var \SalesAgility\Imap\Manager\PhpImapExtensionManager */ + private static $manager; + /** @var PhpImapExtensionMessageTransporter */ + private static $transporter; + /** @var \ReflectionClass */ + private static $reflected; + + public function _before() + { + parent::_before(); + if (self::$reflected === null) { + self::$reflected = new ReflectionClass(PhpImapExtensionMessageTransporter::class); + } + + self::$manager = ManagerFactory::instance()->PhpImapExtensionManager(); + self::$transporter = self::$manager->transporter(); + + } + + public function testReceive() + { + $this->tester->expectException( + new \Exception('Native Php Client handles it\'s own transmission'), + function () { + self::$transporter->receive(); + } + ); + } + + public function testIsEndOfFile() + { + $this->tester->expectException( + new \Exception('Native Php Client handles it\'s own transmission'), + function () { + self::$transporter->isEndOfFile(''); + } + ); + } + + public function testTransmit() + { + $this->tester->expectException( + new \Exception('Native Php Client handles it\'s own transmission'), + function () { + self::$transporter->transmit(''); + } + ); + } + + public function testSetConnection() + { + $reflection = new \ReflectionClass(PhpImapExtensionMessageTransporter::class); + $connectionProperty = $reflection->getProperty('connection'); + $connectionProperty->setAccessible(true); + $this->assertEquals(self::$transporter->connection(), $connectionProperty->getValue(self::$transporter)); + + $this->tester->expectException( + new \Exception('Native client is only compatible with PhpImpExtensionConnection'), + function () { + $connection = new \SalesAgility\Stream\Connection(ManagerFactory::instance()); + self::$transporter->setConnection($connection); + } + ); + } + + + public function testTransmitCommand() + { + $this->tester->expectException( + new \Exception('Command Not Supported: IDLE'), + function () { + $command = \SalesAgility\Imap\CommandBuilder\PimapCommandBuilder::instance()->idle()->build(); + self::$transporter->transmitCommand($command); + } + ); + } + + public function testPipline() + { + $actual = self::$manager->transporter()->pipeline(); + $this->assertInstanceOf(SalesAgility\Imap\Pipeline\Pipeline::class, $actual); + } + + public function testLogin() + { + $command = PhpImapExtensionCommandBuilder::instance()->login()->user('user')->password('')->build(); + $actual = self::$transporter->transmitCommand($command); + $expectedIncluded = \SalesAgility\Iteration\StringIterator::withLiteral('', 0, 0); + $expectedResponseMessage = \SalesAgility\Iteration\StringIterator::withLiteral('A1 OK login success.' . "\r\n"); + $expected = new Response('OK', $expectedResponseMessage, $expectedIncluded); + $this->assertEquals($expected->included(), $actual->included()); + $this->assertEquals($expected->message(), $actual->message()); + $this->assertEquals($expected->status(), $actual->status()); + } + + public function testSelect() + { + $exists = 5; + $recent = 1; + $GLOBALS['mock_imap_num_msg'] = $exists; + $GLOBALS['mock_imap_num_recent'] = $recent; + + $command = PhpImapExtensionCommandBuilder::instance()->select('INBOX')->build(); + $actual = self::$transporter->transmitCommand($command); + $expected = new \SalesAgility\Imap\Response\Mailbox(); + $expected->offsetSet('exists', (string)$exists); + $expected->offsetSet('recent', (string)$recent); + $this->assertEquals($expected, $actual); + } + + public function testNoop() + { + $command = PhpImapExtensionCommandBuilder::instance()->noop()->build(); + $actual = self::$transporter->transmitCommand($command); + $expected = new Response('OK', StringIterator::withLiteral('A1 OK Nothing Happened.' . "\r\n"), StringIterator::withLiteral('', 0, 0)); + $this->assertEquals($expected->included(), $actual->included()); + $this->assertEquals($expected->message(), $actual->message()); + $this->assertEquals($expected->status(), $actual->status()); + } + + public function testLogout() + { + $command = PhpImapExtensionCommandBuilder::instance()->logout()->build(); + $actual = self::$transporter->transmitCommand($command); + $expected = new Response('OK', StringIterator::withLiteral('A1 OK Logout completed.' . "\r\n"), StringIterator::withLiteral('* BYE Logging out' . "\r\n")); + $this->assertEquals($expected->included(), $actual->included()); + $this->assertEquals($expected->message(), $actual->message()); + $this->assertEquals($expected->status(), $actual->status()); + } + + public function testFetch() + { + + $mock_imap_fetch_overview = array(); + $mock_imap_fetch_overview[] = unserialize(file_get_contents(codecept_data_dir('1534938082_php_imap_extension_imap_fetch_overview.serialise')))['imap_fetch_overview']; + $mock_imap_fetch_overview[] = unserialize(file_get_contents(codecept_data_dir('1534938086_php_imap_extension_imap_fetch_overview.serialise')))['imap_fetch_overview']; + $mock_imap_fetch_overview[] = unserialize(file_get_contents(codecept_data_dir('1534938092_php_imap_extension_imap_fetch_overview.serialise')))['imap_fetch_overview']; + $mock_imap_fetch_overview[] = unserialize(file_get_contents(codecept_data_dir('1534938096_php_imap_extension_imap_fetch_overview.serialise')))['imap_fetch_overview']; + + $mock_imap_fetchstructure = array(); + $mock_imap_fetchstructure[] = unserialize(file_get_contents(codecept_data_dir('1534938083_php_imap_extension_imap_fetchstructure.serialise')))['imap_fetchstructure']; + $mock_imap_fetchstructure[] = unserialize(file_get_contents(codecept_data_dir('1534938087_php_imap_extension_imap_fetchstructure.serialise')))['imap_fetchstructure']; + $mock_imap_fetchstructure[] = unserialize(file_get_contents(codecept_data_dir('1534938093_php_imap_extension_imap_fetchstructure.serialise')))['imap_fetchstructure']; + $mock_imap_fetchstructure[] = unserialize(file_get_contents(codecept_data_dir('1534938097_php_imap_extension_imap_fetchstructure.serialise')))['imap_fetchstructure']; + + $mock_imap_body = array(); + $mock_imap_body[] = unserialize(file_get_contents(codecept_data_dir('1534938088_php_imap_extension_imap_body.serialise')))['imap_body']; + + $mock_imap_fetchbody = array(); + $mock_imap_fetchbody[] = unserialize(file_get_contents(codecept_data_dir('1534938098_php_imap_extension_imap_fetchbody.serialise')))['imap_fetchbody']; + $mock_imap_fetchbody[] = unserialize(file_get_contents(codecept_data_dir('1534938099_php_imap_extension_imap_fetchbody.serialise')))['imap_fetchbody']; + $mock_imap_fetchbody[] = unserialize(file_get_contents(codecept_data_dir('1534938100_php_imap_extension_imap_fetchbody.serialise')))['imap_fetchbody']; + $mock_imap_fetchbody[] = unserialize(file_get_contents(codecept_data_dir('1534938101_php_imap_extension_imap_fetchbody.serialise')))['imap_fetchbody']; + + // mock methods uses pop so we need to reverse the order + // ensure that the results are correctly return at the right time. + $mock_imap_fetch_overview = array_reverse($mock_imap_fetch_overview); + $mock_imap_fetchstructure = array_reverse($mock_imap_fetchstructure); + $mock_imap_fetchbody = array_reverse($mock_imap_fetchbody); + $mock_imap_body = array_reverse($mock_imap_body); + + $GLOBALS['mock_imap_fetch_overview'] = $mock_imap_fetch_overview; + $GLOBALS['mock_imap_fetchstructure'] = $mock_imap_fetchstructure; + $GLOBALS['mock_imap_fetchbody'] = $mock_imap_fetchbody; + $GLOBALS['mock_imap_body'] = $mock_imap_body; + + // Test Plain Text Email + $command = self::$manager->command()->fetch(2677)->flags()->uids()->header()->build(); + /** @var \SalesAgility\Imap\Response\MessageList $actual */ + $actual = self::$transporter->transmitCommand($command); + $expected = unserialize(file_get_contents(codecept_data_dir('1534938084_php_imap_extension_message.serialise'))); + $this->assertEquals($expected, $actual->offsetGet(0)); + + $command = self::$manager->command()->fetch(2677)->body()->build(); + $actual = self::$transporter->transmitCommand($command); + $expected = unserialize(file_get_contents(codecept_data_dir('1534938089_php_imap_extension_message.serialise'))); + $this->assertEquals($expected, $actual->offsetGet(0)); + + // Test Html Email + $command = self::$manager->command()->fetch(2668)->flags()->uids()->header()->build(); + $actual = self::$transporter->transmitCommand($command); + $expected = unserialize(file_get_contents(codecept_data_dir('1534938094_php_imap_extension_message.serialise'))); + $this->assertEquals($expected, $actual->offsetGet(0)); + + $command = self::$manager->command()->uid()->fetch(2668)->body()->build(); + $actual = self::$transporter->transmitCommand($command); + $expected = unserialize(file_get_contents(codecept_data_dir('1534938102_php_imap_extension_message.serialise'))); + $this->assertEquals($expected, $actual->offsetGet(0)); + + } + + + public function testSearch() + { + $messageList = new \SalesAgility\Imap\Response\MessageList(); + $message = new \SalesAgility\Imap\Response\Message();; + $message->offsetSet('number', '1'); + $messageList[] = $message; + $GLOBALS['mock_imap_search'] = $messageList; + + $command = self::$manager->command()->search()->searchRecent()->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\MessageList::class, $actual); + + $command = self::$manager->command()->uid()->search()->searchRecent()->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\MessageList::class, $actual); + } + + public function testStore() + { + $command = self::$manager->command()->store()->withMessage('1')->replaceFlag('Answered')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(Response::class, $actual); + + $command = self::$manager->command()->uid()->store()->withMessage('1')->replaceFlag('Answered')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(Response::class, $actual); + + $command = self::$manager->command()->uid()->store()->withMessage('1')->addFlag('Answered')->addFlag('Seen')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(Response::class, $actual); + + + $command = self::$manager->command()->store()->withMessage('1')->removeFlag('Answered')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(Response::class, $actual); + + $command = self::$manager->command()->uid()->store()->withMessage('1')->removeFlag('Answered')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(Response::class, $actual); + } + + public function testCopy() + { + $command = self::$manager->command()->copy()->withRange(1, 2)->toMailbox('Invoices')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + + $command = self::$manager->command()->uid()->copy()->withRange(1, 2)->toMailbox('Invoices')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + } + + public function testUid() + { + // test invalid case + $this->tester->expectException( + new \Exception('UID Command Not Supported: UID'), + function () { + $command = self::$manager->command()->uid()->build(); + self::$transporter->transmitCommand($command); + } + ); + } + + public function testListMailboxes() + { + $mailboxList = new \SalesAgility\Imap\Response\MailboxList(); + $mailbox1 = new \SalesAgility\Imap\Response\Mailbox(); + $mailbox1->offsetSet('name', 'INBOX'); + $mailbox2 = new \SalesAgility\Imap\Response\Mailbox(); + $mailbox2->offsetSet('name', 'Trash'); + $mailbox3 = new \SalesAgility\Imap\Response\Mailbox(); + $mailbox3->offsetSet('name', 'Sent'); + $mailboxList[] = $mailbox1; + $mailboxList[] = $mailbox2; + $mailboxList[] = $mailbox3; + + $GLOBALS['mock_imap_list'] = array('INBOX', 'Trash', 'Sent'); + + $command = self::$manager->command()->listMailbox()->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\MailboxList::class, $actual); + $this->assertEquals($mailboxList, $actual); + } + + public function testListSubMailboxes() + { + $mailboxList = new \SalesAgility\Imap\Response\MailboxList(); + $mailbox1 = new \SalesAgility\Imap\Response\Mailbox(); + $mailbox1->offsetSet('hierarchy', '/Customer'); + $mailbox1->offsetSet('name', '/B2C'); + $mailbox2 = new \SalesAgility\Imap\Response\Mailbox(); + $mailbox2->offsetSet('hierarchy', '/Customer'); + $mailbox2->offsetSet('name', '/B2B'); + + $mailboxList[] = $mailbox1; + $mailboxList[] = $mailbox2; + + $GLOBALS['mock_imap_lsub'] = array('/Customer/B2C', '/Customer/B2B'); + + $command = self::$manager->command()->listSubsetMailbox('/Customer')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\MailboxList::class, $actual); + $this->assertEquals($mailboxList, $actual); + } + + public function testCheck() + { + $command = self::$manager->command()->check()->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + } + + public function testCreate() + { + $command = self::$manager->command()->create('INVOICES')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + } + + public function testDelete() + { + $command = self::$manager->command()->delete('INVOICES')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + } + + public function testRename() + { + $command = self::$manager->command()->rename('QUOTES', 'INVOICES')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + } + + public function testSubscribe() + { + $command = self::$manager->command()->subscribe('INVOICES')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + } + + public function testUnsubscribe() + { + $command = self::$manager->command()->unsubscribe('INVOICES')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + } + + public function testClose() + { + $command = self::$manager->command()->close()->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + } + + public function testExpunge() + { + $command = self::$manager->command()->expunge()->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + } + + public function testAppend() + { + $command = self::$manager->command()->append('INVOICES')->withMessage('FAKE RFC 2822 MESSAGE')->build(); + $actual = self::$transporter->transmitCommand($command); + $this->assertInstanceOf(\SalesAgility\Imap\Response\Response::class, $actual); + } + + public function testCheckErrors() + { + $GLOBALS['mock_imap_last_error'] = 'test imap_last_error'; + $this->tester->expectException( + new \Exception('test imap_last_error'), + function () { + $command = self::$manager->command()->append('INVOICES')->withMessage('FAKE RFC 2822 MESSAGE')->build(); + $actual = self::$transporter->transmitCommand($command); + } + ); + + } +} diff --git a/tests/unit/SalesAgility/Imap/Token/TokenListTest.php b/tests/unit/SalesAgility/Imap/Token/TokenListTest.php new file mode 100755 index 0000000..d24d950 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Token/TokenListTest.php @@ -0,0 +1,161 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Token\TokenList; +use \SalesAgility\Imap\Token\Token; +use SalesAgility\Imap\Token\TokenType; +use SalesAgility\Iteration\StringIterator; + +class TokenListTest extends \Codeception\Test\Unit +{ + + /** @var UnitTester $tester*/ + protected $tester; + + public function testOffsetSet() + { + $object = new TokenList(); + $object[] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $this->assertEquals(0, $object->key()); + + + $object = new TokenList(); + $object[0] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $this->assertEquals(0, $object->key()); + + // negative cases + $this->tester->expectException( + new \Exception('Token List can only store integer key values'), + function () { + $object = new TokenList(); + $object['string'] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $this->assertEquals(0, $object->key()); + } + ); + + + $this->tester->expectException( + new \Exception('Token List can only store values which derive from a Token'), + function () { + $object = new TokenList(); + $object[0] = 'string'; + $this->assertEquals(0, $object->key()); + } + ); + + } + + public function testValid() + { + $object = new TokenList(); + $object[] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $this->assertEquals(0, $object->key()); + $this->assertTrue($object->valid()); + $object->next(); + $this->assertFalse($object->valid()); + } + + public function testKey() + { + $object = new TokenList(); + $object[] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $object->next(); + $object->next(); + $this->assertEquals(2, $object->key()); + } + + public function testOffsetGet() + { + $object = new TokenList(); + $object[] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $object->next(); + $object->next(); + $this->assertEquals('b', $object->offsetGet(1)->toString()); + } + + public function testOffsetUnset() + { + $object = new TokenList(); + $object[] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + unset($object[1]); + $object->offsetGet(1); + $this->assertEquals('c', $object->offsetGet(1)->toString()); + } + + public function testCurrent() + { + $object = new TokenList(); + $object[] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $this->assertEquals('a', $object->current()->toString()); + $object->next(); + $this->assertEquals('b', $object->current()->toString()); + $object->next(); + $this->assertEquals('c', $object->current()->toString()); + } + + public function testNext() + { + $object = new TokenList(); + $object[] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $object->next(); + $this->assertEquals(1, $object->key()); + } + + public function testOffsetExists() + { + $object = new TokenList(); + $object[] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $this->assertTrue($object->offsetExists(0)); + $this->assertFalse($object->offsetExists(1)); + } + + public function testSeek() + { + $object = new TokenList(); + $object[] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $object->seek(2); + $this->assertEquals(2, $object->key()); + } + + + public function testRewind() + { + $object = new TokenList(); + $object[] = new Token(StringIterator::withLiteral('a'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('b'), TokenType::notWhiteSpaceOrControl()); + $object[] = new Token(StringIterator::withLiteral('c'), TokenType::notWhiteSpaceOrControl()); + $object->next(); + $object->next(); + $object->next(); + $object->rewind(); + $this->assertEquals(0, $object->key()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Token/TokenTest.php b/tests/unit/SalesAgility/Imap/Token/TokenTest.php new file mode 100755 index 0000000..19a58e1 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Token/TokenTest.php @@ -0,0 +1,63 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Token\Token; +use SalesAgility\Imap\Token\TokenType; +use SalesAgility\Iteration\StringIterator; + +class TokenTest extends \Codeception\Test\Unit +{ + public function test__construct() + { + $object = new Token(StringIterator::withLiteral(' '), TokenType::whiteSpace()); + $this->assertTrue($object->valid()); + } + + public function testLastKey() + { + $object = new Token(StringIterator::withLiteral('foobarbaz'), TokenType::notWhiteSpaceOrControl()); + $this->assertEquals(8, $object->lastKey()); + + $iterator = StringIterator::withStringIterator(StringIterator::withLiteral('foobarbaz'), 1, 3); + $object = new Token($iterator, TokenType::notWhiteSpaceOrControl()); + $this->assertEquals(3, $object->lastKey()); + } + + public function testType() + { + $object = new Token(StringIterator::withLiteral('foobarbaz'), TokenType::notWhiteSpaceOrControl()); + $this->assertEquals(TokenType::notWhiteSpaceOrControl(), $object->type()); + } + + public function testFirstKey() + { + $object = new Token(StringIterator::withLiteral('foobarbaz'), TokenType::notWhiteSpaceOrControl()); + $this->assertEquals(0, $object->firstKey()); + + $iterator = StringIterator::withStringIterator(StringIterator::withLiteral('foobarbaz'), 1, 3); + $object = new Token($iterator, TokenType::notWhiteSpaceOrControl()); + $this->assertEquals(1, $object->firstKey()); + } + + public function testToString() + { + $object = new Token(StringIterator::withLiteral('foobarbaz'), TokenType::notWhiteSpaceOrControl()); + $this->assertEquals('foobarbaz', $object->toString()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Token/TokenTypeTest.php b/tests/unit/SalesAgility/Imap/Token/TokenTypeTest.php new file mode 100755 index 0000000..6a496b2 --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Token/TokenTypeTest.php @@ -0,0 +1,256 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Token\TokenType; + +class TokenTypeTest extends \Codeception\Test\Unit +{ + /** @var UnitTester $tester*/ + protected $tester; + + public function testIsControlCharacter() + { + $object = TokenType::controlCharacter(); + $this->assertTrue($object->isControlCharacter()); + + $object = TokenType::whiteSpace(); + $this->assertFalse($object->isControlCharacter()); + } + + public function testIsPaired() + { + $object = TokenType::paired(); + $this->assertTrue($object->isPaired()); + + $object = TokenType::whiteSpace(); + $this->assertFalse($object->isPaired()); + } + + public function testFoldingWhiteSpace() + { + $object = TokenType::foldingWhiteSpace(); + $this->assertTrue($object->isFoldingWhiteSpace()); + } + + public function testIsNonFoldedLiteral() + { + $object = TokenType::nonFoldedLiteral(); + $this->assertTrue($object->isNonFoldedLiteral()); + } + + public function testWhiteSpace() + { + $object = TokenType::whiteSpace(); + $this->assertTrue($object->isWhiteSpace()); + } + + public function testDot() + { + $object = TokenType::dot(); + $this->assertTrue($object->isDot()); + } + + public function testIsAngledAddress() + { + $object = TokenType::angledAddress(); + $this->assertTrue($object->isAngledAddress()); + + $object = TokenType::nonFoldedLiteral(); + $this->assertFalse($object->isAngledAddress()); + } + + public function testNotWhiteSpaceOrControl() + { + $object = TokenType::notWhiteSpaceOrControl(); + $this->assertTrue($object->isNotWhiteSpaceOrControl()); + } + + public function testIsRequiredLineLength() + { + $object = TokenType::requiredLineLength(); + $this->assertTrue($object->isRequiredLineLength()); + } + + public function testIsAtSign() + { + $object = TokenType::atSign(); + $this->assertTrue($object->isAtSign()); + + $object = TokenType::nonFoldedLiteral(); + $this->assertFalse($object->isAtSign()); + } + + public function testGroup() + { + $object = TokenType::group(); + $this->assertTrue($object->isGroup()); + } + + public function testEndOfLine() + { + $object = TokenType::endOfLine(); + $this->assertTrue($object->isEndOfLine()); + } + + public function testRecommendedLineLength() + { + $object = TokenType::recommendedLineLength(); + $this->assertTrue($object->isRecommendedLineLength()); + } + + public function testIsRecommendedLineLength() + { + $object = TokenType::recommendedLineLength(); + $this->assertTrue($object->isRecommendedLineLength()); + + $object = TokenType::nonFoldedLiteral(); + $this->assertFalse($object->isRecommendedLineLength()); + } + + public function testIsDot() + { + $object = TokenType::dot(); + $this->assertTrue($object->isDot()); + } + + public function testIsSpecial() + { + $object = TokenType::special(); + $this->assertTrue($object->isSpecial()); + } + + public function testRequiredLineLength() + { + $object = TokenType::requiredLineLength(); + $this->assertTrue($object->isRequiredLineLength()); + + } + + public function testNonFoldedLiteral() + { + $object = TokenType::nonFoldedLiteral(); + $this->assertTrue($object->isNonFoldedLiteral()); + } + + public function testQuoted() + { + $object = TokenType::quoted(); + $this->assertTrue($object->isQuoted()); + } + + public function testControlCharacter() + { + $object = TokenType::controlCharacter(); + $this->assertTrue($object->isControlCharacter()); + } + + public function testAtSign() + { + $object = TokenType::atSign(); + $this->assertTrue($object->isAtSign()); + } + + public function testIsEndOfLine() + { + $object = TokenType::endOfLine(); + $this->assertTrue($object->isEndOfLine()); + + $object = TokenType::nonFoldedLiteral(); + $this->assertFalse($object->isEndOfLine()); + } + + public function testIsWhiteSpace() + { + $object = TokenType::whiteSpace(); + $this->assertTrue($object->isWhiteSpace()); + + $object = TokenType::nonFoldedLiteral(); + $this->assertFalse($object->isWhiteSpace()); + } + + public function testIsFoldingWhiteSpace() + { + $object = TokenType::foldingWhiteSpace(); + $this->assertTrue($object->isFoldingWhiteSpace()); + + $object = TokenType::nonFoldedLiteral(); + $this->assertFalse($object->isFoldingWhiteSpace()); + } + + public function testSpecial() + { + $object = TokenType::special(); + $this->assertTrue($object->isSpecial()); + } + + public function testListSeparator() + { + $object = TokenType::listSeparator(); + $this->assertTrue($object->isListSeparator()); + } + + public function testIsQuoted() + { + $object = TokenType::quoted(); + $this->assertTrue($object->isQuoted()); + + $object = TokenType::nonFoldedLiteral(); + $this->assertFalse($object->isQuoted()); + } + + public function testIsNotWhiteSpaceOrControl() + { + $object = TokenType::notWhiteSpaceOrControl(); + $this->assertTrue($object->isNotWhiteSpaceOrControl()); + + $object = TokenType::nonFoldedLiteral(); + $this->assertFalse($object->isNotWhiteSpaceOrControl()); + } + + public function testIsGroup() + { + $object = TokenType::group(); + $this->assertTrue($object->isGroup()); + + $object = TokenType::nonFoldedLiteral(); + $this->assertFalse($object->isGroup()); + } + + public function testIsListSeparator() + { + $object = TokenType::listSeparator(); + $this->assertTrue($object->isListSeparator()); + + $object = TokenType::nonFoldedLiteral(); + $this->assertFalse($object->isListSeparator()); + } + + public function testPaired() + { + $object = TokenType::paired(); + $this->assertTrue($object->isPaired()); + + } + + public function testAngledAddress() + { + $object = TokenType::angledAddress(); + $this->assertTrue($object->isAngledAddress()); + } +} diff --git a/tests/unit/SalesAgility/Imap/Token/TokenizerTest.php b/tests/unit/SalesAgility/Imap/Token/TokenizerTest.php new file mode 100755 index 0000000..6a8911a --- /dev/null +++ b/tests/unit/SalesAgility/Imap/Token/TokenizerTest.php @@ -0,0 +1,877 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Imap\Token\Tokenizer; +use SalesAgility\Iteration\StringIterator; +use SalesAgility\Imap\Token\Token; +use SalesAgility\Imap\Token\TokenType; +use SalesAgility\Imap\Token\TokenException; + +/** + * Class TokenizerTest + * @see https://www.ietf.org/rfc/rfc2822.txt + */ +class TokenizerTest extends \Codeception\Test\Unit +{ + + /** @var UnitTester $tester*/ + protected $tester; + public function testIsNotWhiteSpaceOrControl() { + $object = new Tokenizer(); + $methodName = 'isNotWhiteSpaceOrControl'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + $actual = $method->invokeArgs($object, array('a')); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array('1')); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array('[')); + $this->assertTrue($actual); + + // negative test + $actual = $method->invokeArgs($object, array("\x20")); + $this->assertFalse($actual); + + $actual = $method->invokeArgs($object, array("\x09")); + $this->assertFalse($actual); + + $actual = $method->invokeArgs($object, array("\x0D")); + $this->assertFalse($actual); + + $actual = $method->invokeArgs($object, array("\x0A")); + $this->assertFalse($actual); + } + public function testIsWhiteSpace() { + $object = new Tokenizer(); + $methodName = 'isWhiteSpace'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + $actual = $method->invokeArgs($object, array("\x20")); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array("\x09")); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('a')); + $this->assertFalse($actual); + + $actual = $method->invokeArgs($object, array('1')); + $this->assertFalse($actual); + + $actual = $method->invokeArgs($object, array('[')); + $this->assertFalse($actual); + } + + public function testisCarriageReturn() { + $object = new Tokenizer(); + $methodName = 'isCarriageReturn'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + $actual = $method->invokeArgs($object, array("\x0D")); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array("\x09")); + $this->assertFalse($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('a')); + $this->assertFalse($actual); + } + + public function testIsLineFeed() { + $object = new Tokenizer(); + $methodName = 'isLineFeed'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + $actual = $method->invokeArgs($object, array("\x0A")); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array("\x09")); + $this->assertFalse($actual); + + $actual = $method->invokeArgs($object, array('a')); + $this->assertFalse($actual); + } + + public function testIsSpecial() { + $object = new Tokenizer(); + $methodName = 'isSpecial'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + $actual = $method->invokeArgs($object, array('(')); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array(')')); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array('[')); + $this->assertTrue($actual); + + + $actual = $method->invokeArgs($object, array(']')); + $this->assertTrue($actual); + + + $actual = $method->invokeArgs($object, array(';')); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array(':')); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array('<')); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array('>')); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array('\\')); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array('@')); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array('.')); + $this->assertTrue($actual); + + $actual = $method->invokeArgs($object, array(',')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('\'')); + $this->assertFalse($actual); + + + $actual = $method->invokeArgs($object, array('"')); + $this->assertFalse($actual); + } + + public function testIsQuotedPair() { + $object = new Tokenizer(); + $methodName = 'isQuotedPair'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array('"')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('\'')); + $this->assertFalse($actual); + } + + public function testIsSpecialGroup() + { + $object = new Tokenizer(); + $methodName = 'isSpecialGroup'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array('(')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array(')')); + $this->assertFalse($actual); + } + + public function testIsSpecialClosingGroup() + { + $object = new Tokenizer(); + $methodName = 'isSpecialClosingGroup'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array(')')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('(')); + $this->assertFalse($actual); + } + + public function testIsSpecialOption() { + $object = new Tokenizer(); + $methodName = 'isSpecialOption'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array('[')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array(']')); + $this->assertFalse($actual); + } + + public function testIsSpecialClosingOption() + { + $object = new Tokenizer(); + $methodName = 'isSpecialClosingOption'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array(']')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('[')); + $this->assertFalse($actual); + } + + public function testIsSpecialAngledAddress() + { + $object = new Tokenizer(); + $methodName = 'isSpecialAngledAddress'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array('<')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('>')); + $this->assertFalse($actual); + } + + public function testIsSpecialClosingAngledAddress() + { + $object = new Tokenizer(); + $methodName = 'isSpecialClosingAngledAddress'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array('>')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('<')); + $this->assertFalse($actual); + } + + public function testIsSpecialEscape() { + $object = new Tokenizer(); + $methodName = 'isSpecialEscape'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array('\\')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('a')); + $this->assertFalse($actual); + } + + public function testIsSpecialAt() { + $object = new Tokenizer(); + $methodName = 'isSpecialAt'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array('@')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('a')); + $this->assertFalse($actual); + } + + public function testIsSpecialDot() { + $object = new Tokenizer(); + $methodName = 'isSpecialDot'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array('.')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('a')); + $this->assertFalse($actual); + } + + public function testIsSpecialListSeperator() { + $object = new Tokenizer(); + $methodName = 'isSpecialListSeparator'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array(',')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('a')); + $this->assertFalse($actual); + } + + + public function testIsDoubleQuote() { + $object = new Tokenizer(); + $methodName = 'isDoubleQuote'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + $actual = $method->invokeArgs($object, array('"')); + $this->assertTrue($actual); + + // negative tests + $actual = $method->invokeArgs($object, array('a')); + $this->assertFalse($actual); + } + + public function testSeekNotWhiteSpaceOrControl() + { + $object = new Tokenizer(); + $methodName = 'seekNotWhiteSpaceOrControl'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + // negative tests + $this->tester->expectException( + new \InvalidArgumentException('Iterator::current() must not start with whitespace or control character'), + function () use ($object, $method) { + $iterator = StringIterator::withLiteral(' '); + $method->invokeArgs($object, array(&$iterator)); + } + ); + } + + public function testSeekEndOfLine () { + $object = new Tokenizer(); + $methodName = 'seekEndOfLine'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + $string = "\x0D\x0A"; + $iterator = new StringIterator($string); + // Move current position +1 after the opening character + /** @var Token $actual */ + $actual = $method->invokeArgs($object, array(&$iterator)); + $actualString = $actual->toString(); + $this->assertEquals($string, $actualString); + $this->assertInstanceOf(Token::class, $actual); + $this->assertEquals($iterator->current(), "\x0A"); + $actual->rewind(); + $this->assertEquals($actual->current(), "\x0D"); + $actual->fastForward(); + $this->assertEquals($actual->current(), "\x0A"); + + // negative tests + $string = 'f' . "\x0D\x0A"; + $iterator = new StringIterator($string, 0); + $this->tester->expectException( + new InvalidArgumentException('Iterator::current() must start with a carriage return'), + function () use ($object, $method, $iterator) { + $method->invokeArgs($object, array(&$iterator)); + } + ); + + $string = "\x0D"; + $iterator = new StringIterator($string); + // Move current position +1 after the opening character + /** @var Token $actual */ + $actual = $method->invokeArgs($object, array(&$iterator)); + $this->assertEquals(false, $actual); + + $string = "\x0Da"; + $iterator = new StringIterator($string); + // Move current position +1 after the opening character + /** @var Token $actual */ + $actual = $method->invokeArgs($object, array(&$iterator)); + $this->assertEquals(false, $actual); + } + + public function testSeekClosingPair() + { + $object = new Tokenizer(); + $methodName = 'seekClosingPair'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + $openCharacter = "\x28"; + $closeCharacter = "\x29"; + $string = '()'; + $iterator = new StringIterator($string); + // Move current position +1 after the opening character + /** @var Token $actual */ + $actual = $method->invokeArgs($object, array(&$iterator, $openCharacter, $closeCharacter)); + $this->assertEquals(')', $iterator->current(), 'Offset character must be the close character'); + $this->assertInstanceOf(Token::class, $actual); + $this->assertTrue($actual->type()->isGroup()); + $this->assertEquals($actual->current(), '(', 'Offset character must be the open character'); + $actual->fastForward(); + $this->assertEquals($actual->current(), ')', 'Offset character must be the close character'); + + $openCharacter = "\x28"; + $closeCharacter = "\x29"; + $string = '(())'; + $iterator = new StringIterator($string); + // Move current position +1 after the opening character + /** @var Token $actual */ + $actual = $method->invokeArgs($object, array(&$iterator, $openCharacter, $closeCharacter)); + $this->assertEquals(')', $iterator->current(), 'Offset character must be the close character'); + $this->assertInstanceOf(Token::class, $actual); + $this->assertTrue($actual->type()->isGroup()); + $this->assertEquals($actual->current(), '(', 'Offset character must be the open character'); + $actual->fastForward(); + $this->assertEquals($actual->current(), ')', 'Offset character must be the close character'); + + $openCharacter = "\x28"; + $closeCharacter = "\x29"; + $string = '(("Daniel" NIL "user" "localhost.localdomain"))'; + $iterator = new StringIterator($string); + // Move current position +1 after the opening character + /** @var Token $actual */ + $actual = $method->invokeArgs($object, array(&$iterator, $openCharacter, $closeCharacter)); + $this->assertEquals(')', $iterator->current(), 'Offset character must be the close character'); + $this->assertInstanceOf(Token::class, $actual); + $this->assertEquals($actual->current(), '(', 'Offset character must be the open character'); + $this->assertTrue($actual->type()->isGroup()); + $this->assertEquals(')', $iterator->current(), 'Offset character must be the close character'); + $actual->fastForward(); + $this->assertEquals($actual->current(), ')', 'Offset character must be the close character'); + $this->assertEquals($actual->key(), strlen($string) - 1); + + $openCharacter = "\x28"; + $closeCharacter = "\x29"; + $string = '('."\x0D\x0A".'("Daniel" NIL "user" "localhost.localdomain")'."\x0D\x0A".')'; + $iterator = new StringIterator($string); + // Move current position +1 after the opening character + /** @var Token $actual */ + $actual = $method->invokeArgs($object, array(&$iterator, $openCharacter, $closeCharacter)); + $this->assertEquals(')', $iterator->current(), 'Offset character must be the close character'); + $this->assertInstanceOf(Token::class, $actual); + $this->assertTrue($actual->type()->isGroup()); + $this->assertEquals($actual->current(), '(', 'Offset character must be the open character'); + $actual->fastForward(); + $this->assertEquals($actual->current(), ')', 'Offset character must be the close character'); + $this->assertEquals($actual->key(), strlen($string) - 1); + + $openCharacter = ":"; + $closeCharacter = ";"; + $string = ':value;comment'; + $iterator = new StringIterator($string); + // Move current position +1 after the opening character + /** @var Token $actual */ + $actual = $method->invokeArgs($object, array(&$iterator, $openCharacter, $closeCharacter)); + $this->assertEquals(';', $iterator->current(), 'Offset character must be the close character'); + $this->assertInstanceOf(Token::class, $actual); + $this->assertTrue($actual->type()->isPaired()); + $this->assertEquals($actual->current(), ':', 'Offset character must be the open character'); + $actual->fastForward(); + $this->assertEquals($actual->current(), ';', 'Offset character must be the close character'); + + // negative cases + $openCharacter = "\x28"; + $closeCharacter = "\x29"; + $string = '(("Daniel" NIL "user" "localhost.localdomain")'; + $iterator = new StringIterator($string); + // Move current position +1 after the opening character + /** @var Token $actual */ + $actual = $method->invokeArgs($object, array(&$iterator, $openCharacter, $closeCharacter)); + $this->assertEquals(false, $actual); + + $string = '(("Daniel" NIL "user" "localhost.localdomain")'; + $iterator = new StringIterator($string, 2); + $this->tester->expectException( + new InvalidArgumentException('Iterator->current() position must be at the open character'), + function () use ($object, $method, $iterator) { + $method->invokeArgs($object, array(&$iterator, '(', ')')); + } + ); + } + + public function testSeekLineFolding() + { + // Test for non folded lines + // Test for folded lines + // as a message body may not use CRLF so therefor it is not line folding + + $object = new Tokenizer(); + $methodName = 'seekLineFolding'; + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + // Positive Tests + $string = 'a b '. "\x0D\x0A" . "\x20\x20" . 'd e f g' . "\x0D\x0A"; + $iterator = new StringIterator($string, 4); + $actual = $method->invokeArgs($object, array(&$iterator)); + $this->assertNotEquals(true, $actual, 'seekLineFolding is not detecting the standard positive case'); + $this->assertInstanceOf(Token::class, $actual, 'seekLineFolding must return the token when a folded line has been detected'); + // TODO: Verify that the start and positions are correct + + // Negative Tests + $string = 'a b '. "\x0D\x0A" . "\x20\x20" . 'd e f g' . "\x0D\x0A"; + $iterator = new StringIterator($string); + $this->tester->expectException( + new InvalidArgumentException('Iterator::current() must start with a carriage return'), + function () use ($object, $method, $iterator) { + $method->invokeArgs($object, array(&$iterator)); + } + ); + + $string = 'a b '. "\x0D\x0A" . "a:" . 'd e f g' . "\x0D\x0A"; + $iterator = new StringIterator($string, 4); + $actual = $method->invokeArgs($object, array(&$iterator)); + $this->assertEquals(false, $actual, "seekLineFolding() failed to detect when the line folding does not exist"); + + $string = 'a b '. "\x0D\x0A" . "\x0D" . 'd e f g' . "\x0D\x0A"; + $iterator = new StringIterator($string, 4); + $actual = $method->invokeArgs($object, array(&$iterator)); + $this->assertEquals(false, $actual, 'seekLineFolding() failed to detect control characters'); + + $string = "\x0D"; + $iterator = new StringIterator($string); + $actual = $method->invokeArgs($object, array(&$iterator)); + $this->assertEquals(false, $actual, "seekLineFolding() failed to detect corrupted line ending"); + + $string = "\x0D\x0A"; + $iterator = new StringIterator($string); + $actual = $method->invokeArgs($object, array(&$iterator)); + $this->assertEquals(false, $actual, "seekLineFolding() failed to detect end of file"); + + $string = "\x0D\x0A\x0D\x0A"; + $iterator = new StringIterator($string); + $actual = $method->invokeArgs($object, array(&$iterator)); + $this->assertEquals(false, $actual, "seekLineFolding() failed to detect 2 x EOL"); + } + + // Please keep the parser until last + // as it will like help work our any issues faster + public function testParse() + { + $object = new Tokenizer(); + // test with group + $iterator = StringIterator::withLiteral('20 FETCH 1:20 (FLAGS UID BODY[HEADER] BODYSTRUCTURE)'."\x0D\x0A"); + /** @var \SalesAgility\Imap\Token\TokenList $tokenlist */ + $tokenlist = $object->parse($iterator); + $this->assertCount(10, $tokenlist); + $this->assertEquals('20', $tokenlist->offsetGet(0)->toString()); + $this->assertEquals("\x20", $tokenlist->offsetGet(1)->toString()); + $this->assertEquals('FETCH', $tokenlist->offsetGet(2)->toString()); + $this->assertEquals("\x20", $tokenlist->offsetGet(3)->toString()); + $this->assertEquals('1', $tokenlist->offsetGet(4)->toString()); + $this->assertEquals(':', $tokenlist->offsetGet(5)->toString()); + $this->assertEquals('20', $tokenlist->offsetGet(6)->toString()); + $this->assertEquals("\x20", $tokenlist->offsetGet(7)->toString()); + $this->assertEquals('(FLAGS UID BODY[HEADER] BODYSTRUCTURE)', $tokenlist->offsetGet(8)->toString()); + $this->assertEquals("\x0D\x0A", $tokenlist->offsetGet(9)->toString()); + + // test with optional + $iterator = StringIterator::withLiteral('* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted.'."\x0D\x0A"); + /** @var \SalesAgility\Imap\Token\TokenList $tokenlist */ + $tokenlist = $object->parse($iterator); + $this->assertCount(11, $tokenlist); + // test with DQUOTE group + $iterator = StringIterator::withLiteral('INTERNALDATE "05-Jun-2018 08:59:50 +0100"'); + /** @var \SalesAgility\Imap\Token\TokenList $tokenlist */ + $tokenlist = $object->parse($iterator); + $this->assertCount(3, $tokenlist); + + // test with angle brackets + $iterator = StringIterator::withLiteral('<address>'); + $tokenlist = $object->parse($iterator); + $this->assertCount(1, $tokenlist); + + // test list separator + // test @ sign + $iterator = StringIterator::withLiteral('A <a@localhost>, B <b@localhost>'); + $tokenlist = $object->parse($iterator); + $this->assertCount(8, $tokenlist); + + + // test escaping + $iterator = StringIterator::withLiteral('\Answered'); + $tokenlist = $object->parse($iterator); + $this->assertCount(2, $tokenlist); + + // test . + $iterator = StringIterator::withLiteral('domain.tld'); + $tokenlist = $object->parse($iterator); + $this->assertCount(3, $tokenlist); + + // CTL + $iterator = StringIterator::withLiteral("\x0D"); + $tokenlist = $object->parse($iterator); + $this->assertCount(1, $tokenlist); + + $iterator = StringIterator::withLiteral("\x0A"); + $tokenlist = $object->parse($iterator); + $this->assertCount(1, $tokenlist); + + + // test line folding + $string = 'a b '. "\x0D\x0A" . "\x20\x20" . 'd e' . "\x0D\x0A"; + $iterator = StringIterator::withLiteral($string); + $tokenlist = $object->parse($iterator); + $this->assertCount(9, $tokenlist); + + // closing groups + $iterator = StringIterator::withLiteral(" ) "); + $tokenlist = $object->parse($iterator); + $this->assertCount(3, $tokenlist); + + // closing option + $iterator = StringIterator::withLiteral(" ] "); + $tokenlist = $object->parse($iterator); + $this->assertCount(3, $tokenlist); + + // closing DQUOTE + $iterator = StringIterator::withLiteral(" \" "); + $tokenlist = $object->parse($iterator); + $this->assertCount(3, $tokenlist); + + // closing angle bracket + $iterator = StringIterator::withLiteral(" > "); + $tokenlist = $object->parse($iterator); + $this->assertCount(3, $tokenlist); + + $iterator = StringIterator::withLiteral('@'); + $tokenlist = $object->parse($iterator); + $this->assertCount(1, $tokenlist); + + + // test possible errors + $iterator = StringIterator::withLiteral('(()'); + $tokenlist = $object->parse($iterator); + $this->assertCount(2, $tokenlist); + + $iterator = StringIterator::withLiteral('<<>'); + $tokenlist = $object->parse($iterator); + $this->assertCount(2, $tokenlist); + + $iterator = StringIterator::withLiteral('[[]'); + $tokenlist = $object->parse($iterator); + $this->assertCount(2, $tokenlist); + + + $iterator = StringIterator::withLiteral('<<>'); + $tokenlist = $object->parse($iterator); + $this->assertCount(2, $tokenlist); + + // line length + try { + $justRight = str_repeat("\x20", 998) . "\x0D\x0A"; + $iterator = StringIterator::withLiteral($justRight); + $tokenlist = $object->parse($iterator); + // Keep in mind that the tokenizer + // creates 998 for the spaces + a token to mark the limit + // + a token for the CRLF + $this->assertCount(1001, $tokenlist); + } catch (TokenException $e) { + $this->tester->fail('tokenizer required line length exceeded too early'); + } + + // negative tests + // line length + $this->tester->expectException( + TokenException::requiredLineLengthExceeded(), + function () { + $object = new Tokenizer(); + $tooLong = str_repeat("\x20", 999) . "\x0D\x0A"; + $iterator = StringIterator::withLiteral($tooLong); + $object->parse($iterator); + } + ); + + + $this->tester->expectException( + TokenException::requiredLineLengthExceeded(), + function () { + $object = new Tokenizer(); + $tooLong = str_repeat("\x20", 998) . "\x0D\x20"; + $iterator = StringIterator::withLiteral($tooLong); + $object->parse($iterator); + } + ); + + $this->tester->expectException( + TokenException::requiredLineLengthExceeded(), + function () { + $object = new Tokenizer(); + $tooLong = str_repeat("\x20", 1000); + $iterator = StringIterator::withLiteral($tooLong); + $object->parse($iterator); + } + ); + + $this->tester->expectException( + TokenException::requiredLineLengthExceeded(), + function () { + $object = new Tokenizer(); + $tooLong = str_repeat("\x20", 998) . "x0A"; + $iterator = StringIterator::withLiteral($tooLong); + $object->parse($iterator); + } + ); + + $this->tester->expectException( + TokenException::requiredLineLengthExceeded(), + function () { + $object = new Tokenizer(); + $tooLong = str_repeat("\x20", 998) . "\x29"; + $iterator = StringIterator::withLiteral($tooLong); + $object->parse($iterator); + } + ); + + + $this->tester->expectException( + TokenException::requiredLineLengthExceeded(), + function () { + $object = new Tokenizer(); + $tooLong = str_repeat("\x20", 998) . "\x0A\x20"; + $iterator = StringIterator::withLiteral($tooLong); + $object->parse($iterator); + } + ); + + } + + public function testParseWithoutLineRestrictions() + { + $object = new Tokenizer(); + // test with group + $iterator = StringIterator::withLiteral('20 FETCH 1:20 (FLAGS UID BODY[HEADER] BODYSTRUCTURE)'."\x0D\x0A"); + /** @var \SalesAgility\Imap\Token\TokenList $tokenlist */ + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(10, $tokenlist); + $this->assertEquals('20', $tokenlist->offsetGet(0)->toString()); + $this->assertEquals("\x20", $tokenlist->offsetGet(1)->toString()); + $this->assertEquals('FETCH', $tokenlist->offsetGet(2)->toString()); + $this->assertEquals("\x20", $tokenlist->offsetGet(3)->toString()); + $this->assertEquals('1', $tokenlist->offsetGet(4)->toString()); + $this->assertEquals(':', $tokenlist->offsetGet(5)->toString()); + $this->assertEquals('20', $tokenlist->offsetGet(6)->toString()); + $this->assertEquals("\x20", $tokenlist->offsetGet(7)->toString()); + $this->assertEquals('(FLAGS UID BODY[HEADER] BODYSTRUCTURE)', $tokenlist->offsetGet(8)->toString()); + $this->assertEquals("\x0D\x0A", $tokenlist->offsetGet(9)->toString()); + + // test with optional + $iterator = StringIterator::withLiteral('* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted.'."\x0D\x0A"); + /** @var \SalesAgility\Imap\Token\TokenList $tokenlist */ + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(11, $tokenlist); + // test with DQUOTE group + $iterator = StringIterator::withLiteral('INTERNALDATE "05-Jun-2018 08:59:50 +0100"'); + /** @var \SalesAgility\Imap\Token\TokenList $tokenlist */ + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(3, $tokenlist); + + // test with angle brackets + $iterator = StringIterator::withLiteral('<address>'); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(1, $tokenlist); + + // test list separator + // test @ sign + $iterator = StringIterator::withLiteral('A <a@localhost>, B <b@localhost>'); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(8, $tokenlist); + + + // test escaping + $iterator = StringIterator::withLiteral('\Answered'); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(2, $tokenlist); + + // test . + $iterator = StringIterator::withLiteral('domain.tld'); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(3, $tokenlist); + + // CTL + $iterator = StringIterator::withLiteral("\x0D"); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(1, $tokenlist); + + $iterator = StringIterator::withLiteral("\x0A"); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(1, $tokenlist); + + + // test line folding + $string = 'a b '. "\x0D\x0A" . "\x20\x20" . 'd e' . "\x0D\x0A"; + $iterator = StringIterator::withLiteral($string); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(9, $tokenlist); + + // closing groups + $iterator = StringIterator::withLiteral(" ) "); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(3, $tokenlist); + + // closing option + $iterator = StringIterator::withLiteral(" ] "); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(3, $tokenlist); + + // closing DQUOTE + $iterator = StringIterator::withLiteral(" \" "); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(3, $tokenlist); + + // closing angle bracket + $iterator = StringIterator::withLiteral(" > "); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(3, $tokenlist); + + $iterator = StringIterator::withLiteral('@'); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(1, $tokenlist); + + + // test possible errors + $iterator = StringIterator::withLiteral('(()'); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(2, $tokenlist); + + $iterator = StringIterator::withLiteral('<<>'); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(2, $tokenlist); + + $iterator = StringIterator::withLiteral('[[]'); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(2, $tokenlist); + + + $iterator = StringIterator::withLiteral('<<>'); + $tokenlist = $object->parseWithoutLineRestrictions($iterator); + $this->assertCount(2, $tokenlist); + + } +} diff --git a/tests/unit/SalesAgility/Iteration/StringIteratorTest.php b/tests/unit/SalesAgility/Iteration/StringIteratorTest.php new file mode 100755 index 0000000..55a1cf2 --- /dev/null +++ b/tests/unit/SalesAgility/Iteration/StringIteratorTest.php @@ -0,0 +1,155 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Iteration\StringIterator; + +class StringIteratorTest extends \Codeception\Test\Unit +{ + /** @var UnitTester */ + protected $tester; + + public function test__construct() + { + $string = ''; + $class = new StringIterator($string); + $this->assertEquals(0, $class->key()); + + $string = 'bar'; + $class = new StringIterator($string, 0, 1); + $this->assertEquals(0, $class->key()); + $this->assertEquals('b', $class->current()); + $class->next(); + $this->assertEquals(false, $class->valid()); + + $string = 'bar'; + $class = new StringIterator($string, 1, 2); + $this->assertEquals(1, $class->key()); + $this->assertEquals('a', $class->current()); + $class->next(); + $this->assertEquals('r', $class->current()); + $class->next(); + $this->assertEquals(false, $class->valid()); + + // Negative Test + $this->tester->expectException( + new \InvalidArgumentException('$string must be a string'), + function () { + $string = array(); + new StringIterator($string); + }); + + $this->tester->expectException( + new \InvalidArgumentException('$offset must be a integer'), + function () { + $string = ''; + new StringIterator($string, true); + }); + + $this->tester->expectException( + new \InvalidArgumentException('$count must be a integer'), + function () { + $string = ''; + new StringIterator($string, 0, true); + }); + } + + public function testWithLiteral() + { + $class = StringIterator::withLiteral('foo'); + $this->assertEquals('f', $class->current()); + } + + public function testCurrent() + { + $string = 'bar'; + $class = new StringIterator($string); + $this->assertEquals('b', $class->current()); + } + + public function testNext() + { + $string = 'bar'; + $class = new StringIterator($string); + $class->next(); + $this->assertEquals('a', $class->current()); + $this->assertEquals(1, $class->key()); + } + + public function testRewind() + { + $string = 'bar'; + $class = new StringIterator($string); + $class->next(); + $class->rewind(); + $this->assertEquals(0, $class->key()); + } + + public function testValid() + { + $string = ''; + $class = new StringIterator($string); + $this->assertEquals(false, $class->valid()); + + $string = 'bar'; + $class = new StringIterator($string); + $this->assertEquals(true, $class->valid()); + + $class->next(); + $class->next(); + $class->next(); + $this->assertEquals(false, $class->valid()); + } + + public function testKey() + { + $string = 'bar'; + $class = new StringIterator($string); + $class->next(); + $this->assertEquals(1, $class->key()); + } + + public function testFastForward() + { + $string = 'bar'; + $class = new StringIterator($string); + $class->fastForward(); + $this->assertEquals(2, $class->key()); + } + + public function testGetInnerString() + { + $string = 'bar'; + $class = new StringIterator($string); + $this->assertEquals('bar', $class->getInnerString()); + } + + public function testSeek() + { + $string = 'bar'; + $class = new StringIterator($string); + $return = $class->seek(2); + $this->assertEquals(2, $class->key()); + $this->assertEquals(2, $return); + + // negative + $return = $class->seek(10000); + $this->assertEquals(2, $class->key()); + $this->assertFalse($return); + } +} diff --git a/tests/unit/SalesAgility/Iteration/StringValueTest.php b/tests/unit/SalesAgility/Iteration/StringValueTest.php new file mode 100755 index 0000000..1741436 --- /dev/null +++ b/tests/unit/SalesAgility/Iteration/StringValueTest.php @@ -0,0 +1,72 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Utility\StringValue; + +class StringValueTest extends \Codeception\Test\Unit +{ + /** + * @var \UnitTester + */ + protected $tester; + + public function testStartsWith() + { + $testString = 'foobarbaz'; + $this->assertTrue(StringValue::startsWith($testString, 'foo')); + $this->assertFalse(StringValue::startsWith($testString, 'bar')); + + // Test negative cases + $this->tester->expectException( + new \Exception('StringValue::startsWith $haystack must be a string'), + function () { + StringValue::startsWith(1, 'foo'); + } + ); + + $this->tester->expectException( + new \Exception('StringValue::startsWith $needle must be a string'), + function () { + StringValue::startsWith('bar', 1); + } + ); + } + + public function testEndsWith() + { + $testString = 'foobarbaz'; + $this->assertTrue(StringValue::endsWith($testString, 'baz')); + $this->assertFalse(StringValue::endsWith($testString, 'bar')); + + // Test negative cases + $this->tester->expectException( + new \Exception('StringValue::endsWith $haystack must be a string'), + function () { + StringValue::endsWith(1, 'foo'); + } + ); + + $this->tester->expectException( + new \Exception('StringValue::endsWith $needle must be a string'), + function () { + StringValue::endsWith('bar', 1); + } + ); + } +} diff --git a/tests/unit/SalesAgility/Stream/ConnectionExceptionTest.php b/tests/unit/SalesAgility/Stream/ConnectionExceptionTest.php new file mode 100755 index 0000000..ec4b5fd --- /dev/null +++ b/tests/unit/SalesAgility/Stream/ConnectionExceptionTest.php @@ -0,0 +1,38 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Stream\ConnectionException; + +class ConnectionExceptionTest extends \Codeception\Test\Unit +{ + + public function testConnectionFailure() + { + $object = ConnectionException::connectionFailure(''); + $this->assertEquals(ConnectionException::CODE_CONNECTION_FAILURE, $object->getCode()); + $this->assertEquals('Failed to connected: ', $object->getMessage()); + } + + public function testTimeout() + { + $object = ConnectionException::timeout(''); + $this->assertEquals(ConnectionException::CODE_TIMEOUT, $object->getCode()); + $this->assertEquals('Timed Out: ', $object->getMessage()); + } +} diff --git a/tests/unit/SalesAgility/Stream/ConnectionTest.php b/tests/unit/SalesAgility/Stream/ConnectionTest.php new file mode 100755 index 0000000..b36abf0 --- /dev/null +++ b/tests/unit/SalesAgility/Stream/ConnectionTest.php @@ -0,0 +1,296 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Stream\Connection; +use SalesAgility\Stream\StreamConnectionInterface; +use \SalesAgility\Stream\ConnectionException; +use SalesAgility\Imap\ManagerFactory; + +require_once __DIR__.'/../../../mock/stream.php'; + +class ConnectionTest extends \Codeception\Test\Unit +{ + /** + * @var UnitTester + */ + protected $tester; + + public function testSetConnectionString() + { + $class = new Connection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionTimeout = $reflection->getProperty('connectionTimeout'); + $propertyConnectionTimeout->setAccessible(true); + $propertyConnectionTTL = $reflection->getProperty('connectionTTL'); + $propertyConnectionTTL->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $expected = 'tcp://google.com:80'; + $class->setConnectionString($expected); + $actual = $propertyConnectionString->getValue($class); + $this->assertEquals($expected, $actual); + + // Test negative cases + $this->tester->expectException( + new \Exception('connection string must be a string'), + function () use ($class) { + $class->setConnectionString(1); + } + ); + + $this->tester->expectException( + new \Exception('connection string must not be empty'), + function () use ($class) { + $class->setConnectionString(''); + } + ); + } + + public function testEnableEncryption() + { + $class = new Connection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionTimeout = $reflection->getProperty('connectionTimeout'); + $propertyConnectionTimeout->setAccessible(true); + $propertyConnectionTTL = $reflection->getProperty('connectionTTL'); + $propertyConnectionTTL->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $expected = Connection::TLS; + $class->enableEncryption($expected); + $actual = $propertySecurity->getValue($class); + $this->assertEquals($expected, $actual); + } + + public function testDisableEncryption() + { + $class = new Connection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionTimeout = $reflection->getProperty('connectionTimeout'); + $propertyConnectionTimeout->setAccessible(true); + $propertyConnectionTTL = $reflection->getProperty('connectionTTL'); + $propertyConnectionTTL->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $expected = false; + $class->disableEncryption(); + $actual = $propertySecurity->getValue($class); + $this->assertEquals($expected, $actual, 'Failed To Disable Encryption'); + } + + public function testIsConnected() + { + $class = new Connection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionTimeout = $reflection->getProperty('connectionTimeout'); + $propertyConnectionTimeout->setAccessible(true); + $propertyConnectionTTL = $reflection->getProperty('connectionTTL'); + $propertyConnectionTTL->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $expected = false; + $class->isConnected(); + $actual = $class->isConnected(); + $this->assertEquals($expected, $actual); + + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + + $expected = true; + $class->isConnected(); + $actual = $class->isConnected(); + $this->assertEquals($expected, $actual); + } + + + public function testConnect() + { + $log = new \SalesAgility\Utility\PimapLogger(); + $class = new Connection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionTimeout = $reflection->getProperty('connectionTimeout'); + $propertyConnectionTimeout->setAccessible(true); + $propertyConnectionTTL = $reflection->getProperty('connectionTTL'); + $propertyConnectionTTL->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + $expected = true; + $class->isConnected(); + $actual = $class->isConnected(); + $this->assertEquals($expected, $actual); + $class->disconnect(); + + // negative tests + $this->tester->expectException( + ConnectionException::connectionFailure('Connection String is empty. expected tcp://address:port'), + function () { + $class = new Connection(ManagerFactory::instance()); + $class->connect(); + } + ); + + $GLOBALS['mock_stream_socket_client'] = false; + $this->tester->expectException( + ConnectionException::connectionFailure('Unable to connect to tcp://google.com:443'), + function () { + $class = new Connection(ManagerFactory::instance()); + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + } + ); + unset($GLOBALS['mock_stream_socket_client']); + + $GLOBALS['mock_stream_socket_enable_crypto_exception'] = new \Exception('mock_stream_socket_enable_crypto_exception'); + $this->tester->expectException( + ConnectionException::connectionFailure('Failed to enable security'), + function () { + $class = new Connection(ManagerFactory::instance()); + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + } + ); + unset($GLOBALS['mock_stream_socket_enable_crypto_exception']); + } + + + public function testDisconnect() + { + $class = new Connection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionTimeout = $reflection->getProperty('connectionTimeout'); + $propertyConnectionTimeout->setAccessible(true); + $propertyConnectionTTL = $reflection->getProperty('connectionTTL'); + $propertyConnectionTTL->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + $class->disconnect(); + $expected = false; + $class->isConnected(); + $actual = $class->isConnected(); + $this->assertEquals($expected, $actual); + } + + + public function testTransmitMessage() + { + $class = new Connection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionTimeout = $reflection->getProperty('connectionTimeout'); + $propertyConnectionTimeout->setAccessible(true); + $propertyConnectionTTL = $reflection->getProperty('connectionTTL'); + $propertyConnectionTTL->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + $class->transmitMessage("GET / HTTP/1.0\r\nHost: google.com\r\n\r\n\r\n"); + $class->disconnect(); + } + + public function testReadMessage() + { + $class = new Connection(ManagerFactory::instance()); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionTimeout = $reflection->getProperty('connectionTimeout'); + $propertyConnectionTimeout->setAccessible(true); + $propertyConnectionTTL = $reflection->getProperty('connectionTTL'); + $propertyConnectionTTL->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + $class->transmitMessage("GET / HTTP/1.0\r\nHost: google.com\r\n\r\n\r\n"); + $class->readMessage(); + $class->disconnect(); + } + + public function testIsEndOfLine() + { + $class = new Connection(ManagerFactory::instance()); + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + $class->transmitMessage("GET / HTTP/1.0\r\nHost: google.com\r\n\r\n\r\n"); + $this->assertFalse($class->isEndOfFile()); + + $GLOBALS['mock_feof'] = true; + + // It is generally a bad practice to have loops in unit tests. + // however, we need to get to the end of the html file + // in order to test this method. + while(!$class->isEndOfFile()) { + $class->readMessage(); + } + + $this->assertTrue($class->isEndOfFile()); + + $class->disconnect(); + } +} diff --git a/tests/unit/SalesAgility/Stream/MessageTransporterTest.php b/tests/unit/SalesAgility/Stream/MessageTransporterTest.php new file mode 100755 index 0000000..d157f66 --- /dev/null +++ b/tests/unit/SalesAgility/Stream/MessageTransporterTest.php @@ -0,0 +1,115 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Stream\MessageTransporter; +use SalesAgility\Stream\MockConnection as Connection; +use SalesAgility\Stream\ConnectionException; +use SalesAgility\Imap\ManagerFactory; + +class MessageTransporterTest extends \Codeception\Test\Unit +{ + /** + * @var UnitTester + */ + protected $tester; + + public function testSetConnection() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $reflection = new \ReflectionClass(MessageTransporter::class); + $propertyConnection = $reflection->getProperty('connection'); + $propertyConnection->setAccessible(true); + + $connection = new Connection(); + $object->setConnection($connection); + $this->assertEquals($connection, $propertyConnection->getValue($object)); + } + + public function testConnection() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $connection = new Connection(); + $object->setConnection($connection); + $this->assertEquals($connection, $object->connection()); + } + + public function testTransmit() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $connection = new Connection(); + $reflection = new \ReflectionClass(Connection::class); + $property = $reflection->getProperty('messageSent'); + $property->setAccessible(true); + $object->setConnection($connection); + $object->transmit('hello connection'); + $this->assertEquals('hello connection', $property->getValue($connection)); + } + + public function testReceive() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $connection = new Connection(); +// $reflection = new ReflectionClass(\SalesAgility\Stream\MockConnection::class); +// $property = $reflection->getProperty('messageReceived'); +// $property->setAccessible(true); +// $property->setValue( +// $connection, +// array('A1 OK nothing happened'."\x0A\x0D") +// ); + + $object->setConnection($connection); + $this->assertEquals('* OK mock server v1.0some headersome body', $object->receive()); + + // negative tests + $reflection = new \ReflectionClass(MessageTransporter::class); + $property = $reflection->getProperty('TTL'); + $property->setAccessible(true); + $property->setValue($object, 1); + } + + public function testIsEndOfFile() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $connection = new Connection(); + $object->setConnection($connection); + $object->receive(); + $this->assertTrue($object->isEndOfFile('')); + } + + public function testIsConfigured() + { + $object = new MessageTransporter(ManagerFactory::instance()); + $reflection = new \ReflectionClass(MessageTransporter::class); + $method = $reflection->getMethod('isConfigured'); + $method->setAccessible(true); + + $this->tester->expectException( + new \Exception('Connection must be set'), + function () use ($object, $method) { + $method->invoke($object); + } + ); + + $connection = new Connection(); + $object->setConnection($connection); + $this->assertTrue($method->invoke($object)); + } + + +} diff --git a/tests/unit/SalesAgility/Stream/MockConnectionTest.php b/tests/unit/SalesAgility/Stream/MockConnectionTest.php new file mode 100755 index 0000000..bbd4852 --- /dev/null +++ b/tests/unit/SalesAgility/Stream/MockConnectionTest.php @@ -0,0 +1,237 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Stream\MockConnection as Connection; + +class MockConnectionTest extends \Codeception\Test\Unit +{ + /** + * @var UnitTester + */ + protected $tester; + + public function testSetConnectionString() + { + $class = new Connection(); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $expected = 'tcp://google.com:80'; + $class->setConnectionString($expected); + $actual = $propertyConnectionString->getValue($class); + $this->assertEquals($expected, $actual); + + // Test negative cases + $this->tester->expectException( + new \Exception('connection string must be a string'), + function () use ($class) { + $class->setConnectionString(1); + } + ); + + $this->tester->expectException( + new \Exception('connection string must not be empty'), + function () use ($class) { + $class->setConnectionString(''); + } + ); + } + + public function testEnableEncryption() + { + $class = new Connection(); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $expected = Connection::TLS; + $class->enableEncryption($expected); + $actual = $propertySecurity->getValue($class); + $this->assertEquals($expected, $actual); + } + + public function testDisableEncryption() + { + $class = new Connection(); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $expected = false; + $class->disableEncryption(); + $actual = $propertySecurity->getValue($class); + $this->assertEquals($expected, $actual, 'Failed To Disable Encryption'); + } + + public function testIsConnected() + { + $class = new Connection(); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $expected = false; + $class->isConnected(); + $actual = $class->isConnected(); + $this->assertEquals($expected, $actual); + + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + + $expected = true; + $class->isConnected(); + $actual = $class->isConnected(); + $this->assertEquals($expected, $actual); + } + + + public function testConnect() + { + $log = new \SalesAgility\Utility\PimapLogger(); + $class = new Connection(); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->setLogger($log); + $class->connect(); + $expected = true; + $class->isConnected(); + $actual = $class->isConnected(); + $this->assertEquals($expected, $actual); + $class->disconnect(); + } + + + public function testDisconnect() + { + $class = new Connection(); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + $class->disconnect(); + $expected = false; + $class->isConnected(); + $actual = $class->isConnected(); + $this->assertEquals($expected, $actual); + } + + + public function testTransmitMessage() + { + $class = new Connection(); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + $class->transmitMessage("GET / HTTP/1.0\r\nHost: google.com\r\n\r\n\r\n"); + $class->disconnect(); + } + + public function testReadMessage() + { + $class = new Connection(); + $reflection = new \ReflectionClass(Connection::class); + $propertyResource = $reflection->getProperty('resource'); + $propertyResource->setAccessible(true); + $propertyConnectionString = $reflection->getProperty('connectionString'); + $propertyConnectionString->setAccessible(true); + $propertySecurity = $reflection->getProperty('security'); + $propertySecurity->setAccessible(true); + + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + $class->transmitMessage("GET / HTTP/1.0\r\nHost: google.com\r\n\r\n\r\n"); + $class->readMessage(); + $class->disconnect(); + } + + public function testIsEndOfLine() + { + $class = new Connection(); + $class->enableEncryption(Connection::TLS); + $class->setConnectionString('tcp://google.com:443'); + $class->connect(); + $class->transmitMessage("GET / HTTP/1.0\r\nHost: google.com\r\n\r\n\r\n"); + $this->assertFalse($class->isEndOfFile()); + + // It is generally a bad practice to have loops in unit tests. + // however, we need to get to the end of the html file + // in order to test this method. + while(!$class->isEndOfFile()) { + $class->readMessage(); + } + + $this->assertTrue($class->isEndOfFile()); + + $class->disconnect(); + } + + public function testSetLogger() + { + $log = new \SalesAgility\Utility\PimapLogger(); + $object = new Connection(); + $reflection = new \ReflectionClass(Connection::class); + $propertyLog = $reflection->getProperty('log'); + $propertyLog->setAccessible(true); + + $object->setLogger($log); + $this->assertEquals($log, $propertyLog->getValue($object)); + } +} diff --git a/tests/unit/SalesAgility/Utility/AssertTest.php b/tests/unit/SalesAgility/Utility/AssertTest.php new file mode 100755 index 0000000..50ea4b0 --- /dev/null +++ b/tests/unit/SalesAgility/Utility/AssertTest.php @@ -0,0 +1,44 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/ + +use SalesAgility\Utility\Assert; + +class AssertTest extends \Codeception\Test\Unit +{ + /** + * @var \UnitTester + */ + protected $tester; + + public function testIs() + { + $this->tester->expectException( + new \Exception(''), + function () { + Assert::is(false, ''); + } + ); + + try { + Assert::is(true, ''); + } catch (Exception $exception) { + $this->fail('was not expect exception'); + } + } +} diff --git a/tests/unit/SalesAgility/Utility/PimapLoggerTest.php b/tests/unit/SalesAgility/Utility/PimapLoggerTest.php new file mode 100644 index 0000000..bf4e6c7 --- /dev/null +++ b/tests/unit/SalesAgility/Utility/PimapLoggerTest.php @@ -0,0 +1,38 @@ +<?php +/** + * Created by PhpStorm. + * User: user + * Date: 27/08/18 + * Time: 14:21 + */ + +use SalesAgility\Utility\PimapLogger; + +class PimapLoggerTest extends \Codeception\Test\Unit +{ + + /** @var UnitTester $tester */ + protected $tester; + + public function testLog() + { + $object = new PimapLogger(); + $object->emergency('{a}', array('a' => 'test')); + $object->alert('{a}', array('a' => 'test')); + $object->critical('{a}', array('a' => 'test')); + $object->error('{a}', array('a' => 'test')); + $object->warning('{a}', array('a' => 'test')); + $object->notice('{a}', array('a' => 'test')); + $object->info('{a}', array('a' => 'test')); + $object->debug('{a}', array('a' => 'test')); + $object->debug('test'); + + $this->tester->expectException( + new \InvalidArgumentException('Invalid Log Level'), + function () { + $object = new PimapLogger(); + $object->log('invalidLevel', 'message'); + } + ); + } +} diff --git a/tests/unit/_bootstrap.php b/tests/unit/_bootstrap.php new file mode 100755 index 0000000..5049b83 --- /dev/null +++ b/tests/unit/_bootstrap.php @@ -0,0 +1,18 @@ +<?php +/********************************************************************************* + * Pimap is a PHP IMAP library developed by SalesAgility Ltd. + * Copyright (C) 2018 SalesAgility Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + *********************************************************************************/