From 8329a0287b5579470434e62a87ede4705179811f Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Wed, 19 Jan 2011 21:32:09 +0000 Subject: [PATCH] Upgrade to mime4j. "0.7" branch as of http://svn.apache.org/repos/asf/james/mime4j/trunk@1058339 --- src/com/fsck/k9/mail/Address.java | 31 +- .../fsck/k9/mail/internet/DecoderUtil.java | 4 +- .../fsck/k9/mail/internet/MimeMessage.java | 37 +- .../fsck/k9/mail/internet/MimeUtility.java | 4 +- .../james/mime4j/AbstractContentHandler.java | 113 --- .../james/mime4j/CloseShieldInputStream.java | 129 --- .../james/mime4j/MimeBoundaryInputStream.java | 184 ---- .../apache/james/mime4j/MimeException.java | 65 ++ .../apache/james/mime4j/MimeIOException.java | 58 ++ .../apache/james/mime4j/MimeStreamParser.java | 320 ------- .../apache/james/mime4j/RootInputStream.java | 111 --- .../james/mime4j/SimpleContentHandler.java | 100 -- .../james/mime4j/codec/Base64InputStream.java | 286 ++++++ .../mime4j/codec/Base64OutputStream.java | 321 +++++++ .../apache/james/mime4j/codec/CodecUtil.java | 108 +++ .../james/mime4j/codec/DecodeMonitor.java | 65 ++ .../james/mime4j/codec/DecoderUtil.java | 260 ++++++ .../james/mime4j/codec/EncoderUtil.java | 39 +- .../mime4j/codec/QuotedPrintableEncoder.java | 271 ------ .../codec/QuotedPrintableInputStream.java | 309 +++++++ .../codec/QuotedPrintableOutputStream.java | 213 ++++- .../mime4j/decoder/Base64InputStream.java | 146 --- .../james/mime4j/decoder/DecoderUtil.java | 277 ------ .../decoder/QuotedPrintableInputStream.java | 227 ----- .../decoder/UnboundedFifoByteBuffer.java | 272 ------ .../apache/james/mime4j/dom/BinaryBody.java | 33 + .../james/mime4j/{message => dom}/Body.java | 32 +- .../apache/james/mime4j/dom/Disposable.java | 36 + src/org/apache/james/mime4j/dom/Entity.java | 550 +++++++++++ src/org/apache/james/mime4j/dom/Header.java | 204 +++++ src/org/apache/james/mime4j/dom/Message.java | 512 +++++++++++ .../james/mime4j/dom/MessageBuilder.java | 46 + .../mime4j/dom/MessageBuilderFactory.java | 41 + .../apache/james/mime4j/dom/Multipart.java | 238 +++++ .../james/mime4j/dom/ServiceLoader.java | 95 ++ .../apache/james/mime4j/dom/SingleBody.java | 139 +++ .../mime4j/{message => dom}/TextBody.java | 31 +- .../{field => dom}/address/Address.java | 50 +- .../james/mime4j/dom/address/AddressList.java | 98 ++ .../james/mime4j/dom/address/DomainList.java | 95 ++ .../james/mime4j/dom/address/Group.java | 113 +++ .../james/mime4j/dom/address/Mailbox.java | 204 +++++ .../{field => dom}/address/MailboxList.java | 83 +- .../{field => dom}/datetime/DateTime.java | 72 +- .../mime4j/dom/field/AddressListField.java | 28 + .../dom/field/ContentDispositionField.java | 117 +++ .../field/ContentTransferEncodingField.java | 31 + .../mime4j/dom/field/ContentTypeField.java | 97 ++ .../james/mime4j/dom/field/DateTimeField.java | 9 + .../apache/james/mime4j/dom/field/Field.java | 51 ++ .../field/FieldName.java} | 63 +- .../james/mime4j/dom/field/MailboxField.java | 28 + .../mime4j/dom/field/MailboxListField.java | 28 + .../mime4j/dom/field/ParseException.java | 64 ++ .../james/mime4j/dom/field/ParsedField.java | 45 + .../mime4j/dom/field/UnstructuredField.java | 26 + .../james/mime4j/field/AbstractField.java | 97 ++ ...stField.java => AddressListFieldImpl.java} | 64 +- .../field/ContentDispositionFieldImpl.java | 253 ++++++ ... => ContentTransferEncodingFieldImpl.java} | 67 +- .../james/mime4j/field/ContentTypeField.java | 256 ------ .../mime4j/field/ContentTypeFieldImpl.java | 208 +++++ .../james/mime4j/field/DateTimeField.java | 65 -- .../james/mime4j/field/DateTimeFieldImpl.java | 86 ++ .../mime4j/field/DefaultFieldParser.java | 166 +++- .../mime4j/field/DelegatingFieldParser.java | 65 +- src/org/apache/james/mime4j/field/Field.java | 192 ---- .../james/mime4j/field/FieldParser.java | 43 +- src/org/apache/james/mime4j/field/Fields.java | 643 +++++++++++++ .../james/mime4j/field/MailboxField.java | 68 -- .../james/mime4j/field/MailboxFieldImpl.java | 84 ++ ...stField.java => MailboxListFieldImpl.java} | 67 +- .../mime4j/field/UnstructuredFieldImpl.java | 62 ++ .../mime4j/field/address/AddressList.java | 140 --- .../james/mime4j/field/address/Builder.java | 244 ----- .../mime4j/field/address/DomainList.java | 76 -- .../james/mime4j/field/address/Group.java | 73 -- .../james/mime4j/field/address/Mailbox.java | 119 --- .../mime4j/field/address/NamedMailbox.java | 70 -- .../address/formatter/AddressFormatter.java | 209 +++++ .../field/address/parser/ASTaddr_spec.java | 8 +- .../field/address/parser/ASTaddress.java | 8 +- .../field/address/parser/ASTaddress_list.java | 8 +- .../field/address/parser/ASTangle_addr.java | 8 +- .../field/address/parser/ASTdomain.java | 8 +- .../field/address/parser/ASTgroup_body.java | 8 +- .../field/address/parser/ASTlocal_part.java | 8 +- .../field/address/parser/ASTmailbox.java | 8 +- .../field/address/parser/ASTname_addr.java | 8 +- .../field/address/parser/ASTphrase.java | 8 +- .../mime4j/field/address/parser/ASTroute.java | 8 +- .../field/address/parser/AddressBuilder.java | 115 +++ .../address/parser/AddressListParser.java | 184 ++-- .../field/address/parser/AddressListParser.jj | 595 ------------ .../parser/AddressListParserConstants.java | 55 +- .../parser/AddressListParserTokenManager.java | 296 +++--- .../AddressListParserTreeConstants.java | 4 +- .../parser/AddressListParserVisitor.java | 4 +- .../mime4j/field/address/parser/BaseNode.java | 2 - .../mime4j/field/address/parser/Builder.java | 229 +++++ .../parser/JJTAddressListParserState.java | 74 +- .../mime4j/field/address/parser/Node.java | 8 +- .../field/address/parser/ParseException.java | 51 +- .../address/parser/SimpleCharStream.java | 465 +++++----- .../field/address/parser/SimpleNode.java | 29 +- .../mime4j/field/address/parser/Token.java | 117 ++- .../field/address/parser/TokenMgrError.java | 277 +++--- .../parser/ContentDispositionParser.java | 292 ++++++ .../ContentDispositionParserConstants.java | 82 ++ .../ContentDispositionParserTokenManager.java | 857 +++++++++++++++++ .../parser/ParseException.java | 220 +++++ .../parser/SimpleCharStream.java | 489 ++++++++++ .../contentdisposition/parser/Token.java | 149 +++ .../parser/TokenMgrError.java | 165 ++++ .../contenttype/parser/ContentTypeParser.java | 86 +- .../parser/ContentTypeParserConstants.java | 51 +- .../parser/ContentTypeParserTokenManager.java | 272 +++--- .../contenttype/parser/ParseException.java | 51 +- .../contenttype/parser/SimpleCharStream.java | 465 +++++----- .../field/contenttype/parser/Token.java | 117 ++- .../contenttype/parser/TokenMgrError.java | 277 +++--- .../field/datetime/parser/DateTimeParser.java | 80 +- .../parser/DateTimeParserConstants.java | 50 +- .../parser/DateTimeParserTokenManager.java | 243 +++-- .../field/datetime/parser/ParseException.java | 51 +- .../datetime/parser/SimpleCharStream.java | 465 +++++----- .../mime4j/field/datetime/parser/Token.java | 117 ++- .../field/datetime/parser/TokenMgrError.java | 277 +++--- .../parser/ContentLanguageParser.java | 272 ++++++ .../ContentLanguageParserConstants.java | 86 ++ .../ContentLanguageParserTokenManager.java | 860 ++++++++++++++++++ .../field/language/parser/ParseException.java | 220 +++++ .../language/parser/SimpleCharStream.java | 489 ++++++++++ .../mime4j/field/language/parser/Token.java | 149 +++ .../field/language/parser/TokenMgrError.java | 165 ++++ .../mimeversion/parser/MimeVersionParser.java | 235 +++++ .../parser/MimeVersionParserConstants.java | 80 ++ .../parser/MimeVersionParserTokenManager.java | 824 +++++++++++++++++ .../mimeversion/parser/ParseException.java | 220 +++++ .../mimeversion/parser/SimpleCharStream.java | 489 ++++++++++ .../field/mimeversion/parser/Token.java | 149 +++ .../mimeversion/parser/TokenMgrError.java | 165 ++++ .../structured/parser/ParseException.java | 220 +++++ .../structured/parser/SimpleCharStream.java | 489 ++++++++++ .../parser/StructuredFieldParser.java | 289 ++++++ .../StructuredFieldParserConstants.java | 77 ++ .../StructuredFieldParserTokenManager.java | 819 +++++++++++++++++ .../mime4j/field/structured/parser/Token.java | 149 +++ .../structured/parser/TokenMgrError.java | 165 ++++ .../io/BufferedLineReaderInputStream.java | 389 ++++++++ .../{ => io}/EOLConvertingInputStream.java | 31 +- .../LimitedInputStream.java} | 42 +- .../mime4j/io/LineNumberInputStream.java | 67 ++ .../james/mime4j/io/LineNumberSource.java | 30 + .../mime4j/io/LineReaderInputStream.java | 64 ++ .../io/LineReaderInputStreamAdaptor.java | 148 +++ .../io/MaxHeaderLengthLimitException.java | 35 + .../mime4j/io/MaxHeaderLimitException.java | 35 + .../mime4j/io/MaxLineLimitException.java | 36 + .../mime4j/io/MimeBoundaryInputStream.java | 314 +++++++ .../{util => io}/PositionInputStream.java | 44 +- .../james/mime4j/message/BodyCopier.java | 74 ++ .../james/mime4j/message/BodyFactory.java | 323 +++++++ .../apache/james/mime4j/message/BodyPart.java | 95 +- .../apache/james/mime4j/message/Entity.java | 170 ---- .../james/mime4j/message/EntityBuilder.java | 234 +++++ .../apache/james/mime4j/message/Header.java | 155 ---- .../james/mime4j/message/HeaderImpl.java | 92 ++ .../mime4j/message/MaximalBodyDescriptor.java | 481 ++++++++++ ...java => MaximalBodyDescriptorFactory.java} | 26 +- .../mime4j/message/MemoryBinaryBody.java | 90 -- .../james/mime4j/message/MemoryTextBody.java | 116 --- .../apache/james/mime4j/message/Message.java | 256 ------ .../message/MessageBuilderFactoryImpl.java | 73 ++ .../mime4j/message/MessageBuilderImpl.java | 73 ++ .../james/mime4j/message/MessageImpl.java | 287 ++++++ .../james/mime4j/message/MessageWriter.java | 240 +++++ .../james/mime4j/message/Multipart.java | 203 ----- .../james/mime4j/message/MultipartImpl.java | 169 ++++ .../mime4j/message/SimpleContentHandler.java | 86 ++ .../mime4j/message/StorageBinaryBody.java | 76 ++ .../james/mime4j/message/StorageTextBody.java | 79 ++ .../james/mime4j/message/StringTextBody.java | 89 ++ .../mime4j/message/TempFileBinaryBody.java | 89 -- .../mime4j/message/TempFileTextBody.java | 115 --- .../mime4j/parser/AbstractContentHandler.java | 77 ++ .../mime4j/{ => parser}/ContentHandler.java | 127 +-- .../james/mime4j/parser/MimeStreamParser.java | 246 +++++ .../storage/AbstractStorageProvider.java | 61 ++ .../mime4j/storage/CipherStorageProvider.java | 177 ++++ .../storage/DefaultStorageProvider.java | 93 ++ .../mime4j/storage/MemoryStorageProvider.java | 87 ++ .../mime4j/storage/MultiReferenceStorage.java | 131 +++ .../apache/james/mime4j/storage/Storage.java | 53 ++ .../mime4j/storage/StorageOutputStream.java | 170 ++++ .../james/mime4j/storage/StorageProvider.java | 51 ++ .../storage/TempFileStorageProvider.java | 183 ++++ .../storage/ThresholdStorageProvider.java | 161 ++++ .../james/mime4j/stream/AbstractEntity.java | 337 +++++++ .../james/mime4j/stream/BodyDescriptor.java | 35 + .../mime4j/stream/ContentDescriptor.java | 88 ++ .../DefaultBodyDescriptor.java} | 440 ++++----- .../mime4j/stream/EntityStateMachine.java | 112 +++ .../james/mime4j/stream/EntityStates.java | 95 ++ src/org/apache/james/mime4j/stream/Event.java | 74 ++ .../james/mime4j/stream/MimeEntity.java | 291 ++++++ .../james/mime4j/stream/MimeEntityConfig.java | 265 ++++++ .../MimeParseEventException.java} | 45 +- .../james/mime4j/stream/MimeTokenStream.java | 385 ++++++++ .../mime4j/stream/MutableBodyDescriptor.java | 38 + .../stream/MutableBodyDescriptorFactory.java | 31 + .../apache/james/mime4j/stream/RawEntity.java | 98 ++ .../apache/james/mime4j/stream/RawField.java | 136 +++ .../RecursionMode.java} | 36 +- .../james/mime4j/util/ByteArrayBuffer.java | 181 ++++ .../james/mime4j/util/ByteSequence.java | 58 ++ .../apache/james/mime4j/util/CharsetUtil.java | 193 ++-- .../apache/james/mime4j/util/ContentUtil.java | 138 +++ .../james/mime4j/util/EmptyByteSequence.java | 36 + .../apache/james/mime4j/util/LangUtils.java | 69 ++ .../apache/james/mime4j/util/MimeUtil.java | 365 ++++++++ .../james/mime4j/util/SimpleTempStorage.java | 236 ----- .../apache/james/mime4j/util/TempFile.java | 84 -- .../apache/james/mime4j/util/TempPath.java | 73 -- .../apache/james/mime4j/util/TempStorage.java | 70 -- 225 files changed, 26744 insertions(+), 8494 deletions(-) delete mode 100644 src/org/apache/james/mime4j/AbstractContentHandler.java delete mode 100644 src/org/apache/james/mime4j/CloseShieldInputStream.java delete mode 100644 src/org/apache/james/mime4j/MimeBoundaryInputStream.java create mode 100644 src/org/apache/james/mime4j/MimeException.java create mode 100644 src/org/apache/james/mime4j/MimeIOException.java delete mode 100644 src/org/apache/james/mime4j/MimeStreamParser.java delete mode 100644 src/org/apache/james/mime4j/RootInputStream.java delete mode 100644 src/org/apache/james/mime4j/SimpleContentHandler.java create mode 100644 src/org/apache/james/mime4j/codec/Base64InputStream.java create mode 100644 src/org/apache/james/mime4j/codec/Base64OutputStream.java create mode 100644 src/org/apache/james/mime4j/codec/CodecUtil.java create mode 100644 src/org/apache/james/mime4j/codec/DecodeMonitor.java create mode 100644 src/org/apache/james/mime4j/codec/DecoderUtil.java delete mode 100644 src/org/apache/james/mime4j/codec/QuotedPrintableEncoder.java create mode 100644 src/org/apache/james/mime4j/codec/QuotedPrintableInputStream.java delete mode 100644 src/org/apache/james/mime4j/decoder/Base64InputStream.java delete mode 100644 src/org/apache/james/mime4j/decoder/DecoderUtil.java delete mode 100644 src/org/apache/james/mime4j/decoder/QuotedPrintableInputStream.java delete mode 100644 src/org/apache/james/mime4j/decoder/UnboundedFifoByteBuffer.java create mode 100644 src/org/apache/james/mime4j/dom/BinaryBody.java rename src/org/apache/james/mime4j/{message => dom}/Body.java (74%) create mode 100644 src/org/apache/james/mime4j/dom/Disposable.java create mode 100644 src/org/apache/james/mime4j/dom/Entity.java create mode 100644 src/org/apache/james/mime4j/dom/Header.java create mode 100644 src/org/apache/james/mime4j/dom/Message.java create mode 100644 src/org/apache/james/mime4j/dom/MessageBuilder.java create mode 100644 src/org/apache/james/mime4j/dom/MessageBuilderFactory.java create mode 100644 src/org/apache/james/mime4j/dom/Multipart.java create mode 100644 src/org/apache/james/mime4j/dom/ServiceLoader.java create mode 100644 src/org/apache/james/mime4j/dom/SingleBody.java rename src/org/apache/james/mime4j/{message => dom}/TextBody.java (76%) rename src/org/apache/james/mime4j/{field => dom}/address/Address.java (60%) create mode 100644 src/org/apache/james/mime4j/dom/address/AddressList.java create mode 100644 src/org/apache/james/mime4j/dom/address/DomainList.java create mode 100644 src/org/apache/james/mime4j/dom/address/Group.java create mode 100644 src/org/apache/james/mime4j/dom/address/Mailbox.java rename src/org/apache/james/mime4j/{field => dom}/address/MailboxList.java (53%) rename src/org/apache/james/mime4j/{field => dom}/datetime/DateTime.java (67%) create mode 100644 src/org/apache/james/mime4j/dom/field/AddressListField.java create mode 100644 src/org/apache/james/mime4j/dom/field/ContentDispositionField.java create mode 100644 src/org/apache/james/mime4j/dom/field/ContentTransferEncodingField.java create mode 100644 src/org/apache/james/mime4j/dom/field/ContentTypeField.java create mode 100644 src/org/apache/james/mime4j/dom/field/DateTimeField.java create mode 100644 src/org/apache/james/mime4j/dom/field/Field.java rename src/org/apache/james/mime4j/{decoder/ByteQueue.java => dom/field/FieldName.java} (50%) create mode 100644 src/org/apache/james/mime4j/dom/field/MailboxField.java create mode 100644 src/org/apache/james/mime4j/dom/field/MailboxListField.java create mode 100644 src/org/apache/james/mime4j/dom/field/ParseException.java create mode 100644 src/org/apache/james/mime4j/dom/field/ParsedField.java create mode 100644 src/org/apache/james/mime4j/dom/field/UnstructuredField.java create mode 100644 src/org/apache/james/mime4j/field/AbstractField.java rename src/org/apache/james/mime4j/field/{AddressListField.java => AddressListFieldImpl.java} (51%) create mode 100644 src/org/apache/james/mime4j/field/ContentDispositionFieldImpl.java rename src/org/apache/james/mime4j/field/{ContentTransferEncodingField.java => ContentTransferEncodingFieldImpl.java} (54%) delete mode 100644 src/org/apache/james/mime4j/field/ContentTypeField.java create mode 100644 src/org/apache/james/mime4j/field/ContentTypeFieldImpl.java delete mode 100644 src/org/apache/james/mime4j/field/DateTimeField.java create mode 100644 src/org/apache/james/mime4j/field/DateTimeFieldImpl.java delete mode 100644 src/org/apache/james/mime4j/field/Field.java create mode 100644 src/org/apache/james/mime4j/field/Fields.java delete mode 100644 src/org/apache/james/mime4j/field/MailboxField.java create mode 100644 src/org/apache/james/mime4j/field/MailboxFieldImpl.java rename src/org/apache/james/mime4j/field/{MailboxListField.java => MailboxListFieldImpl.java} (51%) create mode 100644 src/org/apache/james/mime4j/field/UnstructuredFieldImpl.java delete mode 100644 src/org/apache/james/mime4j/field/address/AddressList.java delete mode 100644 src/org/apache/james/mime4j/field/address/Builder.java delete mode 100644 src/org/apache/james/mime4j/field/address/DomainList.java delete mode 100644 src/org/apache/james/mime4j/field/address/Group.java delete mode 100644 src/org/apache/james/mime4j/field/address/Mailbox.java delete mode 100644 src/org/apache/james/mime4j/field/address/NamedMailbox.java create mode 100644 src/org/apache/james/mime4j/field/address/formatter/AddressFormatter.java create mode 100644 src/org/apache/james/mime4j/field/address/parser/AddressBuilder.java delete mode 100644 src/org/apache/james/mime4j/field/address/parser/AddressListParser.jj create mode 100644 src/org/apache/james/mime4j/field/address/parser/Builder.java create mode 100644 src/org/apache/james/mime4j/field/contentdisposition/parser/ContentDispositionParser.java create mode 100644 src/org/apache/james/mime4j/field/contentdisposition/parser/ContentDispositionParserConstants.java create mode 100644 src/org/apache/james/mime4j/field/contentdisposition/parser/ContentDispositionParserTokenManager.java create mode 100644 src/org/apache/james/mime4j/field/contentdisposition/parser/ParseException.java create mode 100644 src/org/apache/james/mime4j/field/contentdisposition/parser/SimpleCharStream.java create mode 100644 src/org/apache/james/mime4j/field/contentdisposition/parser/Token.java create mode 100644 src/org/apache/james/mime4j/field/contentdisposition/parser/TokenMgrError.java create mode 100644 src/org/apache/james/mime4j/field/language/parser/ContentLanguageParser.java create mode 100644 src/org/apache/james/mime4j/field/language/parser/ContentLanguageParserConstants.java create mode 100644 src/org/apache/james/mime4j/field/language/parser/ContentLanguageParserTokenManager.java create mode 100644 src/org/apache/james/mime4j/field/language/parser/ParseException.java create mode 100644 src/org/apache/james/mime4j/field/language/parser/SimpleCharStream.java create mode 100644 src/org/apache/james/mime4j/field/language/parser/Token.java create mode 100644 src/org/apache/james/mime4j/field/language/parser/TokenMgrError.java create mode 100644 src/org/apache/james/mime4j/field/mimeversion/parser/MimeVersionParser.java create mode 100644 src/org/apache/james/mime4j/field/mimeversion/parser/MimeVersionParserConstants.java create mode 100644 src/org/apache/james/mime4j/field/mimeversion/parser/MimeVersionParserTokenManager.java create mode 100644 src/org/apache/james/mime4j/field/mimeversion/parser/ParseException.java create mode 100644 src/org/apache/james/mime4j/field/mimeversion/parser/SimpleCharStream.java create mode 100644 src/org/apache/james/mime4j/field/mimeversion/parser/Token.java create mode 100644 src/org/apache/james/mime4j/field/mimeversion/parser/TokenMgrError.java create mode 100644 src/org/apache/james/mime4j/field/structured/parser/ParseException.java create mode 100644 src/org/apache/james/mime4j/field/structured/parser/SimpleCharStream.java create mode 100644 src/org/apache/james/mime4j/field/structured/parser/StructuredFieldParser.java create mode 100644 src/org/apache/james/mime4j/field/structured/parser/StructuredFieldParserConstants.java create mode 100644 src/org/apache/james/mime4j/field/structured/parser/StructuredFieldParserTokenManager.java create mode 100644 src/org/apache/james/mime4j/field/structured/parser/Token.java create mode 100644 src/org/apache/james/mime4j/field/structured/parser/TokenMgrError.java create mode 100644 src/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java rename src/org/apache/james/mime4j/{ => io}/EOLConvertingInputStream.java (94%) rename src/org/apache/james/mime4j/{util/PartialInputStream.java => io/LimitedInputStream.java} (66%) create mode 100644 src/org/apache/james/mime4j/io/LineNumberInputStream.java create mode 100644 src/org/apache/james/mime4j/io/LineNumberSource.java create mode 100644 src/org/apache/james/mime4j/io/LineReaderInputStream.java create mode 100644 src/org/apache/james/mime4j/io/LineReaderInputStreamAdaptor.java create mode 100644 src/org/apache/james/mime4j/io/MaxHeaderLengthLimitException.java create mode 100644 src/org/apache/james/mime4j/io/MaxHeaderLimitException.java create mode 100644 src/org/apache/james/mime4j/io/MaxLineLimitException.java create mode 100644 src/org/apache/james/mime4j/io/MimeBoundaryInputStream.java rename src/org/apache/james/mime4j/{util => io}/PositionInputStream.java (75%) create mode 100644 src/org/apache/james/mime4j/message/BodyCopier.java create mode 100644 src/org/apache/james/mime4j/message/BodyFactory.java delete mode 100644 src/org/apache/james/mime4j/message/Entity.java create mode 100644 src/org/apache/james/mime4j/message/EntityBuilder.java delete mode 100644 src/org/apache/james/mime4j/message/Header.java create mode 100644 src/org/apache/james/mime4j/message/HeaderImpl.java create mode 100644 src/org/apache/james/mime4j/message/MaximalBodyDescriptor.java rename src/org/apache/james/mime4j/message/{BinaryBody.java => MaximalBodyDescriptorFactory.java} (72%) delete mode 100644 src/org/apache/james/mime4j/message/MemoryBinaryBody.java delete mode 100644 src/org/apache/james/mime4j/message/MemoryTextBody.java delete mode 100644 src/org/apache/james/mime4j/message/Message.java create mode 100644 src/org/apache/james/mime4j/message/MessageBuilderFactoryImpl.java create mode 100644 src/org/apache/james/mime4j/message/MessageBuilderImpl.java create mode 100644 src/org/apache/james/mime4j/message/MessageImpl.java create mode 100644 src/org/apache/james/mime4j/message/MessageWriter.java delete mode 100644 src/org/apache/james/mime4j/message/Multipart.java create mode 100644 src/org/apache/james/mime4j/message/MultipartImpl.java create mode 100644 src/org/apache/james/mime4j/message/SimpleContentHandler.java create mode 100644 src/org/apache/james/mime4j/message/StorageBinaryBody.java create mode 100644 src/org/apache/james/mime4j/message/StorageTextBody.java create mode 100644 src/org/apache/james/mime4j/message/StringTextBody.java delete mode 100644 src/org/apache/james/mime4j/message/TempFileBinaryBody.java delete mode 100644 src/org/apache/james/mime4j/message/TempFileTextBody.java create mode 100644 src/org/apache/james/mime4j/parser/AbstractContentHandler.java rename src/org/apache/james/mime4j/{ => parser}/ContentHandler.java (68%) create mode 100644 src/org/apache/james/mime4j/parser/MimeStreamParser.java create mode 100644 src/org/apache/james/mime4j/storage/AbstractStorageProvider.java create mode 100644 src/org/apache/james/mime4j/storage/CipherStorageProvider.java create mode 100644 src/org/apache/james/mime4j/storage/DefaultStorageProvider.java create mode 100644 src/org/apache/james/mime4j/storage/MemoryStorageProvider.java create mode 100644 src/org/apache/james/mime4j/storage/MultiReferenceStorage.java create mode 100644 src/org/apache/james/mime4j/storage/Storage.java create mode 100644 src/org/apache/james/mime4j/storage/StorageOutputStream.java create mode 100644 src/org/apache/james/mime4j/storage/StorageProvider.java create mode 100644 src/org/apache/james/mime4j/storage/TempFileStorageProvider.java create mode 100644 src/org/apache/james/mime4j/storage/ThresholdStorageProvider.java create mode 100644 src/org/apache/james/mime4j/stream/AbstractEntity.java create mode 100644 src/org/apache/james/mime4j/stream/BodyDescriptor.java create mode 100644 src/org/apache/james/mime4j/stream/ContentDescriptor.java rename src/org/apache/james/mime4j/{BodyDescriptor.java => stream/DefaultBodyDescriptor.java} (56%) create mode 100644 src/org/apache/james/mime4j/stream/EntityStateMachine.java create mode 100644 src/org/apache/james/mime4j/stream/EntityStates.java create mode 100644 src/org/apache/james/mime4j/stream/Event.java create mode 100644 src/org/apache/james/mime4j/stream/MimeEntity.java create mode 100644 src/org/apache/james/mime4j/stream/MimeEntityConfig.java rename src/org/apache/james/mime4j/{field/UnstructuredField.java => stream/MimeParseEventException.java} (60%) create mode 100644 src/org/apache/james/mime4j/stream/MimeTokenStream.java create mode 100644 src/org/apache/james/mime4j/stream/MutableBodyDescriptor.java create mode 100644 src/org/apache/james/mime4j/stream/MutableBodyDescriptorFactory.java create mode 100644 src/org/apache/james/mime4j/stream/RawEntity.java create mode 100644 src/org/apache/james/mime4j/stream/RawField.java rename src/org/apache/james/mime4j/{message/AbstractBody.java => stream/RecursionMode.java} (66%) create mode 100644 src/org/apache/james/mime4j/util/ByteArrayBuffer.java create mode 100644 src/org/apache/james/mime4j/util/ByteSequence.java create mode 100644 src/org/apache/james/mime4j/util/ContentUtil.java create mode 100644 src/org/apache/james/mime4j/util/EmptyByteSequence.java create mode 100644 src/org/apache/james/mime4j/util/LangUtils.java create mode 100644 src/org/apache/james/mime4j/util/MimeUtil.java delete mode 100644 src/org/apache/james/mime4j/util/SimpleTempStorage.java delete mode 100644 src/org/apache/james/mime4j/util/TempFile.java delete mode 100644 src/org/apache/james/mime4j/util/TempPath.java delete mode 100644 src/org/apache/james/mime4j/util/TempStorage.java diff --git a/src/com/fsck/k9/mail/Address.java b/src/com/fsck/k9/mail/Address.java index 9af0f7c6a..b40861e49 100644 --- a/src/com/fsck/k9/mail/Address.java +++ b/src/com/fsck/k9/mail/Address.java @@ -13,11 +13,12 @@ import com.fsck.k9.K9; import com.fsck.k9.helper.Contacts; import com.fsck.k9.helper.Utility; import org.apache.james.mime4j.codec.EncoderUtil; -import org.apache.james.mime4j.field.address.AddressList; -import org.apache.james.mime4j.field.address.Mailbox; -import org.apache.james.mime4j.field.address.MailboxList; -import org.apache.james.mime4j.field.address.NamedMailbox; -import org.apache.james.mime4j.field.address.parser.ParseException; +import org.apache.james.mime4j.dom.address.AddressList; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.address.MailboxList; +import org.apache.james.mime4j.field.DefaultFieldParser; +import org.apache.james.mime4j.field.address.parser.AddressBuilder; +import org.apache.james.mime4j.MimeException; import java.util.ArrayList; import java.util.List; @@ -137,21 +138,21 @@ public class Address } try { - MailboxList parsedList = AddressList.parse(addressList).flatten(); + MailboxList parsedList = (MailboxList) AddressBuilder.parseAddressList(addressList).flatten(); for (int i = 0, count = parsedList.size(); i < count; i++) { - org.apache.james.mime4j.field.address.Address address = parsedList.get(i); - if (address instanceof NamedMailbox) - { - NamedMailbox namedMailbox = (NamedMailbox)address; - addresses.add(new Address(namedMailbox.getLocalPart() + "@" - + namedMailbox.getDomain(), namedMailbox.getName())); - } - else if (address instanceof Mailbox) + org.apache.james.mime4j.dom.address.Address address = parsedList.get(i); + if (address instanceof Mailbox) { Mailbox mailbox = (Mailbox)address; + if (mailbox.getName() != null ) + { + addresses.add(new Address(mailbox.getLocalPart() + "@" + mailbox.getDomain(), mailbox.getName())); + } else + { addresses.add(new Address(mailbox.getLocalPart() + "@" + mailbox.getDomain())); } + } else { Log.e(K9.LOG_TAG, "Unknown address type from Mime4J: " @@ -160,7 +161,7 @@ public class Address } } - catch (ParseException pe) + catch (MimeException pe) { } return addresses.toArray(EMPTY_ADDRESS_ARRAY); diff --git a/src/com/fsck/k9/mail/internet/DecoderUtil.java b/src/com/fsck/k9/mail/internet/DecoderUtil.java index 22f0011dd..e1690f6f1 100644 --- a/src/com/fsck/k9/mail/internet/DecoderUtil.java +++ b/src/com/fsck/k9/mail/internet/DecoderUtil.java @@ -8,8 +8,8 @@ import com.fsck.k9.mail.MessagingException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; -import org.apache.james.mime4j.decoder.Base64InputStream; -import org.apache.james.mime4j.decoder.QuotedPrintableInputStream; +import org.apache.james.mime4j.codec.Base64InputStream; +import org.apache.james.mime4j.codec.QuotedPrintableInputStream; import org.apache.james.mime4j.util.CharsetUtil; diff --git a/src/com/fsck/k9/mail/internet/MimeMessage.java b/src/com/fsck/k9/mail/internet/MimeMessage.java index 20b4a4dc7..ddabf38cc 100644 --- a/src/com/fsck/k9/mail/internet/MimeMessage.java +++ b/src/com/fsck/k9/mail/internet/MimeMessage.java @@ -4,12 +4,16 @@ package com.fsck.k9.mail.internet; import com.fsck.k9.mail.*; import com.fsck.k9.mail.store.UnavailableStorageException; -import org.apache.james.mime4j.BodyDescriptor; -import org.apache.james.mime4j.ContentHandler; -import org.apache.james.mime4j.EOLConvertingInputStream; -import org.apache.james.mime4j.MimeStreamParser; -import org.apache.james.mime4j.field.DateTimeField; -import org.apache.james.mime4j.field.Field; +import org.apache.james.mime4j.stream.BodyDescriptor; +import org.apache.james.mime4j.stream.RawField; +import org.apache.james.mime4j.parser.ContentHandler; +import org.apache.james.mime4j.io.EOLConvertingInputStream; +import org.apache.james.mime4j.parser.MimeStreamParser; +import org.apache.james.mime4j.dom.field.DateTimeField; +import org.apache.james.mime4j.dom.field.Field; +import org.apache.james.mime4j.field.DefaultFieldParser; + +import org.apache.james.mime4j.MimeException; import java.io.*; import java.text.SimpleDateFormat; @@ -74,7 +78,12 @@ public class MimeMessage extends Message MimeStreamParser parser = new MimeStreamParser(); parser.setContentHandler(new MimeMessageBuilder()); - parser.parse(new EOLConvertingInputStream(in)); + try { + parser.parse(new EOLConvertingInputStream(in)); + } catch (MimeException me) { + throw new Error(me); + + } } @Override @@ -84,7 +93,7 @@ public class MimeMessage extends Message { try { - DateTimeField field = (DateTimeField)Field.parse("Date: " + DateTimeField field = (DateTimeField)DefaultFieldParser.parse("Date: " + MimeUtility.unfoldAndDecode(getFirstHeader("Date"))); mSentDate = field.getDate(); } @@ -565,6 +574,18 @@ public class MimeMessage extends Message expect(Part.class); } + public void field(RawField field) + { + try { + Field parsedField = DefaultFieldParser.parse(field.getRaw(), null); + ((Part)stack.peek()).addHeader(parsedField.getName(), field.getBody().trim()); + } catch (MessagingException me) { + throw new Error(me); + } catch (MimeException me) { + throw new Error(me); + } + } + public void field(String fieldData) { expect(Part.class); diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 72eca8f9c..1c39da32c 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -5,8 +5,8 @@ import android.util.Log; import com.fsck.k9.K9; import com.fsck.k9.mail.*; import org.apache.commons.io.IOUtils; -import org.apache.james.mime4j.decoder.Base64InputStream; -import org.apache.james.mime4j.decoder.QuotedPrintableInputStream; +import org.apache.james.mime4j.codec.Base64InputStream; +import org.apache.james.mime4j.codec.QuotedPrintableInputStream; import java.io.IOException; import java.io.InputStream; diff --git a/src/org/apache/james/mime4j/AbstractContentHandler.java b/src/org/apache/james/mime4j/AbstractContentHandler.java deleted file mode 100644 index 3df6fb76b..000000000 --- a/src/org/apache/james/mime4j/AbstractContentHandler.java +++ /dev/null @@ -1,113 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Abstract ContentHandler with default implementations of all - * the methods of the ContentHandler interface. - * - * The default is to todo nothing. - * - * - * @version $Id: AbstractContentHandler.java,v 1.3 2004/10/02 12:41:10 ntherning Exp $ - */ -public abstract class AbstractContentHandler implements ContentHandler { - - /** - * @see org.apache.james.mime4j.ContentHandler#endMultipart() - */ - public void endMultipart() { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#startMultipart(org.apache.james.mime4j.BodyDescriptor) - */ - public void startMultipart(BodyDescriptor bd) { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#body(org.apache.james.mime4j.BodyDescriptor, java.io.InputStream) - */ - public void body(BodyDescriptor bd, InputStream is) throws IOException { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#endBodyPart() - */ - public void endBodyPart() { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#endHeader() - */ - public void endHeader() { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#endMessage() - */ - public void endMessage() { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#epilogue(java.io.InputStream) - */ - public void epilogue(InputStream is) throws IOException { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#field(java.lang.String) - */ - public void field(String fieldData) { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#preamble(java.io.InputStream) - */ - public void preamble(InputStream is) throws IOException { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#startBodyPart() - */ - public void startBodyPart() { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#startHeader() - */ - public void startHeader() { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#startMessage() - */ - public void startMessage() { - } - - /** - * @see org.apache.james.mime4j.ContentHandler#raw(java.io.InputStream) - */ - public void raw(InputStream is) throws IOException { - } -} diff --git a/src/org/apache/james/mime4j/CloseShieldInputStream.java b/src/org/apache/james/mime4j/CloseShieldInputStream.java deleted file mode 100644 index 3acc98f7c..000000000 --- a/src/org/apache/james/mime4j/CloseShieldInputStream.java +++ /dev/null @@ -1,129 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j; - -import java.io.InputStream; -import java.io.IOException; - -/** - * InputStream that shields its underlying input stream from - * being closed. - * - * - * @version $Id: CloseShieldInputStream.java,v 1.2 2004/10/02 12:41:10 ntherning Exp $ - */ -public class CloseShieldInputStream extends InputStream { - - /** - * Underlying InputStream - */ - private InputStream is; - - public CloseShieldInputStream(InputStream is) { - this.is = is; - } - - public InputStream getUnderlyingStream() { - return is; - } - - /** - * @see java.io.InputStream#read() - */ - public int read() throws IOException { - checkIfClosed(); - return is.read(); - } - - /** - * @see java.io.InputStream#available() - */ - public int available() throws IOException { - checkIfClosed(); - return is.available(); - } - - - /** - * Set the underlying InputStream to null - */ - public void close() throws IOException { - is = null; - } - - /** - * @see java.io.FilterInputStream#reset() - */ - public synchronized void reset() throws IOException { - checkIfClosed(); - is.reset(); - } - - /** - * @see java.io.FilterInputStream#markSupported() - */ - public boolean markSupported() { - if (is == null) - return false; - return is.markSupported(); - } - - /** - * @see java.io.FilterInputStream#mark(int) - */ - public synchronized void mark(int readlimit) { - if (is != null) - is.mark(readlimit); - } - - /** - * @see java.io.FilterInputStream#skip(long) - */ - public long skip(long n) throws IOException { - checkIfClosed(); - return is.skip(n); - } - - /** - * @see java.io.FilterInputStream#read(byte[]) - */ - public int read(byte b[]) throws IOException { - checkIfClosed(); - return is.read(b); - } - - /** - * @see java.io.FilterInputStream#read(byte[], int, int) - */ - public int read(byte b[], int off, int len) throws IOException { - checkIfClosed(); - return is.read(b, off, len); - } - - /** - * Check if the underlying InputStream is null. If so throw an Exception - * - * @throws IOException if the underlying InputStream is null - */ - private void checkIfClosed() throws IOException { - if (is == null) - throw new IOException("Stream is closed"); - } -} diff --git a/src/org/apache/james/mime4j/MimeBoundaryInputStream.java b/src/org/apache/james/mime4j/MimeBoundaryInputStream.java deleted file mode 100644 index 75d1bf7fc..000000000 --- a/src/org/apache/james/mime4j/MimeBoundaryInputStream.java +++ /dev/null @@ -1,184 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PushbackInputStream; - -/** - * Stream that constrains itself to a single MIME body part. - * After the stream ends (i.e. read() returns -1) {@link #hasMoreParts()} - * can be used to determine if a final boundary has been seen or not. - * If {@link #parentEOF()} is true an unexpected end of stream - * has been detected in the parent stream. - * - * - * - * @version $Id: MimeBoundaryInputStream.java,v 1.2 2004/11/29 13:15:42 ntherning Exp $ - */ -public class MimeBoundaryInputStream extends InputStream { - - private PushbackInputStream s = null; - private byte[] boundary = null; - private boolean first = true; - private boolean eof = false; - private boolean parenteof = false; - private boolean moreParts = true; - - /** - * Creates a new MimeBoundaryInputStream. - * @param s The underlying stream. - * @param boundary Boundary string (not including leading hyphens). - */ - public MimeBoundaryInputStream(InputStream s, String boundary) - throws IOException { - - this.s = new PushbackInputStream(s, boundary.length() + 4); - - boundary = "--" + boundary; - this.boundary = new byte[boundary.length()]; - for (int i = 0; i < this.boundary.length; i++) { - this.boundary[i] = (byte) boundary.charAt(i); - } - - /* - * By reading one byte we will update moreParts to be as expected - * before any bytes have been read. - */ - int b = read(); - if (b != -1) { - this.s.unread(b); - } - } - - /** - * Closes the underlying stream. - * - * @throws IOException on I/O errors. - */ - public void close() throws IOException { - s.close(); - } - - /** - * Determines if the underlying stream has more parts (this stream has - * not seen an end boundary). - * - * @return true if there are more parts in the underlying - * stream, false otherwise. - */ - public boolean hasMoreParts() { - return moreParts; - } - - /** - * Determines if the parent stream has reached EOF - * - * @return true if EOF has been reached for the parent stream, - * false otherwise. - */ - public boolean parentEOF() { - return parenteof; - } - - /** - * Consumes all unread bytes of this stream. After a call to this method - * this stream will have reached EOF. - * - * @throws IOException on I/O errors. - */ - public void consume() throws IOException { - while (read() != -1) { - } - } - - /** - * @see java.io.InputStream#read() - */ - public int read() throws IOException { - if (eof) { - return -1; - } - - if (first) { - first = false; - if (matchBoundary()) { - return -1; - } - } - - int b1 = s.read(); - int b2 = s.read(); - - if (b1 == '\r' && b2 == '\n') { - if (matchBoundary()) { - return -1; - } - } - - if (b2 != -1) { - s.unread(b2); - } - - parenteof = b1 == -1; - eof = parenteof; - - return b1; - } - - private boolean matchBoundary() throws IOException { - - for (int i = 0; i < boundary.length; i++) { - int b = s.read(); - if (b != boundary[i]) { - if (b != -1) { - s.unread(b); - } - for (int j = i - 1; j >= 0; j--) { - s.unread(boundary[j]); - } - return false; - } - } - - /* - * We have a match. Is it an end boundary? - */ - int prev = s.read(); - int curr = s.read(); - moreParts = !(prev == '-' && curr == '-'); - do { - if (curr == '\n' && prev == '\r') { - break; - } - prev = curr; - } while ((curr = s.read()) != -1); - - if (curr == -1) { - moreParts = false; - parenteof = true; - } - - eof = true; - - return true; - } -} diff --git a/src/org/apache/james/mime4j/MimeException.java b/src/org/apache/james/mime4j/MimeException.java new file mode 100644 index 000000000..3cd928f02 --- /dev/null +++ b/src/org/apache/james/mime4j/MimeException.java @@ -0,0 +1,65 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j; + +/** + * MIME processing exception. + *

+ * A MimeException may be thrown by a {@link org.apache.james.mime4j.parser.ContentHandler} to + * indicate that it has failed to process a message event and that no further + * events should be generated. + *

+ * MimeException also gets thrown by the parser to indicate MIME + * protocol errors, e.g. if a message boundary is too long or a header field + * cannot be parsed. + */ +public class MimeException extends Exception { + + private static final long serialVersionUID = 8352821278714188542L; + + /** + * Constructs a new MIME exception with the specified detail message. + * + * @param message detail message + */ + public MimeException(String message) { + super(message); + } + + /** + * Constructs a MIME exception with the specified cause. + * + * @param cause cause of the exception + */ + public MimeException(Throwable cause) { + super(cause); + } + + /** + * Constructs a MIME exception with the specified detail message and cause. + * + * @param message detail message + * @param cause cause of the exception + */ + public MimeException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/org/apache/james/mime4j/MimeIOException.java b/src/org/apache/james/mime4j/MimeIOException.java new file mode 100644 index 000000000..5d524fc33 --- /dev/null +++ b/src/org/apache/james/mime4j/MimeIOException.java @@ -0,0 +1,58 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j; + +import java.io.IOException; + +/** + * A wrapper class based on {@link IOException} for MIME protocol exceptions. + *

+ * This exception is used to signal a MimeException in methods + * that only permit IOException to be thrown. + *

+ * The cause of a MimeIOException is always a + * MimeException therefore. + */ +public class MimeIOException extends IOException { + + private static final long serialVersionUID = 5393613459533735409L; + + /** + * Constructs an IO exception based on {@link MimeException}. + * + * @param cause the cause. + */ + public MimeIOException(MimeException cause) { + super(cause == null ? null : cause.getMessage()); + initCause(cause); + } + + /** + * Returns the MimeException that caused this + * MimeIOException. + * + * @return the cause of this MimeIOException. + */ + @Override + public MimeException getCause() { + return (MimeException) super.getCause(); + } + +} diff --git a/src/org/apache/james/mime4j/MimeStreamParser.java b/src/org/apache/james/mime4j/MimeStreamParser.java deleted file mode 100644 index 5f2d09914..000000000 --- a/src/org/apache/james/mime4j/MimeStreamParser.java +++ /dev/null @@ -1,320 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j; - -import java.io.IOException; -import java.io.InputStream; -import java.util.BitSet; -import java.util.LinkedList; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.james.mime4j.decoder.Base64InputStream; -import org.apache.james.mime4j.decoder.QuotedPrintableInputStream; - -/** - *

- * Parses MIME (or RFC822) message streams of bytes or characters and reports - * parsing events to a ContentHandler instance. - *

- *

- * Typical usage:
- *

- *      ContentHandler handler = new MyHandler();
- *      MimeStreamParser parser = new MimeStreamParser();
- *      parser.setContentHandler(handler);
- *      parser.parse(new BufferedInputStream(new FileInputStream("mime.msg")));
- * 
- * NOTE: All lines must end with CRLF - * (\r\n). If you are unsure of the line endings in your stream - * you should wrap it in a {@link org.apache.james.mime4j.EOLConvertingInputStream} instance. - * - * - * @version $Id: MimeStreamParser.java,v 1.8 2005/02/11 10:12:02 ntherning Exp $ - */ -public class MimeStreamParser { - private static final Log log = LogFactory.getLog(MimeStreamParser.class); - - private static BitSet fieldChars = null; - - private RootInputStream rootStream = null; - private LinkedList bodyDescriptors = new LinkedList(); - private ContentHandler handler = null; - private boolean raw = false; - - static { - fieldChars = new BitSet(); - for (int i = 0x21; i <= 0x39; i++) { - fieldChars.set(i); - } - for (int i = 0x3b; i <= 0x7e; i++) { - fieldChars.set(i); - } - } - - /** - * Creates a new MimeStreamParser instance. - */ - public MimeStreamParser() { - } - - /** - * Parses a stream of bytes containing a MIME message. - * - * @param is the stream to parse. - * @throws IOException on I/O errors. - */ - public void parse(InputStream is) throws IOException { - rootStream = new RootInputStream(is); - parseMessage(rootStream); - } - - /** - * Determines if this parser is currently in raw mode. - * - * @return true if in raw mode, false - * otherwise. - * @see #setRaw(boolean) - */ - public boolean isRaw() { - return raw; - } - - /** - * Enables or disables raw mode. In raw mode all future entities - * (messages or body parts) in the stream will be reported to the - * {@link ContentHandler#raw(InputStream)} handler method only. - * The stream will contain the entire unparsed entity contents - * including header fields and whatever is in the body. - * - * @param raw true enables raw mode, false - * disables it. - */ - public void setRaw(boolean raw) { - this.raw = raw; - } - - /** - * Finishes the parsing and stops reading lines. - * NOTE: No more lines will be parsed but the parser - * will still call - * {@link ContentHandler#endMultipart()}, - * {@link ContentHandler#endBodyPart()}, - * {@link ContentHandler#endMessage()}, etc to match previous calls - * to - * {@link ContentHandler#startMultipart(BodyDescriptor)}, - * {@link ContentHandler#startBodyPart()}, - * {@link ContentHandler#startMessage()}, etc. - */ - public void stop() { - rootStream.truncate(); - } - - /** - * Parses an entity which consists of a header followed by a body containing - * arbitrary data, body parts or an embedded message. - * - * @param is the stream to parse. - * @throws IOException on I/O errors. - */ - private void parseEntity(InputStream is) throws IOException { - BodyDescriptor bd = parseHeader(is); - - if (bd.isMultipart()) { - bodyDescriptors.addFirst(bd); - - handler.startMultipart(bd); - - MimeBoundaryInputStream tempIs = - new MimeBoundaryInputStream(is, bd.getBoundary()); - handler.preamble(new CloseShieldInputStream(tempIs)); - tempIs.consume(); - - while (tempIs.hasMoreParts()) { - tempIs = new MimeBoundaryInputStream(is, bd.getBoundary()); - parseBodyPart(tempIs); - tempIs.consume(); - if (tempIs.parentEOF()) { - if (log.isWarnEnabled()) { - log.warn("Line " + rootStream.getLineNumber() - + ": Body part ended prematurely. " - + "Higher level boundary detected or " - + "EOF reached."); - } - break; - } - } - - handler.epilogue(new CloseShieldInputStream(is)); - - handler.endMultipart(); - - bodyDescriptors.removeFirst(); - - } else if (bd.isMessage()) { - if (bd.isBase64Encoded()) { - log.warn("base64 encoded message/rfc822 detected"); - is = new EOLConvertingInputStream( - new Base64InputStream(is)); - } else if (bd.isQuotedPrintableEncoded()) { - log.warn("quoted-printable encoded message/rfc822 detected"); - is = new EOLConvertingInputStream( - new QuotedPrintableInputStream(is)); - } - bodyDescriptors.addFirst(bd); - parseMessage(is); - bodyDescriptors.removeFirst(); - } else { - handler.body(bd, new CloseShieldInputStream(is)); - } - - /* - * Make sure the stream has been consumed. - */ - while (is.read() != -1) { - } - } - - private void parseMessage(InputStream is) throws IOException { - if (raw) { - handler.raw(new CloseShieldInputStream(is)); - } else { - handler.startMessage(); - parseEntity(is); - handler.endMessage(); - } - } - - private void parseBodyPart(InputStream is) throws IOException { - if (raw) { - handler.raw(new CloseShieldInputStream(is)); - } else { - handler.startBodyPart(); - parseEntity(is); - handler.endBodyPart(); - } - } - - /** - * Parses a header. - * - * @param is the stream to parse. - * @return a BodyDescriptor describing the body following - * the header. - */ - private BodyDescriptor parseHeader(InputStream is) throws IOException { - BodyDescriptor bd = new BodyDescriptor(bodyDescriptors.isEmpty() - ? null : (BodyDescriptor) bodyDescriptors.getFirst()); - - handler.startHeader(); - - int lineNumber = rootStream.getLineNumber(); - - StringBuffer sb = new StringBuffer(); - int curr = 0; - int prev = 0; - while ((curr = is.read()) != -1) { - if (curr == '\n' && (prev == '\n' || prev == 0)) { - /* - * [\r]\n[\r]\n or an immediate \r\n have been seen. - */ - sb.deleteCharAt(sb.length() - 1); - break; - } - sb.append((char) curr); - prev = curr == '\r' ? prev : curr; - } - - if (curr == -1 && log.isWarnEnabled()) { - log.warn("Line " + rootStream.getLineNumber() - + ": Unexpected end of headers detected. " - + "Boundary detected in header or EOF reached."); - } - - int start = 0; - int pos = 0; - int startLineNumber = lineNumber; - while (pos < sb.length()) { - while (pos < sb.length() && sb.charAt(pos) != '\r') { - pos++; - } - if (pos < sb.length() - 1 && sb.charAt(pos + 1) != '\n') { - pos++; - continue; - } - - if (pos >= sb.length() - 2 || fieldChars.get(sb.charAt(pos + 2))) { - - /* - * field should be the complete field data excluding the - * trailing \r\n. - */ - String field = sb.substring(start, pos); - start = pos + 2; - - /* - * Check for a valid field. - */ - int index = field.indexOf(':'); - boolean valid = false; - if (index != -1 && fieldChars.get(field.charAt(0))) { - valid = true; - String fieldName = field.substring(0, index).trim(); - for (int i = 0; i < fieldName.length(); i++) { - if (!fieldChars.get(fieldName.charAt(i))) { - valid = false; - break; - } - } - - if (valid) { - handler.field(field); - bd.addField(fieldName, field.substring(index + 1)); - } - } - - if (!valid && log.isWarnEnabled()) { - log.warn("Line " + startLineNumber - + ": Ignoring invalid field: '" + field.trim() + "'"); - } - - startLineNumber = lineNumber; - } - - pos += 2; - lineNumber++; - } - - handler.endHeader(); - - return bd; - } - - /** - * Sets the ContentHandler to use when reporting - * parsing events. - * - * @param h the ContentHandler. - */ - public void setContentHandler(ContentHandler h) { - this.handler = h; - } - -} diff --git a/src/org/apache/james/mime4j/RootInputStream.java b/src/org/apache/james/mime4j/RootInputStream.java deleted file mode 100644 index ebf36dce2..000000000 --- a/src/org/apache/james/mime4j/RootInputStream.java +++ /dev/null @@ -1,111 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j; - -import java.io.IOException; -import java.io.InputStream; - -/** - * InputStream used by the parser to wrap the original user - * supplied stream. This stream keeps track of the current line number and - * can also be truncated. When truncated the stream will appear to have - * reached end of file. This is used by the parser's - * {@link org.apache.james.mime4j.MimeStreamParser#stop()} method. - * - * - * @version $Id: RootInputStream.java,v 1.2 2004/10/02 12:41:10 ntherning Exp $ - */ -class RootInputStream extends InputStream { - private InputStream is = null; - private int lineNumber = 1; - private int prev = -1; - private boolean truncated = false; - - /** - * Creates a new RootInputStream. - * - * @param in the stream to read from. - */ - public RootInputStream(InputStream is) { - this.is = is; - } - - /** - * Gets the current line number starting at 1 - * (the number of \r\n read so far plus 1). - * - * @return the current line number. - */ - public int getLineNumber() { - return lineNumber; - } - - /** - * Truncates this InputStream. After this call any - * call to {@link #read()}, {@link #read(byte[]) or - * {@link #read(byte[], int, int)} will return - * -1 as if end-of-file had been reached. - */ - public void truncate() { - this.truncated = true; - } - - /** - * @see java.io.InputStream#read() - */ - public int read() throws IOException { - if (truncated) { - return -1; - } - - int b = is.read(); - if (prev == '\r' && b == '\n') { - lineNumber++; - } - prev = b; - return b; - } - - /** - * - * @see java.io.InputStream#read(byte[], int, int) - */ - public int read(byte[] b, int off, int len) throws IOException { - if (truncated) { - return -1; - } - - int n = is.read(b, off, len); - for (int i = off; i < off + n; i++) { - if (prev == '\r' && b[i] == '\n') { - lineNumber++; - } - prev = b[i]; - } - return n; - } - - /** - * @see java.io.InputStream#read(byte[]) - */ - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } -} diff --git a/src/org/apache/james/mime4j/SimpleContentHandler.java b/src/org/apache/james/mime4j/SimpleContentHandler.java deleted file mode 100644 index 7d25d0804..000000000 --- a/src/org/apache/james/mime4j/SimpleContentHandler.java +++ /dev/null @@ -1,100 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j; - -import org.apache.james.mime4j.decoder.Base64InputStream; -import org.apache.james.mime4j.decoder.QuotedPrintableInputStream; -import org.apache.james.mime4j.field.Field; -import org.apache.james.mime4j.message.Header; - -import java.io.InputStream; -import java.io.IOException; - -/** - * Abstract implementation of ContentHandler that automates common - * tasks. Currently performs header parsing and applies content-transfer - * decoding to body parts. - * - * - */ -public abstract class SimpleContentHandler extends AbstractContentHandler { - - /** - * Called after headers are parsed. - */ - public abstract void headers(Header header); - - /** - * Called when the body of a discrete (non-multipart) entity is encountered. - - * @param bd encapsulates the values (either read from the - * message stream or, if not present, determined implictly - * as described in the - * MIME rfc:s) of the Content-Type and - * Content-Transfer-Encoding header fields. - * @param is the contents of the body. Base64 or quoted-printable - * decoding will be applied transparently. - * @throws IOException should be thrown on I/O errors. - */ - public abstract void bodyDecoded(BodyDescriptor bd, InputStream is) throws IOException; - - - /* Implement introduced callbacks. */ - - private Header currHeader; - - /** - * @see org.apache.james.mime4j.AbstractContentHandler#startHeader() - */ - public final void startHeader() { - currHeader = new Header(); - } - - /** - * @see org.apache.james.mime4j.AbstractContentHandler#field(java.lang.String) - */ - public final void field(String fieldData) { - currHeader.addField(Field.parse(fieldData)); - } - - /** - * @see org.apache.james.mime4j.AbstractContentHandler#endHeader() - */ - public final void endHeader() { - Header tmp = currHeader; - currHeader = null; - headers(tmp); - } - - /** - * @see org.apache.james.mime4j.AbstractContentHandler#body(org.apache.james.mime4j.BodyDescriptor, java.io.InputStream) - */ - public final void body(BodyDescriptor bd, InputStream is) throws IOException { - if (bd.isBase64Encoded()) { - bodyDecoded(bd, new Base64InputStream(is)); - } - else if (bd.isQuotedPrintableEncoded()) { - bodyDecoded(bd, new QuotedPrintableInputStream(is)); - } - else { - bodyDecoded(bd, is); - } - } -} diff --git a/src/org/apache/james/mime4j/codec/Base64InputStream.java b/src/org/apache/james/mime4j/codec/Base64InputStream.java new file mode 100644 index 000000000..64cd31725 --- /dev/null +++ b/src/org/apache/james/mime4j/codec/Base64InputStream.java @@ -0,0 +1,286 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.codec; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mime4j.util.ByteArrayBuffer; + +/** + * Performs Base-64 decoding on an underlying stream. + */ +public class Base64InputStream extends InputStream { + private static final int ENCODED_BUFFER_SIZE = 1536; + + private static final int[] BASE64_DECODE = new int[256]; + + static { + for (int i = 0; i < 256; i++) + BASE64_DECODE[i] = -1; + for (int i = 0; i < Base64OutputStream.BASE64_TABLE.length; i++) + BASE64_DECODE[Base64OutputStream.BASE64_TABLE[i] & 0xff] = i; + } + + private static final byte BASE64_PAD = '='; + + private static final int EOF = -1; + + private final byte[] singleByte = new byte[1]; + + private final InputStream in; + private final byte[] encoded; + private final ByteArrayBuffer decodedBuf; + + private int position = 0; // current index into encoded buffer + private int size = 0; // current size of encoded buffer + + private boolean closed = false; + private boolean eof; // end of file or pad character reached + + private final DecodeMonitor monitor; + + public Base64InputStream(InputStream in, DecodeMonitor monitor) { + this(ENCODED_BUFFER_SIZE, in, monitor); + } + + protected Base64InputStream(int bufsize, InputStream in, DecodeMonitor monitor) { + if (in == null) + throw new IllegalArgumentException(); + this.encoded = new byte[bufsize]; + this.decodedBuf = new ByteArrayBuffer(512); + this.in = in; + this.monitor = monitor; + } + + public Base64InputStream(InputStream in) { + this(in, false); + } + + public Base64InputStream(InputStream in, boolean strict) { + this(ENCODED_BUFFER_SIZE, in, strict ? DecodeMonitor.STRICT : DecodeMonitor.SILENT); + } + + @Override + public int read() throws IOException { + if (closed) + throw new IOException("Stream has been closed"); + + while (true) { + int bytes = read0(singleByte, 0, 1); + if (bytes == EOF) + return EOF; + + if (bytes == 1) + return singleByte[0] & 0xff; + } + } + + @Override + public int read(byte[] buffer) throws IOException { + if (closed) + throw new IOException("Stream has been closed"); + + if (buffer == null) + throw new NullPointerException(); + + if (buffer.length == 0) + return 0; + + return read0(buffer, 0, buffer.length); + } + + @Override + public int read(byte[] buffer, int offset, int length) throws IOException { + if (closed) + throw new IOException("Stream has been closed"); + + if (buffer == null) + throw new NullPointerException(); + + if (offset < 0 || length < 0 || offset + length > buffer.length) + throw new IndexOutOfBoundsException(); + + if (length == 0) + return 0; + + return read0(buffer, offset, length); + } + + @Override + public void close() throws IOException { + if (closed) + return; + + closed = true; + } + + private int read0(final byte[] buffer, final int off, final int len) throws IOException { + int from = off; + int to = off + len; + int index = off; + + // check if a previous invocation left decoded content + if (decodedBuf.length() > 0) { + int chunk = Math.min(decodedBuf.length(), len); + System.arraycopy(decodedBuf.buffer(), 0, buffer, index, chunk); + decodedBuf.remove(0, chunk); + index += chunk; + } + + // eof or pad reached? + + if (eof) + return index == from ? EOF : index - from; + + // decode into given buffer + + int data = 0; // holds decoded data; up to four sextets + int sextets = 0; // number of sextets + + while (index < to) { + // make sure buffer not empty + + while (position == size) { + int n = in.read(encoded, 0, encoded.length); + if (n == EOF) { + eof = true; + + if (sextets != 0) { + // error in encoded data + handleUnexpectedEof(sextets); + } + + return index == from ? EOF : index - from; + } else if (n > 0) { + position = 0; + size = n; + } else { + assert n == 0; + } + } + + // decode buffer + + while (position < size && index < to) { + int value = encoded[position++] & 0xff; + + if (value == BASE64_PAD) { + index = decodePad(data, sextets, buffer, index, to); + return index - from; + } + + int decoded = BASE64_DECODE[value]; + if (decoded < 0) { // -1: not a base64 char + if (value != 0x0D && value != 0x0A && value != 0x20) { + if (monitor.warn("Unexpected base64 byte: "+(byte) value, "ignoring.")) + throw new IOException("Unexpected base64 byte"); + } + continue; + } + + data = (data << 6) | decoded; + sextets++; + + if (sextets == 4) { + sextets = 0; + + byte b1 = (byte) (data >>> 16); + byte b2 = (byte) (data >>> 8); + byte b3 = (byte) data; + + if (index < to - 2) { + buffer[index++] = b1; + buffer[index++] = b2; + buffer[index++] = b3; + } else { + if (index < to - 1) { + buffer[index++] = b1; + buffer[index++] = b2; + decodedBuf.append(b3); + } else if (index < to) { + buffer[index++] = b1; + decodedBuf.append(b2); + decodedBuf.append(b3); + } else { + decodedBuf.append(b1); + decodedBuf.append(b2); + decodedBuf.append(b3); + } + + assert index == to; + return to - from; + } + } + } + } + + assert sextets == 0; + assert index == to; + return to - from; + } + + private int decodePad(int data, int sextets, final byte[] buffer, + int index, final int end) throws IOException { + eof = true; + + if (sextets == 2) { + // one byte encoded as "XY==" + + byte b = (byte) (data >>> 4); + if (index < end) { + buffer[index++] = b; + } else { + decodedBuf.append(b); + } + } else if (sextets == 3) { + // two bytes encoded as "XYZ=" + + byte b1 = (byte) (data >>> 10); + byte b2 = (byte) ((data >>> 2) & 0xFF); + + if (index < end - 1) { + buffer[index++] = b1; + buffer[index++] = b2; + } else if (index < end) { + buffer[index++] = b1; + decodedBuf.append(b2); + } else { + decodedBuf.append(b1); + decodedBuf.append(b2); + } + } else { + // error in encoded data + handleUnexpecedPad(sextets); + } + + return index; + } + + private void handleUnexpectedEof(int sextets) throws IOException { + if (monitor.warn("Unexpected end of BASE64 stream", "dropping " + sextets + " sextet(s)")) + throw new IOException("Unexpected end of BASE64 stream"); + } + + private void handleUnexpecedPad(int sextets) throws IOException { + if (monitor.warn("Unexpected padding character", "dropping " + sextets + " sextet(s)")) + throw new IOException("Unexpected padding character"); + } +} diff --git a/src/org/apache/james/mime4j/codec/Base64OutputStream.java b/src/org/apache/james/mime4j/codec/Base64OutputStream.java new file mode 100644 index 000000000..b5bb1ca8e --- /dev/null +++ b/src/org/apache/james/mime4j/codec/Base64OutputStream.java @@ -0,0 +1,321 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.codec; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Set; + +/** + * This class implements section 6.8. Base64 Content-Transfer-Encoding + * from RFC 2045 Multipurpose Internet Mail Extensions (MIME) Part One: + * Format of Internet Message Bodies by Freed and Borenstein. + *

+ * Code is based on Base64 and Base64OutputStream code from Commons-Codec 1.4. + * + * @see RFC 2045 + */ +public class Base64OutputStream extends FilterOutputStream { + + // Default line length per RFC 2045 section 6.8. + private static final int DEFAULT_LINE_LENGTH = 76; + + // CRLF line separator per RFC 2045 section 2.1. + private static final byte[] CRLF_SEPARATOR = { '\r', '\n' }; + + // This array is a lookup table that translates 6-bit positive integer index + // values into their "Base64 Alphabet" equivalents as specified in Table 1 + // of RFC 2045. + static final byte[] BASE64_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' }; + + // Byte used to pad output. + private static final byte BASE64_PAD = '='; + + // This set contains all base64 characters including the pad character. Used + // solely to check if a line separator contains any of these characters. + private static final Set BASE64_CHARS = new HashSet(); + + static { + for (byte b : BASE64_TABLE) { + BASE64_CHARS.add(b); + } + BASE64_CHARS.add(BASE64_PAD); + } + + // Mask used to extract 6 bits + private static final int MASK_6BITS = 0x3f; + + private static final int ENCODED_BUFFER_SIZE = 2048; + + private final byte[] singleByte = new byte[1]; + + private final int lineLength; + private final byte[] lineSeparator; + + private boolean closed = false; + + private final byte[] encoded; + private int position = 0; + + private int data = 0; + private int modulus = 0; + + private int linePosition = 0; + + /** + * Creates a Base64OutputStream that writes the encoded data + * to the given output stream using the default line length (76) and line + * separator (CRLF). + * + * @param out + * underlying output stream. + */ + public Base64OutputStream(OutputStream out) { + this(out, DEFAULT_LINE_LENGTH, CRLF_SEPARATOR); + } + + /** + * Creates a Base64OutputStream that writes the encoded data + * to the given output stream using the given line length and the default + * line separator (CRLF). + *

+ * The given line length will be rounded up to the nearest multiple of 4. If + * the line length is zero then the output will not be split into lines. + * + * @param out + * underlying output stream. + * @param lineLength + * desired line length. + */ + public Base64OutputStream(OutputStream out, int lineLength) { + this(out, lineLength, CRLF_SEPARATOR); + } + + /** + * Creates a Base64OutputStream that writes the encoded data + * to the given output stream using the given line length and line + * separator. + *

+ * The given line length will be rounded up to the nearest multiple of 4. If + * the line length is zero then the output will not be split into lines and + * the line separator is ignored. + *

+ * The line separator must not include characters from the BASE64 alphabet + * (including the padding character =). + * + * @param out + * underlying output stream. + * @param lineLength + * desired line length. + * @param lineSeparator + * line separator to use. + */ + public Base64OutputStream(OutputStream out, int lineLength, + byte[] lineSeparator) { + super(out); + + if (out == null) + throw new IllegalArgumentException(); + if (lineLength < 0) + throw new IllegalArgumentException(); + checkLineSeparator(lineSeparator); + + this.lineLength = lineLength; + this.lineSeparator = new byte[lineSeparator.length]; + System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, + lineSeparator.length); + + this.encoded = new byte[ENCODED_BUFFER_SIZE]; + } + + @Override + public final void write(final int b) throws IOException { + if (closed) + throw new IOException("Base64OutputStream has been closed"); + + singleByte[0] = (byte) b; + write0(singleByte, 0, 1); + } + + @Override + public final void write(final byte[] buffer) throws IOException { + if (closed) + throw new IOException("Base64OutputStream has been closed"); + + if (buffer == null) + throw new NullPointerException(); + + if (buffer.length == 0) + return; + + write0(buffer, 0, buffer.length); + } + + @Override + public final void write(final byte[] buffer, final int offset, + final int length) throws IOException { + if (closed) + throw new IOException("Base64OutputStream has been closed"); + + if (buffer == null) + throw new NullPointerException(); + + if (offset < 0 || length < 0 || offset + length > buffer.length) + throw new IndexOutOfBoundsException(); + + if (length == 0) + return; + + write0(buffer, offset, offset + length); + } + + @Override + public void flush() throws IOException { + if (closed) + throw new IOException("Base64OutputStream has been closed"); + + flush0(); + } + + @Override + public void close() throws IOException { + if (closed) + return; + + closed = true; + close0(); + } + + private void write0(final byte[] buffer, final int from, final int to) + throws IOException { + for (int i = from; i < to; i++) { + data = (data << 8) | (buffer[i] & 0xff); + + if (++modulus == 3) { + modulus = 0; + + // write line separator if necessary + + if (lineLength > 0 && linePosition >= lineLength) { + // writeLineSeparator() inlined for performance reasons + + linePosition = 0; + + if (encoded.length - position < lineSeparator.length) + flush0(); + + for (byte ls : lineSeparator) + encoded[position++] = ls; + } + + // encode data into 4 bytes + + if (encoded.length - position < 4) + flush0(); + + encoded[position++] = BASE64_TABLE[(data >> 18) & MASK_6BITS]; + encoded[position++] = BASE64_TABLE[(data >> 12) & MASK_6BITS]; + encoded[position++] = BASE64_TABLE[(data >> 6) & MASK_6BITS]; + encoded[position++] = BASE64_TABLE[data & MASK_6BITS]; + + linePosition += 4; + } + } + } + + private void flush0() throws IOException { + if (position > 0) { + out.write(encoded, 0, position); + position = 0; + } + } + + private void close0() throws IOException { + if (modulus != 0) + writePad(); + + // write line separator at the end of the encoded data + + if (lineLength > 0 && linePosition > 0) { + writeLineSeparator(); + } + + flush0(); + } + + private void writePad() throws IOException { + // write line separator if necessary + + if (lineLength > 0 && linePosition >= lineLength) { + writeLineSeparator(); + } + + // encode data into 4 bytes + + if (encoded.length - position < 4) + flush0(); + + if (modulus == 1) { + encoded[position++] = BASE64_TABLE[(data >> 2) & MASK_6BITS]; + encoded[position++] = BASE64_TABLE[(data << 4) & MASK_6BITS]; + encoded[position++] = BASE64_PAD; + encoded[position++] = BASE64_PAD; + } else { + assert modulus == 2; + encoded[position++] = BASE64_TABLE[(data >> 10) & MASK_6BITS]; + encoded[position++] = BASE64_TABLE[(data >> 4) & MASK_6BITS]; + encoded[position++] = BASE64_TABLE[(data << 2) & MASK_6BITS]; + encoded[position++] = BASE64_PAD; + } + + linePosition += 4; + } + + private void writeLineSeparator() throws IOException { + linePosition = 0; + + if (encoded.length - position < lineSeparator.length) + flush0(); + + for (byte ls : lineSeparator) + encoded[position++] = ls; + } + + private void checkLineSeparator(byte[] lineSeparator) { + if (lineSeparator.length > ENCODED_BUFFER_SIZE) + throw new IllegalArgumentException("line separator length exceeds " + + ENCODED_BUFFER_SIZE); + + for (byte b : lineSeparator) { + if (BASE64_CHARS.contains(b)) { + throw new IllegalArgumentException( + "line separator must not contain base64 character '" + + (char) (b & 0xff) + "'"); + } + } + } +} diff --git a/src/org/apache/james/mime4j/codec/CodecUtil.java b/src/org/apache/james/mime4j/codec/CodecUtil.java new file mode 100644 index 000000000..b039e56db --- /dev/null +++ b/src/org/apache/james/mime4j/codec/CodecUtil.java @@ -0,0 +1,108 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.codec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Utility methods related to codecs. + */ +public class CodecUtil { + + static final int DEFAULT_ENCODING_BUFFER_SIZE = 1024; + + /** + * Copies the contents of one stream to the other. + * @param in not null + * @param out not null + * @throws IOException + */ + public static void copy(final InputStream in, final OutputStream out) throws IOException { + final byte[] buffer = new byte[DEFAULT_ENCODING_BUFFER_SIZE]; + int inputLength; + while (-1 != (inputLength = in.read(buffer))) { + out.write(buffer, 0, inputLength); + } + } + + /** + * Encodes the given stream using Quoted-Printable. + * This assumes that stream is binary and therefore escapes + * all line endings. + * @param in not null + * @param out not null + * @throws IOException + */ + public static void encodeQuotedPrintableBinary(final InputStream in, final OutputStream out) throws IOException { + QuotedPrintableOutputStream qpOut = new QuotedPrintableOutputStream(out, true); + copy(in, qpOut); + qpOut.close(); + } + + /** + * Encodes the given stream using Quoted-Printable. + * This assumes that stream is text and therefore does not escape + * all line endings. + * @param in not null + * @param out not null + * @throws IOException + */ + public static void encodeQuotedPrintable(final InputStream in, final OutputStream out) throws IOException { + QuotedPrintableOutputStream qpOut = new QuotedPrintableOutputStream(out, false); + copy(in, qpOut); + qpOut.close(); + } + + /** + * Encodes the given stream using base64. + * + * @param in not null + * @param out not null + * @throws IOException if an I/O error occurs + */ + public static void encodeBase64(final InputStream in, final OutputStream out) throws IOException { + Base64OutputStream b64Out = new Base64OutputStream(out); + copy(in, b64Out); + b64Out.close(); + } + + /** + * Wraps the given stream in a Quoted-Printable encoder. + * @param out not null + * @return encoding outputstream + * @throws IOException + */ + public static OutputStream wrapQuotedPrintable(final OutputStream out, boolean binary) throws IOException { + return new QuotedPrintableOutputStream(out, binary); + } + + /** + * Wraps the given stream in a Base64 encoder. + * @param out not null + * @return encoding outputstream + * @throws IOException + */ + public static OutputStream wrapBase64(final OutputStream out) throws IOException { + return new Base64OutputStream(out); + } + +} diff --git a/src/org/apache/james/mime4j/codec/DecodeMonitor.java b/src/org/apache/james/mime4j/codec/DecodeMonitor.java new file mode 100644 index 000000000..5f4b449ad --- /dev/null +++ b/src/org/apache/james/mime4j/codec/DecodeMonitor.java @@ -0,0 +1,65 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.codec; + + +/** + * This class is used to drive how decoder/parser should deal with malformed + * and unexpected data. + * + * 2 basic implementations are provided: + * STRICT return "true" on any occourence. + * SILENT ignores any problem. + * + * @see org.apache.james.mime4j.field.LoggingMonitor for an example + * about logging malformations via Commons-logging. + */ +public class DecodeMonitor { + + /** + * The STRICT monitor throws an exception on every event. + */ + public static final DecodeMonitor STRICT = new DecodeMonitor() { + + @Override + public boolean warn(String error, String dropDesc) { + return true; + } + + @Override + public boolean isListening() { + return true; + } + }; + + /** + * The SILENT monitor ignore requests. + */ + public static final DecodeMonitor SILENT = new DecodeMonitor(); + + public boolean warn(String error, String dropDesc) { + return false; + } + + public boolean isListening() { + return false; + } + +} diff --git a/src/org/apache/james/mime4j/codec/DecoderUtil.java b/src/org/apache/james/mime4j/codec/DecoderUtil.java new file mode 100644 index 000000000..e46863ffb --- /dev/null +++ b/src/org/apache/james/mime4j/codec/DecoderUtil.java @@ -0,0 +1,260 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.codec; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.james.mime4j.util.CharsetUtil; + +/** + * Static methods for decoding strings, byte arrays and encoded words. + */ +public class DecoderUtil { + + private static final Pattern PATTERN_ENCODED_WORD = Pattern.compile( + "(.*?)=\\?([^\\?]+?)\\?(\\w)\\?([^\\?]+?)\\?=", Pattern.DOTALL); + + /** + * Decodes a string containing quoted-printable encoded data. + * + * @param s the string to decode. + * @return the decoded bytes. + */ + private static byte[] decodeQuotedPrintable(String s, DecodeMonitor monitor) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + try { + byte[] bytes = s.getBytes("US-ASCII"); + + QuotedPrintableInputStream is = new QuotedPrintableInputStream( + new ByteArrayInputStream(bytes), monitor); + + int b = 0; + while ((b = is.read()) != -1) { + baos.write(b); + } + } catch (IOException e) { + // This should never happen! + throw new IllegalStateException(e); + } + + return baos.toByteArray(); + } + + /** + * Decodes a string containing base64 encoded data. + * + * @param s the string to decode. + * @param monitor + * @return the decoded bytes. + */ + private static byte[] decodeBase64(String s, DecodeMonitor monitor) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + try { + byte[] bytes = s.getBytes("US-ASCII"); + + Base64InputStream is = new Base64InputStream( + new ByteArrayInputStream(bytes), monitor); + + int b = 0; + while ((b = is.read()) != -1) { + baos.write(b); + } + } catch (IOException e) { + // This should never happen! + throw new IllegalStateException(e); + } + + return baos.toByteArray(); + } + + /** + * Decodes an encoded text encoded with the 'B' encoding (described in + * RFC 2047) found in a header field body. + * + * @param encodedText the encoded text to decode. + * @param charset the Java charset to use. + * @param monitor + * @return the decoded string. + * @throws UnsupportedEncodingException if the given Java charset isn't + * supported. + */ + static String decodeB(String encodedText, String charset, DecodeMonitor monitor) + throws UnsupportedEncodingException { + byte[] decodedBytes = decodeBase64(encodedText, monitor); + return new String(decodedBytes, charset); + } + + /** + * Decodes an encoded text encoded with the 'Q' encoding (described in + * RFC 2047) found in a header field body. + * + * @param encodedText the encoded text to decode. + * @param charset the Java charset to use. + * @return the decoded string. + * @throws UnsupportedEncodingException if the given Java charset isn't + * supported. + */ + static String decodeQ(String encodedText, String charset, DecodeMonitor monitor) + throws UnsupportedEncodingException { + encodedText = replaceUnderscores(encodedText); + + byte[] decodedBytes = decodeQuotedPrintable(encodedText, monitor); + return new String(decodedBytes, charset); + } + + static String decodeEncodedWords(String body) { + return decodeEncodedWords(body, DecodeMonitor.SILENT); + } + + /** + * Decodes a string containing encoded words as defined by RFC 2047. Encoded + * words have the form =?charset?enc?encoded-text?= where enc is either 'Q' + * or 'q' for quoted-printable and 'B' or 'b' for base64. + * + * @param body the string to decode + * @param monitor the DecodeMonitor to be used. + * @return the decoded string. + * @throws IllegalArgumentException only if the DecodeMonitor strategy throws it (Strict parsing) + */ + public static String decodeEncodedWords(String body, DecodeMonitor monitor) throws IllegalArgumentException { + int tailIndex = 0; + boolean lastMatchValid = false; + + StringBuilder sb = new StringBuilder(); + + for (Matcher matcher = PATTERN_ENCODED_WORD.matcher(body); matcher.find();) { + String separator = matcher.group(1); + String mimeCharset = matcher.group(2); + String encoding = matcher.group(3); + String encodedText = matcher.group(4); + + String decoded = null; + decoded = tryDecodeEncodedWord(mimeCharset, encoding, encodedText, monitor); + if (decoded == null) { + sb.append(matcher.group(0)); + } else { + if (!lastMatchValid || !CharsetUtil.isWhitespace(separator)) { + sb.append(separator); + } + sb.append(decoded); + } + + tailIndex = matcher.end(); + lastMatchValid = decoded != null; + } + + if (tailIndex == 0) { + return body; + } else { + sb.append(body.substring(tailIndex)); + return sb.toString(); + } + } + + // return null on error + private static String tryDecodeEncodedWord(final String mimeCharset, + final String encoding, final String encodedText, DecodeMonitor monitor) throws IllegalArgumentException { + String charset = CharsetUtil.toJavaCharset(mimeCharset); + if (charset == null) { + monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded", + "Mime charser '", mimeCharset, "' doesn't have a corresponding Java charset"); + return null; + } else if (!CharsetUtil.isDecodingSupported(charset)) { + monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded", + "Current JDK doesn't support decoding of charset '", charset, + "' - MIME charset '", mimeCharset, "' in encoded word"); + return null; + } + + if (encodedText.length() == 0) { + monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded", + "Missing encoded text in encoded word"); + return null; + } + + try { + if (encoding.equalsIgnoreCase("Q")) { + return DecoderUtil.decodeQ(encodedText, charset, monitor); + } else if (encoding.equalsIgnoreCase("B")) { + return DecoderUtil.decodeB(encodedText, charset, monitor); + } else { + monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded", + "Warning: Unknown encoding in encoded word"); + return null; + } + } catch (UnsupportedEncodingException e) { + // should not happen because of isDecodingSupported check above + monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded", + "Unsupported encoding (", e.getMessage(), ") in encoded word"); + return null; + } catch (RuntimeException e) { + monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded", + "Could not decode (", e.getMessage(), ") encoded word"); + return null; + } + } + + private static void monitor(DecodeMonitor monitor, String mimeCharset, String encoding, + String encodedText, String dropDesc, String... strings) throws IllegalArgumentException { + if (monitor.isListening()) { + String encodedWord = recombine(mimeCharset, encoding, encodedText); + StringBuilder text = new StringBuilder(); + for (String str : strings) { + text.append(str); + } + text.append(" ("); + text.append(encodedWord); + text.append(")"); + String exceptionDesc = text.toString(); + if (monitor.warn(exceptionDesc, dropDesc)) + throw new IllegalArgumentException(text.toString()); + } + } + + private static String recombine(final String mimeCharset, + final String encoding, final String encodedText) { + return "=?" + mimeCharset + "?" + encoding + "?" + encodedText + "?="; + } + + // Replace _ with =20 + private static String replaceUnderscores(String str) { + // probably faster than String#replace(CharSequence, CharSequence) + + StringBuilder sb = new StringBuilder(128); + + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c == '_') { + sb.append("=20"); + } else { + sb.append(c); + } + } + + return sb.toString(); + } +} diff --git a/src/org/apache/james/mime4j/codec/EncoderUtil.java b/src/org/apache/james/mime4j/codec/EncoderUtil.java index 6841bc998..c69c77f42 100644 --- a/src/org/apache/james/mime4j/codec/EncoderUtil.java +++ b/src/org/apache/james/mime4j/codec/EncoderUtil.java @@ -26,31 +26,14 @@ import java.util.Locale; import org.apache.james.mime4j.util.CharsetUtil; -/** - * ANDROID: THIS CLASS IS COPIED FROM A NEWER VERSION OF MIME4J - */ - /** * Static methods for encoding header field values. This includes encoded-words * as defined in RFC 2047 * or display-names of an e-mail address, for example. - * */ public class EncoderUtil { - - // This array is a lookup table that translates 6-bit positive integer index - // values into their "Base64 Alphabet" equivalents as specified in Table 1 - // of RFC 2045. - // ANDROID: THIS TABLE IS COPIED FROM BASE64OUTPUTSTREAM - static final byte[] BASE64_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F', - 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', - 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9', '+', '/' }; - - // Byte used to pad output. - private static final byte BASE64_PAD = '='; + private static final byte[] BASE64_TABLE = Base64OutputStream.BASE64_TABLE; + private static final char BASE64_PAD = '='; private static final BitSet Q_REGULAR_CHARS = initChars("=_?"); @@ -374,14 +357,14 @@ public class EncoderUtil { sb.append((char) BASE64_TABLE[data >> 18 & 0x3f]); sb.append((char) BASE64_TABLE[data >> 12 & 0x3f]); sb.append((char) BASE64_TABLE[data >> 6 & 0x3f]); - sb.append((char) BASE64_PAD); + sb.append(BASE64_PAD); } else if (idx == end - 1) { int data = (bytes[idx] & 0xff) << 16; sb.append((char) BASE64_TABLE[data >> 18 & 0x3f]); sb.append((char) BASE64_TABLE[data >> 12 & 0x3f]); - sb.append((char) BASE64_PAD); - sb.append((char) BASE64_PAD); + sb.append(BASE64_PAD); + sb.append(BASE64_PAD); } return sb.toString(); @@ -518,14 +501,12 @@ public class EncoderUtil { if (totalLength <= ENCODED_WORD_MAX_LENGTH - usedCharacters) { return prefix + encodeB(bytes) + ENC_WORD_SUFFIX; } else { - int splitOffset = text.offsetByCodePoints(text.length() / 2, -1); - - String part1 = text.substring(0, splitOffset); + String part1 = text.substring(0, text.length() / 2); byte[] bytes1 = encode(part1, charset); String word1 = encodeB(prefix, part1, usedCharacters, charset, bytes1); - String part2 = text.substring(splitOffset); + String part2 = text.substring(text.length() / 2); byte[] bytes2 = encode(part2, charset); String word2 = encodeB(prefix, part2, 0, charset, bytes2); @@ -546,14 +527,12 @@ public class EncoderUtil { if (totalLength <= ENCODED_WORD_MAX_LENGTH - usedCharacters) { return prefix + encodeQ(bytes, usage) + ENC_WORD_SUFFIX; } else { - int splitOffset = text.offsetByCodePoints(text.length() / 2, -1); - - String part1 = text.substring(0, splitOffset); + String part1 = text.substring(0, text.length() / 2); byte[] bytes1 = encode(part1, charset); String word1 = encodeQ(prefix, part1, usage, usedCharacters, charset, bytes1); - String part2 = text.substring(splitOffset); + String part2 = text.substring(text.length() / 2); byte[] bytes2 = encode(part2, charset); String word2 = encodeQ(prefix, part2, usage, 0, charset, bytes2); diff --git a/src/org/apache/james/mime4j/codec/QuotedPrintableEncoder.java b/src/org/apache/james/mime4j/codec/QuotedPrintableEncoder.java deleted file mode 100644 index ea1c68c05..000000000 --- a/src/org/apache/james/mime4j/codec/QuotedPrintableEncoder.java +++ /dev/null @@ -1,271 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.codec; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -// Taken from Apache Mime4j 0.6 - -final class QuotedPrintableEncoder -{ - private static final byte TAB = 0x09; - private static final byte SPACE = 0x20; - private static final byte EQUALS = 0x3D; - private static final byte CR = 0x0D; - private static final byte LF = 0x0A; - private static final byte QUOTED_PRINTABLE_LAST_PLAIN = 0x7E; - private static final int QUOTED_PRINTABLE_MAX_LINE_LENGTH = 76; - private static final int QUOTED_PRINTABLE_OCTETS_PER_ESCAPE = 3; - private static final byte[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - - private final byte[] inBuffer; - private final byte[] outBuffer; - private final boolean binary; - - private boolean pendingSpace; - private boolean pendingTab; - private boolean pendingCR; - private int nextSoftBreak; - private int outputIndex; - private OutputStream out; - - public QuotedPrintableEncoder(int bufferSize, boolean binary) - { - inBuffer = new byte[bufferSize]; - outBuffer = new byte[3 * bufferSize]; - outputIndex = 0; - nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1; - out = null; - this.binary = binary; - pendingSpace = false; - pendingTab = false; - pendingCR = false; - } - - void initEncoding(final OutputStream out) - { - this.out = out; - pendingSpace = false; - pendingTab = false; - pendingCR = false; - nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1; - } - - void encodeChunk(byte[] buffer, int off, int len) throws IOException - { - for (int inputIndex = off; inputIndex < len + off; inputIndex++) - { - encode(buffer[inputIndex]); - } - } - - void completeEncoding() throws IOException - { - writePending(); - flushOutput(); - } - - public void encode(final InputStream in, final OutputStream out) - throws IOException - { - initEncoding(out); - int inputLength; - while ((inputLength = in.read(inBuffer)) > -1) - { - encodeChunk(inBuffer, 0, inputLength); - } - completeEncoding(); - } - - private void writePending() throws IOException - { - if (pendingSpace) - { - plain(SPACE); - } - else if (pendingTab) - { - plain(TAB); - } - else if (pendingCR) - { - plain(CR); - } - clearPending(); - } - - private void clearPending() throws IOException - { - pendingSpace = false; - pendingTab = false; - pendingCR = false; - } - - private void encode(byte next) throws IOException - { - if (next == LF) - { - if (binary) - { - writePending(); - escape(next); - } - else - { - if (pendingCR) - { - // Expect either space or tab pending - // but not both - if (pendingSpace) - { - escape(SPACE); - } - else if (pendingTab) - { - escape(TAB); - } - lineBreak(); - clearPending(); - } - else - { - writePending(); - plain(next); - } - } - } - else if (next == CR) - { - if (binary) - { - escape(next); - } - else - { - pendingCR = true; - } - } - else - { - writePending(); - if (next == SPACE) - { - if (binary) - { - escape(next); - } - else - { - pendingSpace = true; - } - } - else if (next == TAB) - { - if (binary) - { - escape(next); - } - else - { - pendingTab = true; - } - } - else if (next < SPACE) - { - escape(next); - } - else if (next > QUOTED_PRINTABLE_LAST_PLAIN) - { - escape(next); - } - else if (next == EQUALS) - { - escape(next); - } - else - { - plain(next); - } - } - } - - private void plain(byte next) throws IOException - { - if (--nextSoftBreak <= 1) - { - softBreak(); - } - write(next); - } - - private void escape(byte next) throws IOException - { - if (--nextSoftBreak <= QUOTED_PRINTABLE_OCTETS_PER_ESCAPE) - { - softBreak(); - } - - int nextUnsigned = next & 0xff; - - write(EQUALS); - --nextSoftBreak; - write(HEX_DIGITS[nextUnsigned >> 4]); - --nextSoftBreak; - write(HEX_DIGITS[nextUnsigned % 0x10]); - } - - private void write(byte next) throws IOException - { - outBuffer[outputIndex++] = next; - if (outputIndex >= outBuffer.length) - { - flushOutput(); - } - } - - private void softBreak() throws IOException - { - write(EQUALS); - lineBreak(); - } - - private void lineBreak() throws IOException - { - write(CR); - write(LF); - nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH; - } - - void flushOutput() throws IOException - { - if (outputIndex < outBuffer.length) - { - out.write(outBuffer, 0, outputIndex); - } - else - { - out.write(outBuffer); - } - outputIndex = 0; - } -} diff --git a/src/org/apache/james/mime4j/codec/QuotedPrintableInputStream.java b/src/org/apache/james/mime4j/codec/QuotedPrintableInputStream.java new file mode 100644 index 000000000..7d07bde86 --- /dev/null +++ b/src/org/apache/james/mime4j/codec/QuotedPrintableInputStream.java @@ -0,0 +1,309 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.codec; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mime4j.util.ByteArrayBuffer; + +/** + * Performs Quoted-Printable decoding on an underlying stream. + */ +public class QuotedPrintableInputStream extends InputStream { + + private static final int DEFAULT_BUFFER_SIZE = 1024 * 2; + + private static final byte EQ = 0x3D; + private static final byte CR = 0x0D; + private static final byte LF = 0x0A; + + private final byte[] singleByte = new byte[1]; + + private final InputStream in; + private final ByteArrayBuffer decodedBuf; + private final ByteArrayBuffer blanks; + + private final byte[] encoded; + private int pos = 0; // current index into encoded buffer + private int limit = 0; // current size of encoded buffer + + private boolean closed; + + private final DecodeMonitor monitor; + + public QuotedPrintableInputStream(final InputStream in, DecodeMonitor monitor) { + this(DEFAULT_BUFFER_SIZE, in, monitor); + } + + protected QuotedPrintableInputStream(final int bufsize, final InputStream in, DecodeMonitor monitor) { + super(); + this.in = in; + this.encoded = new byte[bufsize]; + this.decodedBuf = new ByteArrayBuffer(512); + this.blanks = new ByteArrayBuffer(512); + this.closed = false; + this.monitor = monitor; + } + + protected QuotedPrintableInputStream(final int bufsize, final InputStream in, boolean strict) { + this(bufsize, in, strict ? DecodeMonitor.STRICT : DecodeMonitor.SILENT); + } + + public QuotedPrintableInputStream(final InputStream in, boolean strict) { + this(DEFAULT_BUFFER_SIZE, in, strict); + } + + public QuotedPrintableInputStream(final InputStream in) { + this(in, false); + } + + /** + * Terminates Quoted-Printable coded content. This method does NOT close + * the underlying input stream. + * + * @throws IOException on I/O errors. + */ + @Override + public void close() throws IOException { + closed = true; + } + + private int fillBuffer() throws IOException { + // Compact buffer if needed + if (pos < limit) { + System.arraycopy(encoded, pos, encoded, 0, limit - pos); + limit -= pos; + pos = 0; + } else { + limit = 0; + pos = 0; + } + + int capacity = encoded.length - limit; + if (capacity > 0) { + int bytesRead = in.read(encoded, limit, capacity); + if (bytesRead > 0) { + limit += bytesRead; + } + return bytesRead; + } else { + return 0; + } + } + + private int getnext() { + if (pos < limit) { + byte b = encoded[pos]; + pos++; + return b & 0xFF; + } else { + return -1; + } + } + + private int peek(int i) { + if (pos + i < limit) { + return encoded[pos + i] & 0xFF; + } else { + return -1; + } + } + + private int transfer( + final int b, final byte[] buffer, final int from, final int to, boolean keepblanks) throws IOException { + int index = from; + if (keepblanks && blanks.length() > 0) { + int chunk = Math.min(blanks.length(), to - index); + System.arraycopy(blanks.buffer(), 0, buffer, index, chunk); + index += chunk; + int remaining = blanks.length() - chunk; + if (remaining > 0) { + decodedBuf.append(blanks.buffer(), chunk, remaining); + } + blanks.clear(); + } else if (blanks.length() > 0 && !keepblanks) { + StringBuilder sb = new StringBuilder(blanks.length() * 3); + for (int i = 0; i < blanks.length(); i++) sb.append(" "+blanks.byteAt(i)); + if (monitor.warn("ignored blanks", sb.toString())) + throw new IOException("ignored blanks"); + } + if (b != -1) { + if (index < to) { + buffer[index++] = (byte) b; + } else { + decodedBuf.append(b); + } + } + return index; + } + + private int read0(final byte[] buffer, final int off, final int len) throws IOException { + boolean eof = false; + int from = off; + int to = off + len; + int index = off; + + // check if a previous invocation left decoded content + if (decodedBuf.length() > 0) { + int chunk = Math.min(decodedBuf.length(), to - index); + System.arraycopy(decodedBuf.buffer(), 0, buffer, index, chunk); + decodedBuf.remove(0, chunk); + index += chunk; + } + + while (index < to) { + + if (limit - pos < 3) { + int bytesRead = fillBuffer(); + eof = bytesRead == -1; + } + + // end of stream? + if (limit - pos == 0 && eof) { + return index == from ? -1 : index - from; + } + + boolean lastWasCR = false; + while (pos < limit && index < to) { + int b = encoded[pos++] & 0xFF; + + if (lastWasCR && b != LF) { + if (monitor.warn("Found CR without LF", "Leaving it as is")) + throw new IOException("Found CR without LF"); + index = transfer(CR, buffer, index, to, false); + } else if (!lastWasCR && b == LF) { + if (monitor.warn("Found LF without CR", "Translating to CRLF")) + throw new IOException("Found LF without CR"); + } + + if (b == CR) { + lastWasCR = true; + continue; + } else { + lastWasCR = false; + } + + if (b == LF) { + // at end of line + if (blanks.length() == 0) { + index = transfer(CR, buffer, index, to, false); + index = transfer(LF, buffer, index, to, false); + } else { + if (blanks.byteAt(0) != EQ) { + // hard line break + index = transfer(CR, buffer, index, to, false); + index = transfer(LF, buffer, index, to, false); + } + } + blanks.clear(); + } else if (b == EQ) { + if (limit - pos < 2 && !eof) { + // not enough buffered data + pos--; + break; + } + + // found special char '=' + int b2 = getnext(); + if (b2 == EQ) { + index = transfer(b2, buffer, index, to, true); + // deal with '==\r\n' brokenness + int bb1 = peek(0); + int bb2 = peek(1); + if (bb1 == LF || (bb1 == CR && bb2 == LF)) { + monitor.warn("Unexpected ==EOL encountered", "== 0x"+bb1+" 0x"+bb2); + blanks.append(b2); + } else { + monitor.warn("Unexpected == encountered", "=="); + } + } else if (Character.isWhitespace((char) b2)) { + // soft line break + index = transfer(-1, buffer, index, to, true); + if (b2 != LF) { + blanks.append(b); + blanks.append(b2); + } + } else { + int b3 = getnext(); + int upper = convert(b2); + int lower = convert(b3); + if (upper < 0 || lower < 0) { + monitor.warn("Malformed encoded value encountered", "leaving "+((char) EQ)+((char) b2)+((char) b3)+" as is"); + // TODO see MIME4J-160 + index = transfer(EQ, buffer, index, to, true); + index = transfer(b2, buffer, index, to, false); + index = transfer(b3, buffer, index, to, false); + } else { + index = transfer((upper << 4) | lower, buffer, index, to, true); + } + } + } else if (Character.isWhitespace(b)) { + blanks.append(b); + } else { + index = transfer((int) b & 0xFF, buffer, index, to, true); + } + } + } + return to - from; + } + + /** + * Converts '0' => 0, 'A' => 10, etc. + * @param c ASCII character value. + * @return Numeric value of hexadecimal character. + */ + private int convert(int c) { + if (c >= '0' && c <= '9') { + return (c - '0'); + } else if (c >= 'A' && c <= 'F') { + return (0xA + (c - 'A')); + } else if (c >= 'a' && c <= 'f') { + return (0xA + (c - 'a')); + } else { + return -1; + } + } + + @Override + public int read() throws IOException { + if (closed) { + throw new IOException("Stream has been closed"); + } + for (;;) { + int bytes = read(singleByte, 0, 1); + if (bytes == -1) { + return -1; + } + if (bytes == 1) { + return singleByte[0] & 0xff; + } + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (closed) { + throw new IOException("Stream has been closed"); + } + return read0(b, off, len); + } + +} diff --git a/src/org/apache/james/mime4j/codec/QuotedPrintableOutputStream.java b/src/org/apache/james/mime4j/codec/QuotedPrintableOutputStream.java index ba5f36332..4ce8356f8 100644 --- a/src/org/apache/james/mime4j/codec/QuotedPrintableOutputStream.java +++ b/src/org/apache/james/mime4j/codec/QuotedPrintableOutputStream.java @@ -23,66 +23,211 @@ import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; -// Taken from Apache Mime4j 0.6 - /** * Performs Quoted-Printable encoding on an underlying stream. */ -public class QuotedPrintableOutputStream extends FilterOutputStream -{ +public class QuotedPrintableOutputStream extends FilterOutputStream { - private static final int DEFAULT_ENCODING_BUFFER_SIZE = 1024; + private static final int DEFAULT_BUFFER_SIZE = 1024 * 3; + + private static final byte TB = 0x09; + private static final byte SP = 0x20; + private static final byte EQ = 0x3D; + private static final byte CR = 0x0D; + private static final byte LF = 0x0A; + private static final byte QUOTED_PRINTABLE_LAST_PLAIN = 0x7E; + private static final int QUOTED_PRINTABLE_MAX_LINE_LENGTH = 76; + private static final int QUOTED_PRINTABLE_OCTETS_PER_ESCAPE = 3; + private static final byte[] HEX_DIGITS = { + '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; + + private final byte[] outBuffer; + private final boolean binary; + + private boolean pendingSpace; + private boolean pendingTab; + private boolean pendingCR; + private int nextSoftBreak; + private int outputIndex; - private QuotedPrintableEncoder encoder; private boolean closed = false; - public QuotedPrintableOutputStream(OutputStream out, boolean binary) - { + private byte[] singleByte = new byte[1]; + + public QuotedPrintableOutputStream(int bufsize, OutputStream out, boolean binary) { super(out); - encoder = new QuotedPrintableEncoder(DEFAULT_ENCODING_BUFFER_SIZE, binary); - encoder.initEncoding(out); + this.outBuffer = new byte[bufsize]; + this.binary = binary; + this.pendingSpace = false; + this.pendingTab = false; + this.pendingCR = false; + this.outputIndex = 0; + this.nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1; + } + + public QuotedPrintableOutputStream(OutputStream out, boolean binary) { + this(DEFAULT_BUFFER_SIZE, out, binary); + } + + private void encodeChunk(byte[] buffer, int off, int len) throws IOException { + for (int inputIndex = off; inputIndex < len + off; inputIndex++) { + encode(buffer[inputIndex]); + } + } + + private void completeEncoding() throws IOException { + writePending(); + flushOutput(); + } + + private void writePending() throws IOException { + if (pendingSpace) { + plain(SP); + } else if (pendingTab) { + plain(TB); + } else if (pendingCR) { + plain(CR); + } + clearPending(); + } + + private void clearPending() throws IOException { + pendingSpace = false; + pendingTab = false; + pendingCR = false; + } + + private void encode(byte next) throws IOException { + if (next == LF) { + if (binary) { + writePending(); + escape(next); + } else { + if (pendingCR) { + // Expect either space or tab pending + // but not both + if (pendingSpace) { + escape(SP); + } else if (pendingTab) { + escape(TB); + } + lineBreak(); + clearPending(); + } else { + writePending(); + plain(next); + } + } + } else if (next == CR) { + if (binary) { + escape(next); + } else { + pendingCR = true; + } + } else { + writePending(); + if (next == SP) { + if (binary) { + escape(next); + } else { + pendingSpace = true; + } + } else if (next == TB) { + if (binary) { + escape(next); + } else { + pendingTab = true; + } + } else if (next < SP) { + escape(next); + } else if (next > QUOTED_PRINTABLE_LAST_PLAIN) { + escape(next); + } else if (next == EQ) { + escape(next); + } else { + plain(next); + } + } + } + + private void plain(byte next) throws IOException { + if (--nextSoftBreak <= 1) { + softBreak(); + } + write(next); + } + + private void escape(byte next) throws IOException { + if (--nextSoftBreak <= QUOTED_PRINTABLE_OCTETS_PER_ESCAPE) { + softBreak(); + } + + int nextUnsigned = next & 0xff; + + write(EQ); + --nextSoftBreak; + write(HEX_DIGITS[nextUnsigned >> 4]); + --nextSoftBreak; + write(HEX_DIGITS[nextUnsigned % 0x10]); + } + + private void write(byte next) throws IOException { + outBuffer[outputIndex++] = next; + if (outputIndex >= outBuffer.length) { + flushOutput(); + } + } + + private void softBreak() throws IOException { + write(EQ); + lineBreak(); + } + + private void lineBreak() throws IOException { + write(CR); + write(LF); + nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH; + } + + void flushOutput() throws IOException { + if (outputIndex < outBuffer.length) { + out.write(outBuffer, 0, outputIndex); + } else { + out.write(outBuffer); + } + outputIndex = 0; } @Override - public void close() throws IOException - { + public void close() throws IOException { if (closed) - { return; - } - try - { - encoder.completeEncoding(); + try { + completeEncoding(); // do not close the wrapped stream - } - finally - { + } finally { closed = true; } } @Override - public void flush() throws IOException - { - encoder.flushOutput(); + public void flush() throws IOException { + flushOutput(); } @Override - public void write(int b) throws IOException - { - this.write(new byte[] { (byte) b }, 0, 1); + public void write(int b) throws IOException { + singleByte[0] = (byte) b; + this.write(singleByte, 0, 1); } @Override - public void write(byte[] b, int off, int len) throws IOException - { - if (closed) - { - throw new IOException("QuotedPrintableOutputStream has been closed"); + public void write(byte[] b, int off, int len) throws IOException { + if (closed) { + throw new IOException("Stream has been closed"); } - - encoder.encodeChunk(b, off, len); + encodeChunk(b, off, len); } -} +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/decoder/Base64InputStream.java b/src/org/apache/james/mime4j/decoder/Base64InputStream.java deleted file mode 100644 index 9af54951b..000000000 --- a/src/org/apache/james/mime4j/decoder/Base64InputStream.java +++ /dev/null @@ -1,146 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.decoder; - -import java.io.IOException; -import java.io.InputStream; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Performs Base-64 decoding on an underlying stream. - * - * - * @version $Id: Base64InputStream.java,v 1.3 2004/11/29 13:15:47 ntherning Exp $ - */ -public class Base64InputStream extends InputStream { - private static Log log = LogFactory.getLog(Base64InputStream.class); - - private final InputStream s; - private final ByteQueue byteq = new ByteQueue(3); - private boolean done = false; - - public Base64InputStream(InputStream s) { - this.s = s; - } - - /** - * Closes the underlying stream. - * - * @throws IOException on I/O errors. - */ - public void close() throws IOException { - s.close(); - } - - public int read() throws IOException { - if (byteq.count() == 0) { - fillBuffer(); - if (byteq.count() == 0) { - return -1; - } - } - - byte val = byteq.dequeue(); - if (val >= 0) - return val; - else - return val & 0xFF; - } - - /** - * Retrieve data from the underlying stream, decode it, - * and put the results in the byteq. - * @throws IOException - */ - private void fillBuffer() throws IOException { - byte[] data = new byte[4]; - int pos = 0; - - int i; - while (!done) { - switch (i = s.read()) { - case -1: - if (pos > 0) { - log.warn("Unexpected EOF in MIME parser, dropping " - + pos + " sextets"); - } - return; - case '=': - decodeAndEnqueue(data, pos); - done = true; - break; - default: - byte sX = TRANSLATION[i]; - if (sX < 0) - continue; - data[pos++] = sX; - if (pos == data.length) { - decodeAndEnqueue(data, pos); - return; - } - break; - } - } - } - - private void decodeAndEnqueue(byte[] data, int len) { - int accum = 0; - accum |= data[0] << 18; - accum |= data[1] << 12; - accum |= data[2] << 6; - accum |= data[3]; - - byte b1 = (byte)(accum >>> 16); - byteq.enqueue(b1); - - if (len > 2) { - byte b2 = (byte)((accum >>> 8) & 0xFF); - byteq.enqueue(b2); - - if (len > 3) { - byte b3 = (byte)(accum & 0xFF); - byteq.enqueue(b3); - } - } - } - - private static byte[] TRANSLATION = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x00 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x10 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 0x20 */ - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 0x30 */ - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 0x40 */ - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 0x50 */ - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 0x60 */ - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 0x70 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x80 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x90 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xA0 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xB0 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xC0 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xD0 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xE0 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* 0xF0 */ - }; - - -} diff --git a/src/org/apache/james/mime4j/decoder/DecoderUtil.java b/src/org/apache/james/mime4j/decoder/DecoderUtil.java deleted file mode 100644 index c39129367..000000000 --- a/src/org/apache/james/mime4j/decoder/DecoderUtil.java +++ /dev/null @@ -1,277 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.decoder; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.james.mime4j.util.CharsetUtil; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; - -/** - * Static methods for decoding strings, byte arrays and encoded words. - * - * - * @version $Id: DecoderUtil.java,v 1.3 2005/02/07 15:33:59 ntherning Exp $ - */ -public class DecoderUtil { - private static Log log = LogFactory.getLog(DecoderUtil.class); - - /** - * Decodes a string containing quoted-printable encoded data. - * - * @param s the string to decode. - * @return the decoded bytes. - */ - public static byte[] decodeBaseQuotedPrintable(String s) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - try { - byte[] bytes = s.getBytes("US-ASCII"); - - QuotedPrintableInputStream is = new QuotedPrintableInputStream( - new ByteArrayInputStream(bytes)); - - int b = 0; - while ((b = is.read()) != -1) { - baos.write(b); - } - } catch (IOException e) { - /* - * This should never happen! - */ - log.error(e); - } - - return baos.toByteArray(); - } - - /** - * Decodes a string containing base64 encoded data. - * - * @param s the string to decode. - * @return the decoded bytes. - */ - public static byte[] decodeBase64(String s) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - try { - byte[] bytes = s.getBytes("US-ASCII"); - - Base64InputStream is = new Base64InputStream( - new ByteArrayInputStream(bytes)); - - int b = 0; - while ((b = is.read()) != -1) { - baos.write(b); - } - } catch (IOException e) { - /* - * This should never happen! - */ - log.error(e); - } - - return baos.toByteArray(); - } - - /** - * Decodes an encoded word encoded with the 'B' encoding (described in - * RFC 2047) found in a header field body. - * - * @param encodedWord the encoded word to decode. - * @param charset the Java charset to use. - * @return the decoded string. - * @throws UnsupportedEncodingException if the given Java charset isn't - * supported. - */ - public static String decodeB(String encodedWord, String charset) - throws UnsupportedEncodingException { - - return new String(decodeBase64(encodedWord), charset); - } - - /** - * Decodes an encoded word encoded with the 'Q' encoding (described in - * RFC 2047) found in a header field body. - * - * @param encodedWord the encoded word to decode. - * @param charset the Java charset to use. - * @return the decoded string. - * @throws UnsupportedEncodingException if the given Java charset isn't - * supported. - */ - public static String decodeQ(String encodedWord, String charset) - throws UnsupportedEncodingException { - - /* - * Replace _ with =20 - */ - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < encodedWord.length(); i++) { - char c = encodedWord.charAt(i); - if (c == '_') { - sb.append("=20"); - } else { - sb.append(c); - } - } - - return new String(decodeBaseQuotedPrintable(sb.toString()), charset); - } - - /** - * Decodes a string containing encoded words as defined by RFC 2047. - * Encoded words in have the form - * =?charset?enc?Encoded word?= where enc is either 'Q' or 'q' for - * quoted-printable and 'B' or 'b' for Base64. - * - * ANDROID: COPIED FROM A NEWER VERSION OF MIME4J - * - * @param body the string to decode. - * @return the decoded string. - */ - public static String decodeEncodedWords(String body) { - - // ANDROID: Most strings will not include "=?" so a quick test can prevent unneeded - // object creation. This could also be handled via lazy creation of the StringBuilder. - if (body.indexOf("=?") == -1) { - return body; - } - - int previousEnd = 0; - boolean previousWasEncoded = false; - - StringBuilder sb = new StringBuilder(); - - while (true) { - int begin = body.indexOf("=?", previousEnd); - - // ANDROID: The mime4j original version has an error here. It gets confused if - // the encoded string begins with an '=' (just after "?Q?"). This patch seeks forward - // to find the two '?' in the "header", before looking for the final "?=". - int endScan = begin + 2; - if (begin != -1) { - int qm1 = body.indexOf('?', endScan + 2); - int qm2 = body.indexOf('?', qm1 + 1); - if (qm2 != -1) { - endScan = qm2 + 1; - } - } - - int end = begin == -1 ? -1 : body.indexOf("?=", endScan); - if (end == -1) { - if (previousEnd == 0) - return body; - - sb.append(body.substring(previousEnd)); - return sb.toString(); - } - end += 2; - - String sep = body.substring(previousEnd, begin); - - String decoded = decodeEncodedWord(body, begin, end); - if (decoded == null) { - sb.append(sep); - sb.append(body.substring(begin, end)); - } else { - if (!previousWasEncoded || !CharsetUtil.isWhitespace(sep)) { - sb.append(sep); - } - sb.append(decoded); - } - - previousEnd = end; - previousWasEncoded = decoded != null; - } - } - - // return null on error - private static String decodeEncodedWord(String body, int begin, int end) { - int qm1 = body.indexOf('?', begin + 2); - if (qm1 == end - 2) - return null; - - int qm2 = body.indexOf('?', qm1 + 1); - if (qm2 == end - 2) - return null; - - String mimeCharset = body.substring(begin + 2, qm1); - String encoding = body.substring(qm1 + 1, qm2); - String encodedText = body.substring(qm2 + 1, end - 2); - - String charset = CharsetUtil.toJavaCharset(mimeCharset); - if (charset == null) { - if (log.isWarnEnabled()) { - log.warn("MIME charset '" + mimeCharset + "' in encoded word '" - + body.substring(begin, end) + "' doesn't have a " - + "corresponding Java charset"); - } - return null; - } else if (!CharsetUtil.isDecodingSupported(charset)) { - if (log.isWarnEnabled()) { - log.warn("Current JDK doesn't support decoding of charset '" - + charset + "' (MIME charset '" + mimeCharset - + "' in encoded word '" + body.substring(begin, end) - + "')"); - } - return null; - } - - if (encodedText.length() == 0) { - if (log.isWarnEnabled()) { - log.warn("Missing encoded text in encoded word: '" - + body.substring(begin, end) + "'"); - } - return null; - } - - try { - if (encoding.equalsIgnoreCase("Q")) { - return DecoderUtil.decodeQ(encodedText, charset); - } else if (encoding.equalsIgnoreCase("B")) { - return DecoderUtil.decodeB(encodedText, charset); - } else { - if (log.isWarnEnabled()) { - log.warn("Warning: Unknown encoding in encoded word '" - + body.substring(begin, end) + "'"); - } - return null; - } - } catch (UnsupportedEncodingException e) { - // should not happen because of isDecodingSupported check above - if (log.isWarnEnabled()) { - log.warn("Unsupported encoding in encoded word '" - + body.substring(begin, end) + "'", e); - } - return null; - } catch (RuntimeException e) { - if (log.isWarnEnabled()) { - log.warn("Could not decode encoded word '" - + body.substring(begin, end) + "'", e); - } - return null; - } - } -} diff --git a/src/org/apache/james/mime4j/decoder/QuotedPrintableInputStream.java b/src/org/apache/james/mime4j/decoder/QuotedPrintableInputStream.java deleted file mode 100644 index 3a3a1a451..000000000 --- a/src/org/apache/james/mime4j/decoder/QuotedPrintableInputStream.java +++ /dev/null @@ -1,227 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.decoder; - -import java.io.IOException; -import java.io.InputStream; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Performs Quoted-Printable decoding on an underlying stream. - * - * - * - * @version $Id: QuotedPrintableInputStream.java,v 1.3 2004/11/29 13:15:47 ntherning Exp $ - */ -public class QuotedPrintableInputStream extends InputStream { - private static Log log = LogFactory.getLog(QuotedPrintableInputStream.class); - - private InputStream stream; - ByteQueue byteq = new ByteQueue(); - ByteQueue pushbackq = new ByteQueue(); - private byte state = 0; - - public QuotedPrintableInputStream(InputStream stream) { - this.stream = stream; - } - - /** - * Closes the underlying stream. - * - * @throws IOException on I/O errors. - */ - public void close() throws IOException { - stream.close(); - } - - public int read() throws IOException { - fillBuffer(); - if (byteq.count() == 0) - return -1; - else { - byte val = byteq.dequeue(); - if (val >= 0) - return val; - else - return val & 0xFF; - } - } - - /** - * Pulls bytes out of the underlying stream and places them in the - * pushback queue. This is necessary (vs. reading from the - * underlying stream directly) to detect and filter out "transport - * padding" whitespace, i.e., all whitespace that appears immediately - * before a CRLF. - * - * @throws IOException Underlying stream threw IOException. - */ - private void populatePushbackQueue() throws IOException { - //Debug.verify(pushbackq.count() == 0, "PopulatePushbackQueue called when pushback queue was not empty!"); - - if (pushbackq.count() != 0) - return; - - while (true) { - int i = stream.read(); - switch (i) { - case -1: - // stream is done - pushbackq.clear(); // discard any whitespace preceding EOF - return; - case ' ': - case '\t': - pushbackq.enqueue((byte)i); - break; - case '\r': - case '\n': - pushbackq.clear(); // discard any whitespace preceding EOL - pushbackq.enqueue((byte)i); - return; - default: - pushbackq.enqueue((byte)i); - return; - } - } - } - - /** - * Causes the pushback queue to get populated if it is empty, then - * consumes and decodes bytes out of it until one or more bytes are - * in the byte queue. This decoding step performs the actual QP - * decoding. - * - * @throws IOException Underlying stream threw IOException. - */ - private void fillBuffer() throws IOException { - byte msdChar = 0; // first digit of escaped num - while (byteq.count() == 0) { - if (pushbackq.count() == 0) { - populatePushbackQueue(); - if (pushbackq.count() == 0) - return; - } - - byte b = (byte)pushbackq.dequeue(); - - switch (state) { - case 0: // start state, no bytes pending - if (b != '=') { - byteq.enqueue(b); - break; // state remains 0 - } else { - state = 1; - break; - } - case 1: // encountered "=" so far - if (b == '\r') { - state = 2; - break; - } else if ((b >= '0' && b <= '9') || (b >= 'A' && b <= 'F') || (b >= 'a' && b <= 'f')) { - state = 3; - msdChar = b; // save until next digit encountered - break; - } else if (b == '=') { - /* - * Special case when == is encountered. - * Emit one = and stay in this state. - */ - if (log.isWarnEnabled()) { - log.warn("Malformed MIME; got =="); - } - byteq.enqueue((byte)'='); - break; - } else { - if (log.isWarnEnabled()) { - log.warn("Malformed MIME; expected \\r or " - + "[0-9A-Z], got " + b); - } - state = 0; - byteq.enqueue((byte)'='); - byteq.enqueue(b); - break; - } - case 2: // encountered "=\r" so far - if (b == '\n') { - state = 0; - break; - } else { - if (log.isWarnEnabled()) { - log.warn("Malformed MIME; expected " - + (int)'\n' + ", got " + b); - } - state = 0; - byteq.enqueue((byte)'='); - byteq.enqueue((byte)'\r'); - byteq.enqueue(b); - break; - } - case 3: // encountered = so far; expecting another to complete the octet - if ((b >= '0' && b <= '9') || (b >= 'A' && b <= 'F') || (b >= 'a' && b <= 'f')) { - byte msd = asciiCharToNumericValue(msdChar); - byte low = asciiCharToNumericValue(b); - state = 0; - byteq.enqueue((byte)((msd << 4) | low)); - break; - } else { - if (log.isWarnEnabled()) { - log.warn("Malformed MIME; expected " - + "[0-9A-Z], got " + b); - } - state = 0; - byteq.enqueue((byte)'='); - byteq.enqueue(msdChar); - byteq.enqueue(b); - break; - } - default: // should never happen - log.error("Illegal state: " + state); - state = 0; - byteq.enqueue(b); - break; - } - } - } - - /** - * Converts '0' => 0, 'A' => 10, etc. - * @param c ASCII character value. - * @return Numeric value of hexadecimal character. - */ - private byte asciiCharToNumericValue(byte c) { - if (c >= '0' && c <= '9') { - return (byte)(c - '0'); - } else if (c >= 'A' && c <= 'Z') { - return (byte)(0xA + (c - 'A')); - } else if (c >= 'a' && c <= 'z') { - return (byte)(0xA + (c - 'a')); - } else { - /* - * This should never happen since all calls to this method - * are preceded by a check that c is in [0-9A-Za-z] - */ - throw new IllegalArgumentException((char) c - + " is not a hexadecimal digit"); - } - } - -} diff --git a/src/org/apache/james/mime4j/decoder/UnboundedFifoByteBuffer.java b/src/org/apache/james/mime4j/decoder/UnboundedFifoByteBuffer.java deleted file mode 100644 index f01194fd1..000000000 --- a/src/org/apache/james/mime4j/decoder/UnboundedFifoByteBuffer.java +++ /dev/null @@ -1,272 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.decoder; - -import java.util.Iterator; -import java.util.NoSuchElementException; - -/** - * UnboundedFifoByteBuffer is a very efficient buffer implementation. - * According to performance testing, it exhibits a constant access time, but it - * also outperforms ArrayList when used for the same purpose. - *

- * The removal order of an UnboundedFifoByteBuffer is based on the insertion - * order; elements are removed in the same order in which they were added. - * The iteration order is the same as the removal order. - *

- * The {@link #remove()} and {@link #get()} operations perform in constant time. - * The {@link #add(Object)} operation performs in amortized constant time. All - * other operations perform in linear time or worse. - *

- * Note that this implementation is not synchronized. The following can be - * used to provide synchronized access to your UnboundedFifoByteBuffer: - *

- *   Buffer fifo = BufferUtils.synchronizedBuffer(new UnboundedFifoByteBuffer());
- * 
- *

- * This buffer prevents null objects from being added. - * - * @since Commons Collections 3.0 (previously in main package v2.1) - * @version $Revision: 1.1 $ $Date: 2004/08/24 06:52:02 $ - * - * - * - * - * - * - */ -class UnboundedFifoByteBuffer { - - protected byte[] buffer; - protected int head; - protected int tail; - - /** - * Constructs an UnboundedFifoByteBuffer with the default number of elements. - * It is exactly the same as performing the following: - * - *

-     *   new UnboundedFifoByteBuffer(32);
-     * 
- */ - public UnboundedFifoByteBuffer() { - this(32); - } - - /** - * Constructs an UnboundedFifoByteBuffer with the specified number of elements. - * The integer must be a positive integer. - * - * @param initialSize the initial size of the buffer - * @throws IllegalArgumentException if the size is less than 1 - */ - public UnboundedFifoByteBuffer(int initialSize) { - if (initialSize <= 0) { - throw new IllegalArgumentException("The size must be greater than 0"); - } - buffer = new byte[initialSize + 1]; - head = 0; - tail = 0; - } - - /** - * Returns the number of elements stored in the buffer. - * - * @return this buffer's size - */ - public int size() { - int size = 0; - - if (tail < head) { - size = buffer.length - head + tail; - } else { - size = tail - head; - } - - return size; - } - - /** - * Returns true if this buffer is empty; false otherwise. - * - * @return true if this buffer is empty - */ - public boolean isEmpty() { - return (size() == 0); - } - - /** - * Adds the given element to this buffer. - * - * @param b the byte to add - * @return true, always - */ - public boolean add(final byte b) { - - if (size() + 1 >= buffer.length) { - byte[] tmp = new byte[((buffer.length - 1) * 2) + 1]; - - int j = 0; - for (int i = head; i != tail;) { - tmp[j] = buffer[i]; - buffer[i] = 0; - - j++; - i++; - if (i == buffer.length) { - i = 0; - } - } - - buffer = tmp; - head = 0; - tail = j; - } - - buffer[tail] = b; - tail++; - if (tail >= buffer.length) { - tail = 0; - } - return true; - } - - /** - * Returns the next object in the buffer. - * - * @return the next object in the buffer - * @throws BufferUnderflowException if this buffer is empty - */ - public byte get() { - if (isEmpty()) { - throw new IllegalStateException("The buffer is already empty"); - } - - return buffer[head]; - } - - /** - * Removes the next object from the buffer - * - * @return the removed object - * @throws BufferUnderflowException if this buffer is empty - */ - public byte remove() { - if (isEmpty()) { - throw new IllegalStateException("The buffer is already empty"); - } - - byte element = buffer[head]; - - head++; - if (head >= buffer.length) { - head = 0; - } - - return element; - } - - /** - * Increments the internal index. - * - * @param index the index to increment - * @return the updated index - */ - private int increment(int index) { - index++; - if (index >= buffer.length) { - index = 0; - } - return index; - } - - /** - * Decrements the internal index. - * - * @param index the index to decrement - * @return the updated index - */ - private int decrement(int index) { - index--; - if (index < 0) { - index = buffer.length - 1; - } - return index; - } - - /** - * Returns an iterator over this buffer's elements. - * - * @return an iterator over this buffer's elements - */ - public Iterator iterator() { - return new Iterator() { - - private int index = head; - private int lastReturnedIndex = -1; - - public boolean hasNext() { - return index != tail; - - } - - public Object next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - lastReturnedIndex = index; - index = increment(index); - return new Byte(buffer[lastReturnedIndex]); - } - - public void remove() { - if (lastReturnedIndex == -1) { - throw new IllegalStateException(); - } - - // First element can be removed quickly - if (lastReturnedIndex == head) { - UnboundedFifoByteBuffer.this.remove(); - lastReturnedIndex = -1; - return; - } - - // Other elements require us to shift the subsequent elements - int i = lastReturnedIndex + 1; - while (i != tail) { - if (i >= buffer.length) { - buffer[i - 1] = buffer[0]; - i = 0; - } else { - buffer[i - 1] = buffer[i]; - i++; - } - } - - lastReturnedIndex = -1; - tail = decrement(tail); - buffer[tail] = 0; - index = decrement(index); - } - - }; - } - -} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/dom/BinaryBody.java b/src/org/apache/james/mime4j/dom/BinaryBody.java new file mode 100644 index 000000000..d9d20a4e2 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/BinaryBody.java @@ -0,0 +1,33 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom; + +/** + * A body containing binary data. + */ +public abstract class BinaryBody extends SingleBody { + + /** + * Sole constructor. + */ + protected BinaryBody() { + } + +} diff --git a/src/org/apache/james/mime4j/message/Body.java b/src/org/apache/james/mime4j/dom/Body.java similarity index 74% rename from src/org/apache/james/mime4j/message/Body.java rename to src/org/apache/james/mime4j/dom/Body.java index 5d9b38190..d1482830a 100644 --- a/src/org/apache/james/mime4j/message/Body.java +++ b/src/org/apache/james/mime4j/dom/Body.java @@ -17,38 +17,30 @@ * under the License. * ****************************************************************/ -package org.apache.james.mime4j.message; - -import java.io.IOException; -import java.io.OutputStream; +package org.apache.james.mime4j.dom; /** * Encapsulates the body of an entity (see RFC 2045). - * - * - * @version $Id: Body.java,v 1.4 2004/10/04 15:36:43 ntherning Exp $ + *

+ * A body can be a {@link Message}, a {@link Multipart} or a {@link SingleBody}. + * This interface should not be implemented directly by classes other than + * those. */ -public interface Body { +public interface Body extends Disposable { /** * Gets the parent of this body. - * + * * @return the parent. */ Entity getParent(); - + /** * Sets the parent of this body. - * - * @param parent the parent. + * + * @param parent + * the parent. */ void setParent(Entity parent); - - /** - * Writes this body to the given stream in MIME message format. - * - * @param out the stream to write to. - * @throws IOException on I/O errors. - */ - void writeTo(OutputStream out) throws IOException; + } diff --git a/src/org/apache/james/mime4j/dom/Disposable.java b/src/org/apache/james/mime4j/dom/Disposable.java new file mode 100644 index 000000000..5254f00fd --- /dev/null +++ b/src/org/apache/james/mime4j/dom/Disposable.java @@ -0,0 +1,36 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom; + +/** + * A Disposable is an object that should be disposed of explicitly + * when it is no longer needed. + * + * The dispose method is invoked to release resources that the object is + * holding (such as open files). + */ +public interface Disposable { + /** + * Free any resources this object is holding and prepares this object + * for garbage collection. Once an object has been disposed of it can no + * longer be used. + */ + void dispose(); +} diff --git a/src/org/apache/james/mime4j/dom/Entity.java b/src/org/apache/james/mime4j/dom/Entity.java new file mode 100644 index 000000000..92fe16562 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/Entity.java @@ -0,0 +1,550 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.apache.james.mime4j.dom.field.ContentDispositionField; +import org.apache.james.mime4j.dom.field.ContentTransferEncodingField; +import org.apache.james.mime4j.dom.field.ContentTypeField; +import org.apache.james.mime4j.dom.field.Field; +import org.apache.james.mime4j.dom.field.FieldName; + +/** + * MIME entity. An entity has a header and a body (see RFC 2045). + */ +public abstract class Entity implements Disposable { + private Header header = null; + private Body body = null; + private Entity parent = null; + + /** + * Creates a new Entity. Typically invoked implicitly by a + * subclass constructor. + */ + protected Entity() { + } + + /** + * Gets the parent entity of this entity. + * Returns null if this is the root entity. + * + * @return the parent or null. + */ + public Entity getParent() { + return parent; + } + + /** + * Sets the parent entity of this entity. + * + * @param parent the parent entity or null if + * this will be the root entity. + */ + public void setParent(Entity parent) { + this.parent = parent; + } + + /** + * Gets the entity header. + * + * @return the header. + */ + public Header getHeader() { + return header; + } + + /** + * Sets the entity header. + * + * @param header the header. + */ + public void setHeader(Header header) { + this.header = header; + } + + /** + * Gets the body of this entity. + * + * @return the body, + */ + public Body getBody() { + return body; + } + + /** + * Sets the body of this entity. + * + * @param body the body. + * @throws IllegalStateException if the body has already been set. + */ + public void setBody(Body body) { + if (this.body != null) + throw new IllegalStateException("body already set"); + + this.body = body; + body.setParent(this); + } + + /** + * Removes and returns the body of this entity. The removed body may be + * attached to another entity. If it is no longer needed it should be + * {@link Disposable#dispose() disposed} of. + * + * @return the removed body or null if no body was set. + */ + public Body removeBody() { + if (body == null) + return null; + + Body body = this.body; + this.body = null; + body.setParent(null); + + return body; + } + + /** + * Sets the specified message as body of this entity and the content type to + * "message/rfc822". A Header is created if this + * entity does not already have one. + * + * @param message + * the message to set as body. + */ + public void setMessage(Message message) { + setBody(message, "message/rfc822", null); + } + + /** + * Sets the specified multipart as body of this entity. Also sets the + * content type accordingly and creates a message boundary string. A + * Header is created if this entity does not already have + * one. + * + * @param multipart + * the multipart to set as body. + */ + public void setMultipart(Multipart multipart) { + String mimeType = "multipart/" + multipart.getSubType(); + Map parameters = Collections.singletonMap("boundary", + newUniqueBoundary()); + + setBody(multipart, mimeType, parameters); + } + + /** + * Sets the specified multipart as body of this entity. Also sets the + * content type accordingly and creates a message boundary string. A + * Header is created if this entity does not already have + * one. + * + * @param multipart + * the multipart to set as body. + * @param parameters + * additional parameters for the Content-Type header field. + */ + public void setMultipart(Multipart multipart, Map parameters) { + String mimeType = "multipart/" + multipart.getSubType(); + if (!parameters.containsKey("boundary")) { + parameters = new HashMap(parameters); + parameters.put("boundary", newUniqueBoundary()); + } + + setBody(multipart, mimeType, parameters); + } + + /** + * Sets the specified TextBody as body of this entity and the + * content type to "text/plain". A Header is + * created if this entity does not already have one. + * + * @param textBody + * the TextBody to set as body. + * @see org.apache.james.mime4j.message.BodyFactory#textBody(String) + */ + public void setText(TextBody textBody) { + setText(textBody, "plain"); + } + + /** + * Sets the specified TextBody as body of this entity. Also + * sets the content type according to the specified sub-type. A + * Header is created if this entity does not already have + * one. + * + * @param textBody + * the TextBody to set as body. + * @param subtype + * the text subtype (e.g. "plain", "html" or + * "xml"). + * @see org.apache.james.mime4j.message.BodyFactory#textBody(String) + */ + public void setText(TextBody textBody, String subtype) { + String mimeType = "text/" + subtype; + + Map parameters = null; + String mimeCharset = textBody.getMimeCharset(); + if (mimeCharset != null && !mimeCharset.equalsIgnoreCase("us-ascii")) { + parameters = Collections.singletonMap("charset", mimeCharset); + } + + setBody(textBody, mimeType, parameters); + } + + /** + * Sets the body of this entity and sets the content-type to the specified + * value. A Header is created if this entity does not already + * have one. + * + * @param body + * the body. + * @param mimeType + * the MIME media type of the specified body + * ("type/subtype"). + */ + public void setBody(Body body, String mimeType) { + setBody(body, mimeType, null); + } + + /** + * Sets the body of this entity and sets the content-type to the specified + * value. A Header is created if this entity does not already + * have one. + * + * @param body + * the body. + * @param mimeType + * the MIME media type of the specified body + * ("type/subtype"). + * @param parameters + * additional parameters for the Content-Type header field. + */ + public void setBody(Body body, String mimeType, + Map parameters) { + setBody(body); + + Header header = obtainHeader(); + header.setField(newContentType(mimeType, parameters)); + } + + /** + * Determines the MIME type of this Entity. The MIME type + * is derived by looking at the parent's Content-Type field if no + * Content-Type field is set for this Entity. + * + * @return the MIME type. + */ + public String getMimeType() { + ContentTypeField child = + getContentTypeField(); + ContentTypeField parent = getParent() != null + ? (ContentTypeField) getParent().getHeader(). + getField(FieldName.CONTENT_TYPE) + : null; + + return calcMimeType(child, parent); + } + + private ContentTypeField getContentTypeField() { + return (ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE); + } + + /** + * Determines the MIME character set encoding of this Entity. + * + * @return the MIME character set encoding. + */ + public String getCharset() { + return calcCharset((ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE)); + } + + /** + * Determines the transfer encoding of this Entity. + * + * @return the transfer encoding. + */ + public String getContentTransferEncoding() { + ContentTransferEncodingField f = (ContentTransferEncodingField) + getHeader().getField(FieldName.CONTENT_TRANSFER_ENCODING); + + return calcTransferEncoding(f); + } + + /** + * Sets the transfer encoding of this Entity to the specified + * value. + * + * @param contentTransferEncoding + * transfer encoding to use. + */ + public void setContentTransferEncoding(String contentTransferEncoding) { + Header header = obtainHeader(); + header.setField(newContentTransferEncoding(contentTransferEncoding)); + } + + /** + * Return the disposition type of the content disposition of this + * Entity. + * + * @return the disposition type or null if no disposition + * type has been set. + */ + public String getDispositionType() { + ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); + if (field == null) + return null; + + return field.getDispositionType(); + } + + /** + * Sets the content disposition of this Entity to the + * specified disposition type. No filename, size or date parameters + * are included in the content disposition. + * + * @param dispositionType + * disposition type value (usually inline or + * attachment). + */ + public void setContentDisposition(String dispositionType) { + Header header = obtainHeader(); + header.setField(newContentDisposition(dispositionType, null, -1, null, + null, null)); + } + + /** + * Sets the content disposition of this Entity to the + * specified disposition type and filename. No size or date parameters are + * included in the content disposition. + * + * @param dispositionType + * disposition type value (usually inline or + * attachment). + * @param filename + * filename parameter value or null if the + * parameter should not be included. + */ + public void setContentDisposition(String dispositionType, String filename) { + Header header = obtainHeader(); + header.setField(newContentDisposition(dispositionType, filename, -1, + null, null, null)); + } + + /** + * Sets the content disposition of this Entity to the + * specified values. No date parameters are included in the content + * disposition. + * + * @param dispositionType + * disposition type value (usually inline or + * attachment). + * @param filename + * filename parameter value or null if the + * parameter should not be included. + * @param size + * size parameter value or -1 if the parameter + * should not be included. + */ + public void setContentDisposition(String dispositionType, String filename, + long size) { + Header header = obtainHeader(); + header.setField(newContentDisposition(dispositionType, filename, size, + null, null, null)); + } + + /** + * Sets the content disposition of this Entity to the + * specified values. + * + * @param dispositionType + * disposition type value (usually inline or + * attachment). + * @param filename + * filename parameter value or null if the + * parameter should not be included. + * @param size + * size parameter value or -1 if the parameter + * should not be included. + * @param creationDate + * creation-date parameter value or null if the + * parameter should not be included. + * @param modificationDate + * modification-date parameter value or null if + * the parameter should not be included. + * @param readDate + * read-date parameter value or null if the + * parameter should not be included. + */ + public void setContentDisposition(String dispositionType, String filename, + long size, Date creationDate, Date modificationDate, Date readDate) { + Header header = obtainHeader(); + header.setField(newContentDisposition(dispositionType, filename, size, + creationDate, modificationDate, readDate)); + } + + /** + * Returns the filename parameter of the content disposition of this + * Entity. + * + * @return the filename parameter of the content disposition or + * null if the filename has not been set. + */ + public String getFilename() { + ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); + if (field == null) + return null; + + return field.getFilename(); + } + + /** + * Sets the filename parameter of the content disposition of this + * Entity to the specified value. If this entity does not + * have a content disposition header field a new one with disposition type + * attachment is created. + * + * @param filename + * filename parameter value or null if the + * parameter should be removed. + */ + public void setFilename(String filename) { + Header header = obtainHeader(); + ContentDispositionField field = (ContentDispositionField) header + .getField(FieldName.CONTENT_DISPOSITION); + if (field == null) { + if (filename != null) { + header.setField(newContentDisposition( + ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT, + filename, -1, null, null, null)); + } + } else { + String dispositionType = field.getDispositionType(); + Map parameters = new HashMap(field + .getParameters()); + if (filename == null) { + parameters.remove(ContentDispositionField.PARAM_FILENAME); + } else { + parameters + .put(ContentDispositionField.PARAM_FILENAME, filename); + } + header.setField(newContentDisposition(dispositionType, parameters)); + } + } + + /** + * Determines if the MIME type of this Entity matches the + * given one. MIME types are case-insensitive. + * + * @param type the MIME type to match against. + * @return true on match, false otherwise. + */ + public boolean isMimeType(String type) { + return getMimeType().equalsIgnoreCase(type); + } + + /** + * Determines if the MIME type of this Entity is + * multipart/*. Since multipart-entities must have + * a boundary parameter in the Content-Type field this + * method returns false if no boundary exists. + * + * @return true on match, false otherwise. + */ + public boolean isMultipart() { + ContentTypeField f = getContentTypeField(); + return f != null + && f.getBoundary() != null + && getMimeType().startsWith( + ContentTypeField.TYPE_MULTIPART_PREFIX); + } + + /** + * Disposes of the body of this entity. Note that the dispose call does not + * get forwarded to the parent entity of this Entity. + * + * Subclasses that need to free resources should override this method and + * invoke super.dispose(). + * + * @see org.apache.james.mime4j.dom.Disposable#dispose() + */ + public void dispose() { + if (body != null) { + body.dispose(); + } + } + + /** + * Obtains the header of this entity. Creates and sets a new header if this + * entity's header is currently null. + * + * @return the header of this entity; never null. + */ + Header obtainHeader() { + if (header == null) { + header = new Header(); + } + return header; + } + + /** + * Obtains the header field with the specified name. + * + * @param + * concrete field type. + * @param fieldName + * name of the field to retrieve. + * @return the header field or null if this entity has no + * header or the header contains no such field. + */ + F obtainField(String fieldName) { + Header header = getHeader(); + if (header == null) + return null; + + @SuppressWarnings("unchecked") + F field = (F) header.getField(fieldName); + return field; + } + + protected abstract String newUniqueBoundary(); + + protected abstract ContentDispositionField newContentDisposition( + String dispositionType, String filename, long size, + Date creationDate, Date modificationDate, Date readDate); + + protected abstract ContentDispositionField newContentDisposition( + String dispositionType, Map parameters); + + protected abstract ContentTypeField newContentType(String mimeType, + Map parameters); + + protected abstract ContentTransferEncodingField newContentTransferEncoding( + String contentTransferEncoding); + + protected abstract String calcMimeType(ContentTypeField child, ContentTypeField parent); + + protected abstract String calcTransferEncoding(ContentTransferEncodingField f); + + protected abstract String calcCharset(ContentTypeField contentType); +} diff --git a/src/org/apache/james/mime4j/dom/Header.java b/src/org/apache/james/mime4j/dom/Header.java new file mode 100644 index 000000000..9c134bf80 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/Header.java @@ -0,0 +1,204 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.james.mime4j.dom.field.Field; + +/** + * The header of an entity (see RFC 2045). + */ +public class Header implements Iterable { + + private List fields = new LinkedList(); + private Map> fieldMap = new HashMap>(); + + /** + * Creates a new empty Header. + */ + public Header() { + } + + /** + * Creates a new Header from the specified + * Header. The Header instance is initialized + * with a copy of the list of {@link Field}s of the specified + * Header. The Field objects are not copied + * because they are immutable and can safely be shared between headers. + * + * @param other + * header to copy. + */ + public Header(Header other) { + for (Field otherField : other.fields) { + addField(otherField); + } + } + + /** + * Adds a field to the end of the list of fields. + * + * @param field the field to add. + */ + public void addField(Field field) { + List values = fieldMap.get(field.getName().toLowerCase()); + if (values == null) { + values = new LinkedList(); + fieldMap.put(field.getName().toLowerCase(), values); + } + values.add(field); + fields.add(field); + } + + /** + * Gets the fields of this header. The returned list will not be + * modifiable. + * + * @return the list of Field objects. + */ + public List getFields() { + return Collections.unmodifiableList(fields); + } + + /** + * Gets a Field given a field name. If there are multiple + * such fields defined in this header the first one will be returned. + * + * @param name the field name (e.g. From, Subject). + * @return the field or null if none found. + */ + public Field getField(String name) { + List l = fieldMap.get(name.toLowerCase()); + if (l != null && !l.isEmpty()) { + return l.get(0); + } + return null; + } + + /** + * Gets all Fields having the specified field name. + * + * @param name the field name (e.g. From, Subject). + * @return the list of fields. + */ + public List getFields(final String name) { + final String lowerCaseName = name.toLowerCase(); + final List l = fieldMap.get(lowerCaseName); + final List results; + if (l == null || l.isEmpty()) { + results = Collections.emptyList(); + } else { + results = Collections.unmodifiableList(l); + } + return results; + } + + /** + * Returns an iterator over the list of fields of this header. + * + * @return an iterator. + */ + public Iterator iterator() { + return Collections.unmodifiableList(fields).iterator(); + } + + /** + * Removes all Fields having the specified field name. + * + * @param name + * the field name (e.g. From, Subject). + * @return number of fields removed. + */ + public int removeFields(String name) { + final String lowerCaseName = name.toLowerCase(); + List removed = fieldMap.remove(lowerCaseName); + if (removed == null || removed.isEmpty()) + return 0; + + for (Iterator iterator = fields.iterator(); iterator.hasNext();) { + Field field = iterator.next(); + if (field.getName().equalsIgnoreCase(name)) + iterator.remove(); + } + + return removed.size(); + } + + /** + * Sets or replaces a field. This method is useful for header fields such as + * Subject or Message-ID that should not occur more than once in a message. + * + * If this Header does not already contain a header field of + * the same name as the given field then it is added to the end of the list + * of fields (same behavior as {@link #addField(Field)}). Otherwise the + * first occurrence of a field with the same name is replaced by the given + * field and all further occurrences are removed. + * + * @param field the field to set. + */ + public void setField(Field field) { + final String lowerCaseName = field.getName().toLowerCase(); + List l = fieldMap.get(lowerCaseName); + if (l == null || l.isEmpty()) { + addField(field); + return; + } + + l.clear(); + l.add(field); + + int firstOccurrence = -1; + int index = 0; + for (Iterator iterator = fields.iterator(); iterator.hasNext(); index++) { + Field f = iterator.next(); + if (f.getName().equalsIgnoreCase(field.getName())) { + iterator.remove(); + + if (firstOccurrence == -1) + firstOccurrence = index; + } + } + + fields.add(firstOccurrence, field); + } + + /** + * Return Header Object as String representation. Each headerline is + * seperated by "\r\n" + * + * @return headers + */ + @Override + public String toString() { + StringBuilder str = new StringBuilder(128); + for (Field field : fields) { + str.append(field.toString()); + str.append("\r\n"); + } + return str.toString(); + } + +} diff --git a/src/org/apache/james/mime4j/dom/Message.java b/src/org/apache/james/mime4j/dom/Message.java new file mode 100644 index 000000000..35fe2e80e --- /dev/null +++ b/src/org/apache/james/mime4j/dom/Message.java @@ -0,0 +1,512 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.TimeZone; + +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.AddressList; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.address.MailboxList; +import org.apache.james.mime4j.dom.field.AddressListField; +import org.apache.james.mime4j.dom.field.DateTimeField; +import org.apache.james.mime4j.dom.field.Field; +import org.apache.james.mime4j.dom.field.FieldName; +import org.apache.james.mime4j.dom.field.MailboxField; +import org.apache.james.mime4j.dom.field.MailboxListField; +import org.apache.james.mime4j.dom.field.UnstructuredField; + +public abstract class Message extends Entity implements Body { + + /** + * Write the content to the given output stream using the + * {@link org.apache.james.mime4j.message.MessageWriter#DEFAULT default} message writer. + * + * @param out + * the output stream to write to. + * @throws IOException + * in case of an I/O error + * @see org.apache.james.mime4j.message.MessageWriter + */ + public abstract void writeTo(OutputStream out) throws IOException; + + /** + * Returns the value of the Message-ID header field of this message + * or null if it is not present. + * + * @return the identifier of this message. + */ + public String getMessageId() { + Field field = obtainField(FieldName.MESSAGE_ID); + if (field == null) + return null; + + return field.getBody(); + } + + /** + * Creates and sets a new Message-ID header field for this message. + * A Header is created if this message does not already have + * one. + * + * @param hostname + * host name to be included in the identifier or + * null if no host name should be included. + */ + public void createMessageId(String hostname) { + Header header = obtainHeader(); + + header.setField(newMessageId(hostname)); + } + + protected abstract Field newMessageId(String hostname); + + /** + * Returns the (decoded) value of the Subject header field of this + * message or null if it is not present. + * + * @return the subject of this message. + */ + public String getSubject() { + UnstructuredField field = obtainField(FieldName.SUBJECT); + if (field == null) + return null; + + return field.getValue(); + } + + /** + * Sets the Subject header field for this message. The specified + * string may contain non-ASCII characters, in which case it gets encoded as + * an 'encoded-word' automatically. A Header is created if + * this message does not already have one. + * + * @param subject + * subject to set or null to remove the subject + * header field. + */ + public void setSubject(String subject) { + Header header = obtainHeader(); + + if (subject == null) { + header.removeFields(FieldName.SUBJECT); + } else { + header.setField(newSubject(subject)); + } + } + + /** + * Returns the value of the Date header field of this message as + * Date object or null if it is not present. + * + * @return the date of this message. + */ + public Date getDate() { + DateTimeField dateField = obtainField(FieldName.DATE); + if (dateField == null) + return null; + + return dateField.getDate(); + } + + /** + * Sets the Date header field for this message. This method uses the + * default TimeZone of this host to encode the specified + * Date object into a string. + * + * @param date + * date to set or null to remove the date header + * field. + */ + public void setDate(Date date) { + setDate(date, null); + } + + /** + * Sets the Date header field for this message. The specified + * TimeZone is used to encode the specified Date + * object into a string. + * + * @param date + * date to set or null to remove the date header + * field. + * @param zone + * a time zone. + */ + public void setDate(Date date, TimeZone zone) { + Header header = obtainHeader(); + + if (date == null) { + header.removeFields(FieldName.DATE); + } else { + header.setField(newDate(date, zone)); + } + } + + /** + * Returns the value of the Sender header field of this message as + * Mailbox object or null if it is not + * present. + * + * @return the sender of this message. + */ + public Mailbox getSender() { + return getMailbox(FieldName.SENDER); + } + + /** + * Sets the Sender header field of this message to the specified + * mailbox address. + * + * @param sender + * address to set or null to remove the header + * field. + */ + public void setSender(Mailbox sender) { + setMailbox(FieldName.SENDER, sender); + } + + /** + * Returns the value of the From header field of this message as + * MailboxList object or null if it is not + * present. + * + * @return value of the from field of this message. + */ + public MailboxList getFrom() { + return getMailboxList(FieldName.FROM); + } + + /** + * Sets the From header field of this message to the specified + * mailbox address. + * + * @param from + * address to set or null to remove the header + * field. + */ + public void setFrom(Mailbox from) { + setMailboxList(FieldName.FROM, from); + } + + /** + * Sets the From header field of this message to the specified + * mailbox addresses. + * + * @param from + * addresses to set or null or no arguments to + * remove the header field. + */ + public void setFrom(Mailbox... from) { + setMailboxList(FieldName.FROM, from); + } + + /** + * Sets the From header field of this message to the specified + * mailbox addresses. + * + * @param from + * addresses to set or null or an empty collection + * to remove the header field. + */ + public void setFrom(Collection from) { + setMailboxList(FieldName.FROM, from); + } + + /** + * Returns the value of the To header field of this message as + * AddressList object or null if it is not + * present. + * + * @return value of the to field of this message. + */ + public AddressList getTo() { + return getAddressList(FieldName.TO); + } + + /** + * Sets the To header field of this message to the specified + * address. + * + * @param to + * address to set or null to remove the header + * field. + */ + public void setTo(Address to) { + setAddressList(FieldName.TO, to); + } + + /** + * Sets the To header field of this message to the specified + * addresses. + * + * @param to + * addresses to set or null or no arguments to + * remove the header field. + */ + public void setTo(Address... to) { + setAddressList(FieldName.TO, to); + } + + /** + * Sets the To header field of this message to the specified + * addresses. + * + * @param to + * addresses to set or null or an empty collection + * to remove the header field. + */ + public void setTo(Collection

to) { + setAddressList(FieldName.TO, to); + } + + /** + * Returns the value of the Cc header field of this message as + * AddressList object or null if it is not + * present. + * + * @return value of the cc field of this message. + */ + public AddressList getCc() { + return getAddressList(FieldName.CC); + } + + /** + * Sets the Cc header field of this message to the specified + * address. + * + * @param cc + * address to set or null to remove the header + * field. + */ + public void setCc(Address cc) { + setAddressList(FieldName.CC, cc); + } + + /** + * Sets the Cc header field of this message to the specified + * addresses. + * + * @param cc + * addresses to set or null or no arguments to + * remove the header field. + */ + public void setCc(Address... cc) { + setAddressList(FieldName.CC, cc); + } + + /** + * Sets the Cc header field of this message to the specified + * addresses. + * + * @param cc + * addresses to set or null or an empty collection + * to remove the header field. + */ + public void setCc(Collection
cc) { + setAddressList(FieldName.CC, cc); + } + + /** + * Returns the value of the Bcc header field of this message as + * AddressList object or null if it is not + * present. + * + * @return value of the bcc field of this message. + */ + public AddressList getBcc() { + return getAddressList(FieldName.BCC); + } + + /** + * Sets the Bcc header field of this message to the specified + * address. + * + * @param bcc + * address to set or null to remove the header + * field. + */ + public void setBcc(Address bcc) { + setAddressList(FieldName.BCC, bcc); + } + + /** + * Sets the Bcc header field of this message to the specified + * addresses. + * + * @param bcc + * addresses to set or null or no arguments to + * remove the header field. + */ + public void setBcc(Address... bcc) { + setAddressList(FieldName.BCC, bcc); + } + + /** + * Sets the Bcc header field of this message to the specified + * addresses. + * + * @param bcc + * addresses to set or null or an empty collection + * to remove the header field. + */ + public void setBcc(Collection
bcc) { + setAddressList(FieldName.BCC, bcc); + } + + /** + * Returns the value of the Reply-To header field of this message as + * AddressList object or null if it is not + * present. + * + * @return value of the reply to field of this message. + */ + public AddressList getReplyTo() { + return getAddressList(FieldName.REPLY_TO); + } + + /** + * Sets the Reply-To header field of this message to the specified + * address. + * + * @param replyTo + * address to set or null to remove the header + * field. + */ + public void setReplyTo(Address replyTo) { + setAddressList(FieldName.REPLY_TO, replyTo); + } + + /** + * Sets the Reply-To header field of this message to the specified + * addresses. + * + * @param replyTo + * addresses to set or null or no arguments to + * remove the header field. + */ + public void setReplyTo(Address... replyTo) { + setAddressList(FieldName.REPLY_TO, replyTo); + } + + /** + * Sets the Reply-To header field of this message to the specified + * addresses. + * + * @param replyTo + * addresses to set or null or an empty collection + * to remove the header field. + */ + public void setReplyTo(Collection
replyTo) { + setAddressList(FieldName.REPLY_TO, replyTo); + } + + private Mailbox getMailbox(String fieldName) { + MailboxField field = obtainField(fieldName); + if (field == null) + return null; + + return field.getMailbox(); + } + + private void setMailbox(String fieldName, Mailbox mailbox) { + Header header = obtainHeader(); + + if (mailbox == null) { + header.removeFields(fieldName); + } else { + header.setField(newMailbox(fieldName, mailbox)); + } + } + + private MailboxList getMailboxList(String fieldName) { + MailboxListField field = obtainField(fieldName); + if (field == null) + return null; + + return field.getMailboxList(); + } + + private void setMailboxList(String fieldName, Mailbox mailbox) { + setMailboxList(fieldName, mailbox == null ? null : Collections + .singleton(mailbox)); + } + + private void setMailboxList(String fieldName, Mailbox... mailboxes) { + setMailboxList(fieldName, mailboxes == null ? null : Arrays + .asList(mailboxes)); + } + + private void setMailboxList(String fieldName, Collection mailboxes) { + Header header = obtainHeader(); + + if (mailboxes == null || mailboxes.isEmpty()) { + header.removeFields(fieldName); + } else { + header.setField(newMailboxList(fieldName, mailboxes)); + } + } + + private AddressList getAddressList(String fieldName) { + AddressListField field = obtainField(fieldName); + if (field == null) + return null; + + return field.getAddressList(); + } + + private void setAddressList(String fieldName, Address address) { + setAddressList(fieldName, address == null ? null : Collections + .singleton(address)); + } + + private void setAddressList(String fieldName, Address... addresses) { + setAddressList(fieldName, addresses == null ? null : Arrays + .asList(addresses)); + } + + private void setAddressList(String fieldName, Collection
addresses) { + Header header = obtainHeader(); + + if (addresses == null || addresses.isEmpty()) { + header.removeFields(fieldName); + } else { + header.setField(newAddressList(fieldName, addresses)); + } + } + + protected abstract AddressListField newAddressList(String fieldName, Collection
addresses); + + protected abstract UnstructuredField newSubject(String subject); + + protected abstract DateTimeField newDate(Date date, TimeZone zone); + + protected abstract MailboxField newMailbox(String fieldName, Mailbox mailbox); + + protected abstract MailboxListField newMailboxList(String fieldName, Collection mailboxes); + + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/dom/MessageBuilder.java b/src/org/apache/james/mime4j/dom/MessageBuilder.java new file mode 100644 index 000000000..a8affa6a1 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/MessageBuilder.java @@ -0,0 +1,46 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.codec.DecodeMonitor; + +/** + * Defines the API to obtain Message instances from a mime stream. + */ +public abstract class MessageBuilder { + + public abstract Message newMessage(); + + public abstract Message newMessage(Message source); + + public abstract Message parse(InputStream source) throws MimeException, IOException; + + public abstract void setDecodeMonitor( + DecodeMonitor decodeMonitor); + + public abstract void setContentDecoding(boolean contentDecoding); + + public abstract void setFlatMode(); + +} diff --git a/src/org/apache/james/mime4j/dom/MessageBuilderFactory.java b/src/org/apache/james/mime4j/dom/MessageBuilderFactory.java new file mode 100644 index 000000000..3c88c1cfc --- /dev/null +++ b/src/org/apache/james/mime4j/dom/MessageBuilderFactory.java @@ -0,0 +1,41 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom; + +import org.apache.james.mime4j.MimeException; + +/** + * A MessageBuilderFactory is used to create EntityBuilder instances. + * + * MessageBuilderFactory.newInstance() is used to get access to an implementation + * of MessageBuilderFactory. + * Then the method newMessageBuilder is used to create a new EntityBuilder object. + */ +public abstract class MessageBuilderFactory { + + public abstract MessageBuilder newMessageBuilder() throws MimeException; + + public static MessageBuilderFactory newInstance() throws MimeException { + return ServiceLoader.load(MessageBuilderFactory.class); + } + + public abstract void setAttribute(String name, Object value) throws IllegalArgumentException; + +} diff --git a/src/org/apache/james/mime4j/dom/Multipart.java b/src/org/apache/james/mime4j/dom/Multipart.java new file mode 100644 index 000000000..8101faf4a --- /dev/null +++ b/src/org/apache/james/mime4j/dom/Multipart.java @@ -0,0 +1,238 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Represents a MIME multipart body (see RFC 2045).A multipart body has a + * ordered list of body parts. The multipart body also has a preamble and + * epilogue. The preamble consists of whatever characters appear before the + * first body part while the epilogue consists of whatever characters come after + * the last body part. + */ +public abstract class Multipart implements Body { + + protected List bodyParts = new LinkedList(); + private Entity parent = null; + + private String subType; + + /** + * Creates a new empty Multipart instance. + */ + public Multipart(String subType) { + this.subType = subType; + } + + /** + * Gets the multipart sub-type. E.g. alternative (the + * default) or parallel. See RFC 2045 for common sub-types + * and their meaning. + * + * @return the multipart sub-type. + */ + public String getSubType() { + return subType; + } + + /** + * Sets the multipart sub-type. E.g. alternative or + * parallel. See RFC 2045 for common sub-types and their + * meaning. + * + * @param subType + * the sub-type. + */ + public void setSubType(String subType) { + this.subType = subType; + } + + /** + * @see org.apache.james.mime4j.dom.Body#getParent() + */ + public Entity getParent() { + return parent; + } + + /** + * @see org.apache.james.mime4j.dom.Body#setParent(org.apache.james.mime4j.dom.Entity) + */ + public void setParent(Entity parent) { + this.parent = parent; + for (Entity bodyPart : bodyParts) { + bodyPart.setParent(parent); + } + } + + /** + * Returns the number of body parts. + * + * @return number of Entity objects. + */ + public int getCount() { + return bodyParts.size(); + } + + /** + * Gets the list of body parts. The list is immutable. + * + * @return the list of Entity objects. + */ + public List getBodyParts() { + return Collections.unmodifiableList(bodyParts); + } + + /** + * Sets the list of body parts. + * + * @param bodyParts + * the new list of Entity objects. + */ + public void setBodyParts(List bodyParts) { + this.bodyParts = bodyParts; + for (Entity bodyPart : bodyParts) { + bodyPart.setParent(parent); + } + } + + /** + * Adds a body part to the end of the list of body parts. + * + * @param bodyPart + * the body part. + */ + public void addBodyPart(Entity bodyPart) { + if (bodyPart == null) + throw new IllegalArgumentException(); + + bodyParts.add(bodyPart); + bodyPart.setParent(parent); + } + + /** + * Inserts a body part at the specified position in the list of body parts. + * + * @param bodyPart + * the body part. + * @param index + * index at which the specified body part is to be inserted. + * @throws IndexOutOfBoundsException + * if the index is out of range (index < 0 || index > + * getCount()). + */ + public void addBodyPart(Entity bodyPart, int index) { + if (bodyPart == null) + throw new IllegalArgumentException(); + + bodyParts.add(index, bodyPart); + bodyPart.setParent(parent); + } + + /** + * Removes the body part at the specified position in the list of body + * parts. + * + * @param index + * index of the body part to be removed. + * @return the removed body part. + * @throws IndexOutOfBoundsException + * if the index is out of range (index < 0 || index >= + * getCount()). + */ + public Entity removeBodyPart(int index) { + Entity bodyPart = bodyParts.remove(index); + bodyPart.setParent(null); + return bodyPart; + } + + /** + * Replaces the body part at the specified position in the list of body + * parts with the specified body part. + * + * @param bodyPart + * body part to be stored at the specified position. + * @param index + * index of body part to replace. + * @return the replaced body part. + * @throws IndexOutOfBoundsException + * if the index is out of range (index < 0 || index >= + * getCount()). + */ + public Entity replaceBodyPart(Entity bodyPart, int index) { + if (bodyPart == null) + throw new IllegalArgumentException(); + + Entity replacedEntity = bodyParts.set(index, bodyPart); + if (bodyPart == replacedEntity) + throw new IllegalArgumentException( + "Cannot replace body part with itself"); + + bodyPart.setParent(parent); + replacedEntity.setParent(null); + + return replacedEntity; + } + + /** + * Gets the preamble or null if the message has no preamble. + * + * @return the preamble. + */ + public abstract String getPreamble(); + + /** + * Sets the preamble with a value or null to remove the preamble. + * + * @param preamble + * the preamble. + */ + public abstract void setPreamble(String preamble); + + /** + * Gets the epilogue or null if the message has no epilogue + * + * @return the epilogue. + */ + public abstract String getEpilogue(); + + /** + * Sets the epilogue value, or remove it if the value passed is null. + * + * @param epilogue + * the epilogue. + */ + public abstract void setEpilogue(String epilogue); + + /** + * Disposes of the BodyParts of this Multipart. Note that the dispose call + * does not get forwarded to the parent entity of this Multipart. + * + * @see org.apache.james.mime4j.dom.Disposable#dispose() + */ + public void dispose() { + for (Entity bodyPart : bodyParts) { + bodyPart.dispose(); + } + } + +} diff --git a/src/org/apache/james/mime4j/dom/ServiceLoader.java b/src/org/apache/james/mime4j/dom/ServiceLoader.java new file mode 100644 index 000000000..94f5fede2 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/ServiceLoader.java @@ -0,0 +1,95 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.dom; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.Enumeration; + +/** + * Utility class to load Service Providers (SPI). + * This will deprecated as soon as mime4j will be upgraded to Java6 + * as Java6 has javax.util.ServiceLoader as a core class. + */ +class ServiceLoader { + + private ServiceLoader() { + } + + /** + * Loads a Service Provider for the given interface/class (SPI). + */ + static T load(Class spiClass) { + String spiResURI = "META-INF/services/" + spiClass.getName(); + ClassLoader classLoader = spiClass.getClassLoader(); + Enumeration resources; + try { + resources = classLoader.getResources(spiResURI); + } catch (IOException e) { + return null; + } + + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(resource + .openStream())); + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + int cmtIdx = line.indexOf('#'); + if (cmtIdx != -1) { + line = line.substring(0, cmtIdx); + line = line.trim(); + } + + if (line.length() == 0) { + continue; + } + + Class implClass; + try { + implClass = classLoader.loadClass(line); + + if (spiClass.isAssignableFrom(implClass)) { + Object impl = implClass.newInstance(); + return spiClass.cast(impl); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + } + } + } + } + + return null; + } +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/dom/SingleBody.java b/src/org/apache/james/mime4j/dom/SingleBody.java new file mode 100644 index 000000000..c75cad0d0 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/SingleBody.java @@ -0,0 +1,139 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Abstract implementation of a single message body; that is, a body that does + * not contain (directly or indirectly) any other child bodies. It also provides + * the parent functionality required by bodies. + */ +public abstract class SingleBody implements Body { + + private Entity parent = null; + + /** + * Sole constructor. + */ + protected SingleBody() { + } + + /** + * @see org.apache.james.mime4j.dom.Body#getParent() + */ + public Entity getParent() { + return parent; + } + + /** + * @see org.apache.james.mime4j.dom.Body#setParent(org.apache.james.mime4j.dom.Entity) + */ + public void setParent(Entity parent) { + this.parent = parent; + } + + /** + * Gets a InputStream which reads the bytes of the body. + * + * @return the stream, transfer decoded + * @throws IOException + * on I/O errors. + */ + public abstract InputStream getInputStream() throws IOException; + + /** + * Writes this single body to the given stream. The default implementation copies + * the input stream obtained by {@link #getInputStream()} to the specified output + * stream. May be overwritten by a subclass to improve performance. + * + * @param out + * the stream to write to. + * @throws IOException + * in case of an I/O error + */ + public void writeTo(OutputStream out) throws IOException { + if (out == null) + throw new IllegalArgumentException(); + + InputStream in = getInputStream(); + SingleBody.copy(in, out); + in.close(); + } + + /** + * Returns a copy of this SingleBody (optional operation). + *

+ * The general contract of this method is as follows: + *

    + *
  • Invoking {@link #getParent()} on the copy returns null. + * That means that the copy is detached from the parent entity of this + * SingleBody. The copy may get attached to a different + * entity later on.
  • + *
  • The underlying content does not have to be copied. Instead it may be + * shared between multiple copies of a SingleBody.
  • + *
  • If the underlying content is shared by multiple copies the + * implementation has to make sure that the content gets deleted when the + * last copy gets disposed of (and not before that).
  • + *
+ *

+ * This implementation always throws an + * UnsupportedOperationException. + * + * @return a copy of this SingleBody. + * @throws UnsupportedOperationException + * if the copy operation is not supported by this + * single body. + */ + public SingleBody copy() { + throw new UnsupportedOperationException(); + } + + /** + * Subclasses should override this method if they have allocated resources + * that need to be freed explicitly (e.g. cannot be simply reclaimed by the + * garbage collector). + * + * The default implementation of this method does nothing. + * + * @see org.apache.james.mime4j.dom.Disposable#dispose() + */ + public void dispose() { + } + + static final int DEFAULT_ENCODING_BUFFER_SIZE = 1024; + + /** + * Copies the contents of one stream to the other. + * @param in not null + * @param out not null + * @throws IOException + */ + private static void copy(final InputStream in, final OutputStream out) throws IOException { + final byte[] buffer = new byte[DEFAULT_ENCODING_BUFFER_SIZE]; + int inputLength; + while (-1 != (inputLength = in.read(buffer))) { + out.write(buffer, 0, inputLength); + } + } + +} diff --git a/src/org/apache/james/mime4j/message/TextBody.java b/src/org/apache/james/mime4j/dom/TextBody.java similarity index 76% rename from src/org/apache/james/mime4j/message/TextBody.java rename to src/org/apache/james/mime4j/dom/TextBody.java index cbf24c2e7..9929199d8 100644 --- a/src/org/apache/james/mime4j/message/TextBody.java +++ b/src/org/apache/james/mime4j/dom/TextBody.java @@ -17,26 +17,37 @@ * under the License. * ****************************************************************/ -package org.apache.james.mime4j.message; +package org.apache.james.mime4j.dom; import java.io.IOException; import java.io.Reader; - /** * Encapsulates the contents of a text/* entity body. - * - * - * @version $Id: TextBody.java,v 1.3 2004/10/02 12:41:11 ntherning Exp $ */ -public interface TextBody extends Body { - +public abstract class TextBody extends SingleBody { + + /** + * Sole constructor. + */ + protected TextBody() { + } + + /** + * Returns the MIME charset of this text body. + * + * @return the MIME charset. + */ + public abstract String getMimeCharset(); + /** * Gets a Reader which may be used to read out the contents * of this body. - * + * * @return the Reader. - * @throws IOException on I/O errors. + * @throws IOException + * on I/O errors. */ - Reader getReader() throws IOException; + public abstract Reader getReader() throws IOException; + } diff --git a/src/org/apache/james/mime4j/field/address/Address.java b/src/org/apache/james/mime4j/dom/address/Address.java similarity index 60% rename from src/org/apache/james/mime4j/field/address/Address.java rename to src/org/apache/james/mime4j/dom/address/Address.java index 5438eb5b2..3dc73ced3 100644 --- a/src/org/apache/james/mime4j/field/address/Address.java +++ b/src/org/apache/james/mime4j/dom/address/Address.java @@ -17,36 +17,32 @@ * under the License. * ****************************************************************/ -package org.apache.james.mime4j.field.address; +package org.apache.james.mime4j.dom.address; -import java.util.ArrayList; +import java.io.Serializable; +import java.util.List; /** - * The abstract base for classes that represent RFC2822 addresses. - * This includes groups and mailboxes. - * - * Currently, no public methods are introduced on this class. - * - * + * The abstract base for classes that represent RFC2822 addresses. This includes + * groups and mailboxes. */ -public abstract class Address { +public abstract class Address implements Serializable { - /** - * Adds any mailboxes represented by this address - * into the given ArrayList. Note that this method - * has default (package) access, so a doAddMailboxesTo - * method is needed to allow the behavior to be - * overridden by subclasses. - */ - final void addMailboxesTo(ArrayList results) { - doAddMailboxesTo(results); - } - - /** - * Adds any mailboxes represented by this address - * into the given ArrayList. Must be overridden by - * concrete subclasses. - */ - protected abstract void doAddMailboxesTo(ArrayList results); + private static final long serialVersionUID = 634090661990433426L; -} + /** + * Adds any mailboxes represented by this address into the given List. Note + * that this method has default (package) access, so a doAddMailboxesTo + * method is needed to allow the behavior to be overridden by subclasses. + */ + final void addMailboxesTo(List results) { + doAddMailboxesTo(results); + } + + /** + * Adds any mailboxes represented by this address into the given List. Must + * be overridden by concrete subclasses. + */ + protected abstract void doAddMailboxesTo(List results); + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/dom/address/AddressList.java b/src/org/apache/james/mime4j/dom/address/AddressList.java new file mode 100644 index 000000000..91b7a3df1 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/address/AddressList.java @@ -0,0 +1,98 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.address; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * An immutable, random-access list of Address objects. + */ +public class AddressList extends AbstractList

implements Serializable { + + private static final long serialVersionUID = 1L; + + private final List addresses; + + /** + * @param addresses + * A List that contains only Address objects. + * @param dontCopy + * true iff it is not possible for the addresses list to be + * modified by someone else. + */ + public AddressList(List addresses, boolean dontCopy) { + if (addresses != null) + this.addresses = dontCopy ? addresses : new ArrayList
( + addresses); + else + this.addresses = Collections.emptyList(); + } + + /** + * The number of elements in this list. + */ + @Override + public int size() { + return addresses.size(); + } + + /** + * Gets an address. + */ + @Override + public Address get(int index) { + return addresses.get(index); + } + + /** + * Returns a flat list of all mailboxes represented in this address list. + * Use this if you don't care about grouping. + */ + public MailboxList flatten() { + // in the common case, all addresses are mailboxes + boolean groupDetected = false; + for (Address addr : addresses) { + if (!(addr instanceof Mailbox)) { + groupDetected = true; + break; + } + } + + if (!groupDetected) { + @SuppressWarnings("unchecked") + final List mailboxes = (List) addresses; + return new MailboxList(mailboxes, true); + } + + List results = new ArrayList(); + for (Address addr : addresses) { + addr.addMailboxesTo(results); + } + + // copy-on-construct this time, because subclasses + // could have held onto a reference to the results + return new MailboxList(results, false); + } + +} diff --git a/src/org/apache/james/mime4j/dom/address/DomainList.java b/src/org/apache/james/mime4j/dom/address/DomainList.java new file mode 100644 index 000000000..218e6c2c3 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/address/DomainList.java @@ -0,0 +1,95 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.address; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * An immutable, random-access list of Strings (that are supposedly domain names + * or domain literals). + */ +public class DomainList extends AbstractList implements Serializable { + + private static final long serialVersionUID = 1L; + + private final List domains; + + /** + * @param domains + * A List that contains only String objects. + * @param dontCopy + * true iff it is not possible for the domains list to be + * modified by someone else. + */ + public DomainList(List domains, boolean dontCopy) { + if (domains != null) + this.domains = dontCopy ? domains : new ArrayList(domains); + else + this.domains = Collections.emptyList(); + } + + /** + * The number of elements in this list. + */ + @Override + public int size() { + return domains.size(); + } + + /** + * Gets the domain name or domain literal at the specified index. + * + * @throws IndexOutOfBoundsException + * If index is < 0 or >= size(). + */ + @Override + public String get(int index) { + return domains.get(index); + } + + /** + * Returns the list of domains formatted as a route string (not including + * the trailing ':'). + */ + public String toRouteString() { + StringBuilder sb = new StringBuilder(); + + for (String domain : domains) { + if (sb.length() > 0) { + sb.append(','); + } + + sb.append("@"); + sb.append(domain); + } + + return sb.toString(); + } + + @Override + public String toString() { + return toRouteString(); + } + +} diff --git a/src/org/apache/james/mime4j/dom/address/Group.java b/src/org/apache/james/mime4j/dom/address/Group.java new file mode 100644 index 000000000..14b143339 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/address/Group.java @@ -0,0 +1,113 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.address; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * A named group of zero or more mailboxes. + */ +public class Group extends Address { + + private static final long serialVersionUID = 1L; + + private final String name; + private final MailboxList mailboxList; + + /** + * @param name + * The group name. + * @param mailboxes + * The mailboxes in this group. + */ + public Group(String name, MailboxList mailboxes) { + if (name == null) + throw new IllegalArgumentException(); + if (mailboxes == null) + throw new IllegalArgumentException(); + + this.name = name; + this.mailboxList = mailboxes; + } + + /** + * @param name + * The group name. + * @param mailboxes + * The mailboxes in this group. + */ + public Group(String name, Mailbox... mailboxes) { + this(name, new MailboxList(Arrays.asList(mailboxes), true)); + } + + /** + * @param name + * The group name. + * @param mailboxes + * The mailboxes in this group. + */ + public Group(String name, Collection mailboxes) { + this(name, new MailboxList(new ArrayList(mailboxes), true)); + } + + /** + * Returns the group name. + */ + public String getName() { + return name; + } + + /** + * Returns the mailboxes in this group. + */ + public MailboxList getMailboxes() { + return mailboxList; + } + + @Override + protected void doAddMailboxesTo(List results) { + for (Mailbox mailbox : mailboxList) { + results.add(mailbox); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(name); + sb.append(':'); + boolean first = true; + for (Mailbox mailbox : mailboxList) { + if (first) { + first = false; + } else { + sb.append(','); + } + sb.append(' '); + sb.append(mailbox); + } + sb.append(";"); + return sb.toString(); + } + +} diff --git a/src/org/apache/james/mime4j/dom/address/Mailbox.java b/src/org/apache/james/mime4j/dom/address/Mailbox.java new file mode 100644 index 000000000..86304bea5 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/address/Mailbox.java @@ -0,0 +1,204 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.address; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import org.apache.james.mime4j.util.LangUtils; + +/** + * Represents a single e-mail address. + */ +public class Mailbox extends Address { + + private static final long serialVersionUID = 1L; + + private static final DomainList EMPTY_ROUTE_LIST = new DomainList( + Collections. emptyList(), true); + + private final String name; + private final DomainList route; + private final String localPart; + private final String domain; + + /** + * Creates a named mailbox with a route. Routes are obsolete. + * + * @param name + * the name of the e-mail address. May be null. + * @param route + * The zero or more domains that make up the route. May be + * null. + * @param localPart + * The part of the e-mail address to the left of the "@". + * @param domain + * The part of the e-mail address to the right of the "@". + */ + public Mailbox(String name, DomainList route, String localPart, + String domain) { + if (localPart == null || localPart.length() == 0) + throw new IllegalArgumentException(); + + this.name = name == null || name.length() == 0 ? null : name; + this.route = route == null ? EMPTY_ROUTE_LIST : route; + this.localPart = localPart; + this.domain = domain == null || domain.length() == 0 ? null : domain; + } + + /** + * Creates a named mailbox based on an unnamed mailbox. Package private; + * internally used by Builder. + */ + Mailbox(String name, Mailbox baseMailbox) { + this(name, baseMailbox.getRoute(), baseMailbox.getLocalPart(), + baseMailbox.getDomain()); + } + + /** + * Creates an unnamed mailbox without a route. Routes are obsolete. + * + * @param localPart + * The part of the e-mail address to the left of the "@". + * @param domain + * The part of the e-mail address to the right of the "@". + */ + public Mailbox(String localPart, String domain) { + this(null, null, localPart, domain); + } + + /** + * Creates an unnamed mailbox with a route. Routes are obsolete. + * + * @param route + * The zero or more domains that make up the route. May be + * null. + * @param localPart + * The part of the e-mail address to the left of the "@". + * @param domain + * The part of the e-mail address to the right of the "@". + */ + public Mailbox(DomainList route, String localPart, String domain) { + this(null, route, localPart, domain); + } + + /** + * Creates a named mailbox without a route. Routes are obsolete. + * + * @param name + * the name of the e-mail address. May be null. + * @param localPart + * The part of the e-mail address to the left of the "@". + * @param domain + * The part of the e-mail address to the right of the "@". + */ + public Mailbox(String name, String localPart, String domain) { + this(name, null, localPart, domain); + } + + /** + * Returns the name of the mailbox or null if it does not + * have a name. + */ + public String getName() { + return name; + } + + /** + * Returns the route list. If the mailbox does not have a route an empty + * domain list is returned. + */ + public DomainList getRoute() { + return route; + } + + /** + * Returns the left part of the e-mail address (before "@"). + */ + public String getLocalPart() { + return localPart; + } + + /** + * Returns the right part of the e-mail address (after "@"). + */ + public String getDomain() { + return domain; + } + + /** + * Returns the address in the form localPart@domain. + * + * @return the address part of this mailbox. + */ + public String getAddress() { + if (domain == null) { + return localPart; + } else { + return localPart + '@' + domain; + } + } + + @Override + protected final void doAddMailboxesTo(List results) { + results.add(this); + } + + @Override + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, this.localPart); + hash = LangUtils.hashCode(hash, this.domain != null ? + this.domain.toLowerCase(Locale.US) : null); + return hash; + } + + /** + * Indicates whether some other object is "equal to" this mailbox. + *

+ * An object is considered to be equal to this mailbox if it is an instance + * of class Mailbox that holds the same address as this one. + * The domain is considered to be case-insensitive but the local-part is not + * (because of RFC 5321: the local-part of a mailbox MUST BE treated + * as case sensitive). + * + * @param obj + * the object to test for equality. + * @return true if the specified object is a + * Mailbox that holds the same address as this one. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (!(obj instanceof Mailbox)) + return false; + Mailbox that = (Mailbox) obj; + return LangUtils.equals(this.localPart, that.localPart) && + LangUtils.equalsIgnoreCase(this.domain, that.domain); + } + + @Override + public String toString() { + return getAddress(); + } + +} diff --git a/src/org/apache/james/mime4j/field/address/MailboxList.java b/src/org/apache/james/mime4j/dom/address/MailboxList.java similarity index 53% rename from src/org/apache/james/mime4j/field/address/MailboxList.java rename to src/org/apache/james/mime4j/dom/address/MailboxList.java index db814a7fe..da1b786c3 100644 --- a/src/org/apache/james/mime4j/field/address/MailboxList.java +++ b/src/org/apache/james/mime4j/dom/address/MailboxList.java @@ -17,55 +17,52 @@ * under the License. * ****************************************************************/ -package org.apache.james.mime4j.field.address; +package org.apache.james.mime4j.dom.address; +import java.io.Serializable; +import java.util.AbstractList; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * An immutable, random-access list of Mailbox objects. - * - * */ -public class MailboxList { +public class MailboxList extends AbstractList implements Serializable { - private ArrayList mailboxes; - - /** - * @param mailboxes An ArrayList that contains only Mailbox objects. - * @param dontCopy true iff it is not possible for the mailboxes ArrayList to be modified by someone else. - */ - public MailboxList(ArrayList mailboxes, boolean dontCopy) { - if (mailboxes != null) - this.mailboxes = (dontCopy ? mailboxes : (ArrayList) mailboxes.clone()); - else - this.mailboxes = new ArrayList(0); - } - - /** - * The number of elements in this list. - */ - public int size() { - return mailboxes.size(); - } - - /** - * Gets an address. - */ - public Mailbox get(int index) { - if (0 > index || size() <= index) - throw new IndexOutOfBoundsException(); - return (Mailbox) mailboxes.get(index); - } - - /** - * Dumps a representation of this mailbox list to - * stdout, for debugging purposes. - */ - public void print() { - for (int i = 0; i < size(); i++) { - Mailbox mailbox = get(i); - System.out.println(mailbox.toString()); - } - } + private static final long serialVersionUID = 1L; + + private final List mailboxes; + + /** + * @param mailboxes + * A List that contains only Mailbox objects. + * @param dontCopy + * true iff it is not possible for the mailboxes list to be + * modified by someone else. + */ + public MailboxList(List mailboxes, boolean dontCopy) { + if (mailboxes != null) + this.mailboxes = dontCopy ? mailboxes : new ArrayList( + mailboxes); + else + this.mailboxes = Collections.emptyList(); + } + + /** + * The number of elements in this list. + */ + @Override + public int size() { + return mailboxes.size(); + } + + /** + * Gets an address. + */ + @Override + public Mailbox get(int index) { + return mailboxes.get(index); + } } diff --git a/src/org/apache/james/mime4j/field/datetime/DateTime.java b/src/org/apache/james/mime4j/dom/datetime/DateTime.java similarity index 67% rename from src/org/apache/james/mime4j/field/datetime/DateTime.java rename to src/org/apache/james/mime4j/dom/datetime/DateTime.java index 506ff54e5..d19ab8c78 100644 --- a/src/org/apache/james/mime4j/field/datetime/DateTime.java +++ b/src/org/apache/james/mime4j/dom/datetime/DateTime.java @@ -17,17 +17,12 @@ * under the License. * ****************************************************************/ -package org.apache.james.mime4j.field.datetime; +package org.apache.james.mime4j.dom.datetime; -import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; -import org.apache.james.mime4j.field.datetime.parser.ParseException; -import org.apache.james.mime4j.field.datetime.parser.TokenMgrError; - -import java.util.Date; import java.util.Calendar; -import java.util.TimeZone; +import java.util.Date; import java.util.GregorianCalendar; -import java.io.StringReader; +import java.util.TimeZone; public class DateTime { private final Date date; @@ -112,16 +107,59 @@ public class DateTime { } public void print() { - System.out.println(getYear() + " " + getMonth() + " " + getDay() + "; " + getHour() + " " + getMinute() + " " + getSecond() + " " + getTimeZone()); + System.out.println(toString()); + } + + @Override + public String toString() { + return getYear() + " " + getMonth() + " " + getDay() + "; " + getHour() + " " + getMinute() + " " + getSecond() + " " + getTimeZone(); + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((date == null) ? 0 : date.hashCode()); + result = PRIME * result + day; + result = PRIME * result + hour; + result = PRIME * result + minute; + result = PRIME * result + month; + result = PRIME * result + second; + result = PRIME * result + timeZone; + result = PRIME * result + year; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final DateTime other = (DateTime) obj; + if (date == null) { + if (other.date != null) + return false; + } else if (!date.equals(other.date)) + return false; + if (day != other.day) + return false; + if (hour != other.hour) + return false; + if (minute != other.minute) + return false; + if (month != other.month) + return false; + if (second != other.second) + return false; + if (timeZone != other.timeZone) + return false; + if (year != other.year) + return false; + return true; } - public static DateTime parse(String dateString) throws ParseException { - try { - return new DateTimeParser(new StringReader(dateString)).parseAll(); - } - catch (TokenMgrError err) { - throw new ParseException(err.getMessage()); - } - } } diff --git a/src/org/apache/james/mime4j/dom/field/AddressListField.java b/src/org/apache/james/mime4j/dom/field/AddressListField.java new file mode 100644 index 000000000..f8d2f0a99 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/field/AddressListField.java @@ -0,0 +1,28 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.field; + +import org.apache.james.mime4j.dom.address.AddressList; + +public interface AddressListField extends ParsedField { + + AddressList getAddressList(); + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/dom/field/ContentDispositionField.java b/src/org/apache/james/mime4j/dom/field/ContentDispositionField.java new file mode 100644 index 000000000..10f984e6e --- /dev/null +++ b/src/org/apache/james/mime4j/dom/field/ContentDispositionField.java @@ -0,0 +1,117 @@ +package org.apache.james.mime4j.dom.field; + +import java.util.Date; +import java.util.Map; + +public interface ContentDispositionField extends ParsedField { + + /** The inline disposition type. */ + public static final String DISPOSITION_TYPE_INLINE = "inline"; + /** The attachment disposition type. */ + public static final String DISPOSITION_TYPE_ATTACHMENT = "attachment"; + /** The name of the filename parameter. */ + public static final String PARAM_FILENAME = "filename"; + /** The name of the creation-date parameter. */ + public static final String PARAM_CREATION_DATE = "creation-date"; + /** The name of the modification-date parameter. */ + public static final String PARAM_MODIFICATION_DATE = "modification-date"; + /** The name of the read-date parameter. */ + public static final String PARAM_READ_DATE = "read-date"; + /** The name of the size parameter. */ + public static final String PARAM_SIZE = "size"; + + /** + * Gets the disposition type defined in this Content-Disposition field. + * + * @return the disposition type or an empty string if not set. + */ + String getDispositionType(); + + /** + * Gets the value of a parameter. Parameter names are case-insensitive. + * + * @param name + * the name of the parameter to get. + * @return the parameter value or null if not set. + */ + String getParameter(String name); + + /** + * Gets all parameters. + * + * @return the parameters. + */ + Map getParameters(); + + /** + * Determines if the disposition type of this field matches the given one. + * + * @param dispositionType + * the disposition type to match against. + * @return true if the disposition type of this field + * matches, false otherwise. + */ + boolean isDispositionType(String dispositionType); + + /** + * Return true if the disposition type of this field is + * inline, false otherwise. + * + * @return true if the disposition type of this field is + * inline, false otherwise. + */ + boolean isInline(); + + /** + * Return true if the disposition type of this field is + * attachment, false otherwise. + * + * @return true if the disposition type of this field is + * attachment, false otherwise. + */ + public abstract boolean isAttachment(); + + /** + * Gets the value of the filename parameter if set. + * + * @return the filename parameter value or null + * if not set. + */ + String getFilename(); + + /** + * Gets the value of the creation-date parameter if set and + * valid. + * + * @return the creation-date parameter value or + * null if not set or invalid. + */ + Date getCreationDate(); + + /** + * Gets the value of the modification-date parameter if set + * and valid. + * + * @return the modification-date parameter value or + * null if not set or invalid. + */ + Date getModificationDate(); + + /** + * Gets the value of the read-date parameter if set and + * valid. + * + * @return the read-date parameter value or null + * if not set or invalid. + */ + Date getReadDate(); + + /** + * Gets the value of the size parameter if set and valid. + * + * @return the size parameter value or -1 if + * not set or invalid. + */ + long getSize(); + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/dom/field/ContentTransferEncodingField.java b/src/org/apache/james/mime4j/dom/field/ContentTransferEncodingField.java new file mode 100644 index 000000000..994f1e1ad --- /dev/null +++ b/src/org/apache/james/mime4j/dom/field/ContentTransferEncodingField.java @@ -0,0 +1,31 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.field; + +public interface ContentTransferEncodingField extends ParsedField { + + /** + * Gets the encoding defined in this field. + * + * @return the encoding or an empty string if not set. + */ + String getEncoding(); + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/dom/field/ContentTypeField.java b/src/org/apache/james/mime4j/dom/field/ContentTypeField.java new file mode 100644 index 000000000..467a24ce3 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/field/ContentTypeField.java @@ -0,0 +1,97 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.field; + +import java.util.Map; + +public interface ContentTypeField extends ParsedField { + + /** The prefix of all multipart MIME types. */ + public static final String TYPE_MULTIPART_PREFIX = "multipart/"; + /** The multipart/digest MIME type. */ + public static final String TYPE_MULTIPART_DIGEST = "multipart/digest"; + /** The text/plain MIME type. */ + public static final String TYPE_TEXT_PLAIN = "text/plain"; + /** The message/rfc822 MIME type. */ + public static final String TYPE_MESSAGE_RFC822 = "message/rfc822"; + /** The name of the boundary parameter. */ + public static final String PARAM_BOUNDARY = "boundary"; + /** The name of the charset parameter. */ + public static final String PARAM_CHARSET = "charset"; + + /** + * Gets the MIME type defined in this Content-Type field. + * + * @return the MIME type or an empty string if not set. + */ + String getMimeType(); + + /** + * Gets the value of a parameter. Parameter names are case-insensitive. + * + * @param name + * the name of the parameter to get. + * @return the parameter value or null if not set. + */ + String getParameter(String name); + + /** + * Gets all parameters. + * + * @return the parameters. + */ + Map getParameters(); + + /** + * Determines if the MIME type of this field matches the given one. + * + * @param mimeType + * the MIME type to match against. + * @return true if the MIME type of this field matches, + * false otherwise. + */ + boolean isMimeType(String mimeType); + + /** + * Determines if the MIME type of this field is multipart/*. + * + * @return true if this field is has a + * multipart/* MIME type, false + * otherwise. + */ + boolean isMultipart(); + + /** + * Gets the value of the boundary parameter if set. + * + * @return the boundary parameter value or null + * if not set. + */ + String getBoundary(); + + /** + * Gets the value of the charset parameter if set. + * + * @return the charset parameter value or null + * if not set. + */ + String getCharset(); + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/dom/field/DateTimeField.java b/src/org/apache/james/mime4j/dom/field/DateTimeField.java new file mode 100644 index 000000000..ce0282db9 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/field/DateTimeField.java @@ -0,0 +1,9 @@ +package org.apache.james.mime4j.dom.field; + +import java.util.Date; + +public interface DateTimeField extends ParsedField { + + Date getDate(); + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/dom/field/Field.java b/src/org/apache/james/mime4j/dom/field/Field.java new file mode 100644 index 000000000..f9db369f9 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/field/Field.java @@ -0,0 +1,51 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.field; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Abstract MIME field. + */ +public interface Field { + + /** + * Gets the name of the field (Subject, From, etc). + * + * @return the field name. + */ + String getName(); + + /** + * Gets the unparsed and possibly encoded (see RFC 2047) field body string. + * + * @return the unparsed field body string. + */ + String getBody(); + + /** + * Writes the original raw field bytes to an output stream. + * The output is folded, the last CRLF is not included. + * @throws IOException + */ + void writeTo(OutputStream out) throws IOException; + +} diff --git a/src/org/apache/james/mime4j/decoder/ByteQueue.java b/src/org/apache/james/mime4j/dom/field/FieldName.java similarity index 50% rename from src/org/apache/james/mime4j/decoder/ByteQueue.java rename to src/org/apache/james/mime4j/dom/field/FieldName.java index 6d7ccef52..d9ab42fa6 100644 --- a/src/org/apache/james/mime4j/decoder/ByteQueue.java +++ b/src/org/apache/james/mime4j/dom/field/FieldName.java @@ -17,46 +17,37 @@ * under the License. * ****************************************************************/ -package org.apache.james.mime4j.decoder; +package org.apache.james.mime4j.dom.field; -import java.util.Iterator; +/** + * Constants for common header field names. + */ +public class FieldName { -public class ByteQueue { + public static final String CONTENT_DISPOSITION = "Content-Disposition"; + public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding"; + public static final String CONTENT_TYPE = "Content-Type"; - private UnboundedFifoByteBuffer buf; - private int initialCapacity = -1; + public static final String DATE = "Date"; + public static final String MESSAGE_ID = "Message-ID"; + public static final String SUBJECT = "Subject"; - public ByteQueue() { - buf = new UnboundedFifoByteBuffer(); + public static final String FROM = "From"; + public static final String SENDER = "Sender"; + public static final String TO = "To"; + public static final String CC = "Cc"; + public static final String BCC = "Bcc"; + public static final String REPLY_TO = "Reply-To"; + + public static final String RESENT_DATE = "Resent-Date"; + + public static final String RESENT_FROM = "Resent-From"; + public static final String RESENT_SENDER = "Resent-Sender"; + public static final String RESENT_TO = "Resent-To"; + public static final String RESENT_CC = "Resent-Cc"; + public static final String RESENT_BCC = "Resent-Bcc"; + + private FieldName() { } - public ByteQueue(int initialCapacity) { - buf = new UnboundedFifoByteBuffer(initialCapacity); - this.initialCapacity = initialCapacity; - } - - public void enqueue(byte b) { - buf.add(b); - } - - public byte dequeue() { - return buf.remove(); - } - - public int count() { - return buf.size(); - } - - public void clear() { - if (initialCapacity != -1) - buf = new UnboundedFifoByteBuffer(initialCapacity); - else - buf = new UnboundedFifoByteBuffer(); - } - - public Iterator iterator() { - return buf.iterator(); - } - - } diff --git a/src/org/apache/james/mime4j/dom/field/MailboxField.java b/src/org/apache/james/mime4j/dom/field/MailboxField.java new file mode 100644 index 000000000..64c3fa338 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/field/MailboxField.java @@ -0,0 +1,28 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.field; + +import org.apache.james.mime4j.dom.address.Mailbox; + +public interface MailboxField extends ParsedField { + + Mailbox getMailbox(); + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/dom/field/MailboxListField.java b/src/org/apache/james/mime4j/dom/field/MailboxListField.java new file mode 100644 index 000000000..e02bdd89a --- /dev/null +++ b/src/org/apache/james/mime4j/dom/field/MailboxListField.java @@ -0,0 +1,28 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.field; + +import org.apache.james.mime4j.dom.address.MailboxList; + +public interface MailboxListField extends ParsedField { + + MailboxList getMailboxList(); + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/dom/field/ParseException.java b/src/org/apache/james/mime4j/dom/field/ParseException.java new file mode 100644 index 000000000..e4baf77b3 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/field/ParseException.java @@ -0,0 +1,64 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.field; + +import org.apache.james.mime4j.MimeException; + +/** + * This exception is thrown when parse errors are encountered. + */ +public class ParseException extends MimeException { + + private static final long serialVersionUID = 1L; + + /** + * Constructs a new parse exception with the specified detail message. + * + * @param message + * detail message + */ + protected ParseException(String message) { + super(message); + } + + /** + * Constructs a new parse exception with the specified cause. + * + * @param cause + * the cause + */ + protected ParseException(Throwable cause) { + super(cause); + } + + /** + * Constructs a new parse exception with the specified detail message and + * cause. + * + * @param message + * detail message + * @param cause + * the cause + */ + protected ParseException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/org/apache/james/mime4j/dom/field/ParsedField.java b/src/org/apache/james/mime4j/dom/field/ParsedField.java new file mode 100644 index 000000000..4f91e2717 --- /dev/null +++ b/src/org/apache/james/mime4j/dom/field/ParsedField.java @@ -0,0 +1,45 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.field; + + +public interface ParsedField extends Field { + + /** + * Returns true if this field is valid, i.e. no errors were + * encountered while parsing the field value. + * + * @return true if this field is valid, false + * otherwise. + * @see #getParseException() + */ + boolean isValidField(); + + /** + * Returns the exception that was thrown by the field parser while parsing + * the field value. The result is null if the field is valid + * and no errors were encountered. + * + * @return the exception that was thrown by the field parser or + * null if the field is valid. + */ + ParseException getParseException(); + +} diff --git a/src/org/apache/james/mime4j/dom/field/UnstructuredField.java b/src/org/apache/james/mime4j/dom/field/UnstructuredField.java new file mode 100644 index 000000000..6651e2deb --- /dev/null +++ b/src/org/apache/james/mime4j/dom/field/UnstructuredField.java @@ -0,0 +1,26 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.dom.field; + +public interface UnstructuredField extends ParsedField { + + String getValue(); + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/field/AbstractField.java b/src/org/apache/james/mime4j/field/AbstractField.java new file mode 100644 index 000000000..21939a11e --- /dev/null +++ b/src/org/apache/james/mime4j/field/AbstractField.java @@ -0,0 +1,97 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.field.ParseException; +import org.apache.james.mime4j.dom.field.ParsedField; +import org.apache.james.mime4j.util.ByteSequence; + +/** + * The base class of all field classes. + */ +public abstract class AbstractField implements ParsedField { + + private final String name; + private final String body; + private final ByteSequence raw; + protected DecodeMonitor monitor; + + protected AbstractField( + final String name, + final String body, + final ByteSequence raw, + final DecodeMonitor monitor) { + this.name = name; + this.body = body; + this.raw = raw; + this.monitor = monitor != null ? monitor : DecodeMonitor.SILENT; + } + + /** + * Gets the name of the field (Subject, + * From, etc). + * + * @return the field name. + */ + public String getName() { + return name; + } + + /** + * @see org.apache.james.mime4j.dom.field.Field#writeTo(java.io.OutputStream) + */ + public void writeTo(OutputStream out) throws IOException { + out.write(raw.toByteArray()); + } + + /** + * Gets the unfolded, unparsed and possibly encoded (see RFC 2047) field + * body string. + * + * @return the unfolded unparsed field body string. + */ + public String getBody() { + return body; + } + + /** + * @see ParsedField#isValidField() + */ + public boolean isValidField() { + return getParseException() == null; + } + + /** + * @see ParsedField#getParseException() + */ + public ParseException getParseException() { + return null; + } + + @Override + public String toString() { + return name + ": " + body; + } + +} diff --git a/src/org/apache/james/mime4j/field/AddressListField.java b/src/org/apache/james/mime4j/field/AddressListFieldImpl.java similarity index 51% rename from src/org/apache/james/mime4j/field/AddressListField.java rename to src/org/apache/james/mime4j/field/AddressListFieldImpl.java index a2e6f992f..a5395bc1c 100644 --- a/src/org/apache/james/mime4j/field/AddressListField.java +++ b/src/org/apache/james/mime4j/field/AddressListFieldImpl.java @@ -19,45 +19,63 @@ package org.apache.james.mime4j.field; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.james.mime4j.field.address.AddressList; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.address.AddressList; +import org.apache.james.mime4j.field.address.parser.AddressBuilder; import org.apache.james.mime4j.field.address.parser.ParseException; +import org.apache.james.mime4j.util.ByteSequence; + +/** + * Address list field such as To or Reply-To. + */ +public class AddressListFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.AddressListField { + + private boolean parsed = false; -public class AddressListField extends Field { private AddressList addressList; private ParseException parseException; - protected AddressListField(String name, String body, String raw, AddressList addressList, ParseException parseException) { - super(name, body, raw); - this.addressList = addressList; - this.parseException = parseException; + AddressListFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) { + super(name, body, raw, monitor); } + /** + * @see org.apache.james.mime4j.dom.field.AddressListField#getAddressList() + */ public AddressList getAddressList() { + if (!parsed) + parse(); + return addressList; } + /** + * @see org.apache.james.mime4j.dom.field.AddressListField#getParseException() + */ + @Override public ParseException getParseException() { + if (!parsed) + parse(); + return parseException; } - public static class Parser implements FieldParser { - private static Log log = LogFactory.getLog(Parser.class); + private void parse() { + String body = getBody(); - public Field parse(final String name, final String body, final String raw) { - AddressList addressList = null; - ParseException parseException = null; - try { - addressList = AddressList.parse(body); - } - catch (ParseException e) { - if (log.isDebugEnabled()) { - log.debug("Parsing value '" + body + "': "+ e.getMessage()); - } - parseException = e; - } - return new AddressListField(name, body, raw, addressList, parseException); + try { + addressList = AddressBuilder.parseAddressList(body, monitor); + } catch (ParseException e) { + parseException = e; } + + parsed = true; } + + static final FieldParser PARSER = new FieldParser() { + public AddressListFieldImpl parse(final String name, final String body, + final ByteSequence raw, DecodeMonitor monitor) { + return new AddressListFieldImpl(name, body, raw, monitor); + } + }; } diff --git a/src/org/apache/james/mime4j/field/ContentDispositionFieldImpl.java b/src/org/apache/james/mime4j/field/ContentDispositionFieldImpl.java new file mode 100644 index 000000000..81b26bd8d --- /dev/null +++ b/src/org/apache/james/mime4j/field/ContentDispositionFieldImpl.java @@ -0,0 +1,253 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +import java.io.StringReader; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.field.contentdisposition.parser.ContentDispositionParser; +import org.apache.james.mime4j.field.contentdisposition.parser.ParseException; +import org.apache.james.mime4j.field.contentdisposition.parser.TokenMgrError; +import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; +import org.apache.james.mime4j.util.ByteSequence; + +/** + * Represents a Content-Disposition field. + */ +public class ContentDispositionFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.ContentDispositionField { + + private boolean parsed = false; + + private String dispositionType = ""; + private Map parameters = new HashMap(); + private ParseException parseException; + + private boolean creationDateParsed; + private Date creationDate; + + private boolean modificationDateParsed; + private Date modificationDate; + + private boolean readDateParsed; + private Date readDate; + + ContentDispositionFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) { + super(name, body, raw, monitor); + } + + /** + * Gets the exception that was raised during parsing of the field value, if + * any; otherwise, null. + */ + @Override + public ParseException getParseException() { + if (!parsed) + parse(); + + return parseException; + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentDispositionField#getDispositionType() + */ + public String getDispositionType() { + if (!parsed) + parse(); + + return dispositionType; + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentDispositionField#getParameter(java.lang.String) + */ + public String getParameter(String name) { + if (!parsed) + parse(); + + return parameters.get(name.toLowerCase()); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentDispositionField#getParameters() + */ + public Map getParameters() { + if (!parsed) + parse(); + + return Collections.unmodifiableMap(parameters); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentDispositionField#isDispositionType(java.lang.String) + */ + public boolean isDispositionType(String dispositionType) { + if (!parsed) + parse(); + + return this.dispositionType.equalsIgnoreCase(dispositionType); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentDispositionField#isInline() + */ + public boolean isInline() { + if (!parsed) + parse(); + + return dispositionType.equals(DISPOSITION_TYPE_INLINE); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentDispositionField#isAttachment() + */ + public boolean isAttachment() { + if (!parsed) + parse(); + + return dispositionType.equals(DISPOSITION_TYPE_ATTACHMENT); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentDispositionField#getFilename() + */ + public String getFilename() { + return getParameter(PARAM_FILENAME); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentDispositionField#getCreationDate() + */ + public Date getCreationDate() { + if (!creationDateParsed) { + creationDate = parseDate(PARAM_CREATION_DATE); + creationDateParsed = true; + } + + return creationDate; + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentDispositionField#getModificationDate() + */ + public Date getModificationDate() { + if (!modificationDateParsed) { + modificationDate = parseDate(PARAM_MODIFICATION_DATE); + modificationDateParsed = true; + } + + return modificationDate; + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentDispositionField#getReadDate() + */ + public Date getReadDate() { + if (!readDateParsed) { + readDate = parseDate(PARAM_READ_DATE); + readDateParsed = true; + } + + return readDate; + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentDispositionField#getSize() + */ + public long getSize() { + String value = getParameter(PARAM_SIZE); + if (value == null) + return -1; + + try { + long size = Long.parseLong(value); + return size < 0 ? -1 : size; + } catch (NumberFormatException e) { + return -1; + } + } + + private Date parseDate(String paramName) { + String value = getParameter(paramName); + if (value == null) { + monitor.warn("Parsing " + paramName + " null", "returning null"); + return null; + } + + try { + return new DateTimeParser(new StringReader(value)).parseAll() + .getDate(); + } catch (org.apache.james.mime4j.field.datetime.parser.ParseException e) { + monitor.warn("Parsing " + paramName + " '" + value + "': " + + e.getMessage(), "returning null"); + return null; + } catch (org.apache.james.mime4j.field.datetime.parser.TokenMgrError e) { + monitor.warn("Parsing " + paramName + " '" + value + "': " + + e.getMessage(), "returning null"); + return null; + } + } + + private void parse() { + String body = getBody(); + + ContentDispositionParser parser = new ContentDispositionParser( + new StringReader(body)); + try { + parser.parseAll(); + } catch (ParseException e) { + parseException = e; + } catch (TokenMgrError e) { + parseException = new ParseException(e.getMessage()); + } + + final String dispositionType = parser.getDispositionType(); + + if (dispositionType != null) { + this.dispositionType = dispositionType.toLowerCase(Locale.US); + + List paramNames = parser.getParamNames(); + List paramValues = parser.getParamValues(); + + if (paramNames != null && paramValues != null) { + final int len = Math.min(paramNames.size(), paramValues.size()); + for (int i = 0; i < len; i++) { + String paramName = paramNames.get(i).toLowerCase(Locale.US); + String paramValue = paramValues.get(i); + parameters.put(paramName, paramValue); + } + } + } + + parsed = true; + } + + static final FieldParser PARSER = new FieldParser() { + public ContentDispositionFieldImpl parse(final String name, final String body, + final ByteSequence raw, DecodeMonitor monitor) { + return new ContentDispositionFieldImpl(name, body, raw, monitor); + } + }; +} diff --git a/src/org/apache/james/mime4j/field/ContentTransferEncodingField.java b/src/org/apache/james/mime4j/field/ContentTransferEncodingFieldImpl.java similarity index 54% rename from src/org/apache/james/mime4j/field/ContentTransferEncodingField.java rename to src/org/apache/james/mime4j/field/ContentTransferEncodingFieldImpl.java index 73d8d2339..c42220f61 100644 --- a/src/org/apache/james/mime4j/field/ContentTransferEncodingField.java +++ b/src/org/apache/james/mime4j/field/ContentTransferEncodingFieldImpl.java @@ -19,70 +19,47 @@ package org.apache.james.mime4j.field; - +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.field.ContentTransferEncodingField; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.MimeUtil; /** * Represents a Content-Transfer-Encoding field. - * - * - * @version $Id: ContentTransferEncodingField.java,v 1.2 2004/10/02 12:41:11 ntherning Exp $ */ -public class ContentTransferEncodingField extends Field { - /** - * The 7bit encoding. - */ - public static final String ENC_7BIT = "7bit"; - /** - * The 8bit encoding. - */ - public static final String ENC_8BIT = "8bit"; - /** - * The binary encoding. - */ - public static final String ENC_BINARY = "binary"; - /** - * The quoted-printable encoding. - */ - public static final String ENC_QUOTED_PRINTABLE = "quoted-printable"; - /** - * The base64 encoding. - */ - public static final String ENC_BASE64 = "base64"; - +public class ContentTransferEncodingFieldImpl extends AbstractField implements ContentTransferEncodingField { private String encoding; - - protected ContentTransferEncodingField(String name, String body, String raw, String encoding) { - super(name, body, raw); - this.encoding = encoding; + + ContentTransferEncodingFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) { + super(name, body, raw, monitor); + encoding = body.trim().toLowerCase(); } /** - * Gets the encoding defined in this field. - * - * @return the encoding or an empty string if not set. + * @see org.apache.james.mime4j.dom.field.ContentTransferEncodingField#getEncoding() */ public String getEncoding() { return encoding; } - + /** - * Gets the encoding of the given field if. Returns the default - * 7bit if not set or if - * f is null. - * + * Gets the encoding of the given field if. Returns the default + * 7bit if not set or if f is + * null. + * * @return the encoding. */ public static String getEncoding(ContentTransferEncodingField f) { if (f != null && f.getEncoding().length() != 0) { return f.getEncoding(); } - return ENC_7BIT; + return MimeUtil.ENC_7BIT; } - - public static class Parser implements FieldParser { - public Field parse(final String name, final String body, final String raw) { - final String encoding = body.trim().toLowerCase(); - return new ContentTransferEncodingField(name, body, raw, encoding); + + static final FieldParser PARSER = new FieldParser() { + public ContentTransferEncodingFieldImpl parse(final String name, final String body, + final ByteSequence raw, DecodeMonitor monitor) { + return new ContentTransferEncodingFieldImpl(name, body, raw, monitor); } - } + }; } diff --git a/src/org/apache/james/mime4j/field/ContentTypeField.java b/src/org/apache/james/mime4j/field/ContentTypeField.java deleted file mode 100644 index 01f640cce..000000000 --- a/src/org/apache/james/mime4j/field/ContentTypeField.java +++ /dev/null @@ -1,256 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.field; - -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.james.mime4j.field.contenttype.parser.ContentTypeParser; -import org.apache.james.mime4j.field.contenttype.parser.ParseException; -import org.apache.james.mime4j.field.contenttype.parser.TokenMgrError; - -/** - * Represents a Content-Type field. - * - *

TODO: Remove dependency on Java 1.4 regexps

- * - * - * @version $Id: ContentTypeField.java,v 1.6 2005/01/27 14:16:31 ntherning Exp $ - */ -public class ContentTypeField extends Field { - - /** - * The prefix of all multipart MIME types. - */ - public static final String TYPE_MULTIPART_PREFIX = "multipart/"; - /** - * The multipart/digest MIME type. - */ - public static final String TYPE_MULTIPART_DIGEST = "multipart/digest"; - /** - * The text/plain MIME type. - */ - public static final String TYPE_TEXT_PLAIN = "text/plain"; - /** - * The message/rfc822 MIME type. - */ - public static final String TYPE_MESSAGE_RFC822 = "message/rfc822"; - /** - * The name of the boundary parameter. - */ - public static final String PARAM_BOUNDARY = "boundary"; - /** - * The name of the charset parameter. - */ - public static final String PARAM_CHARSET = "charset"; - - private String mimeType = ""; - private Map parameters = null; - private ParseException parseException; - - protected ContentTypeField(String name, String body, String raw, String mimeType, Map parameters, ParseException parseException) { - super(name, body, raw); - this.mimeType = mimeType; - this.parameters = parameters; - this.parseException = parseException; - } - - /** - * Gets the exception that was raised during parsing of - * the field value, if any; otherwise, null. - */ - public ParseException getParseException() { - return parseException; - } - - /** - * Gets the MIME type defined in this Content-Type field. - * - * @return the MIME type or an empty string if not set. - */ - public String getMimeType() { - return mimeType; - } - - /** - * Gets the MIME type defined in the child's - * Content-Type field or derives a MIME type from the parent - * if child is null or hasn't got a MIME type value set. - * If child's MIME type is multipart but no boundary - * has been set the MIME type of child will be derived from - * the parent. - * - * @param child the child. - * @param parent the parent. - * @return the MIME type. - */ - public static String getMimeType(ContentTypeField child, - ContentTypeField parent) { - - if (child == null || child.getMimeType().length() == 0 - || child.isMultipart() && child.getBoundary() == null) { - - if (parent != null && parent.isMimeType(TYPE_MULTIPART_DIGEST)) { - return TYPE_MESSAGE_RFC822; - } else { - return TYPE_TEXT_PLAIN; - } - } - - return child.getMimeType(); - } - - /** - * Gets the value of a parameter. Parameter names are case-insensitive. - * - * @param name the name of the parameter to get. - * @return the parameter value or null if not set. - */ - public String getParameter(String name) { - return parameters != null - ? (String) parameters.get(name.toLowerCase()) - : null; - } - - /** - * Gets all parameters. - * - * @return the parameters. - */ - public Map getParameters() { - return parameters != null - ? Collections.unmodifiableMap(parameters) - : Collections.EMPTY_MAP; - } - - /** - * Gets the value of the boundary parameter if set. - * - * @return the boundary parameter value or null - * if not set. - */ - public String getBoundary() { - return getParameter(PARAM_BOUNDARY); - } - - /** - * Gets the value of the charset parameter if set. - * - * @return the charset parameter value or null - * if not set. - */ - public String getCharset() { - return getParameter(PARAM_CHARSET); - } - - /** - * Gets the value of the charset parameter if set for the - * given field. Returns the default us-ascii if not set or if - * f is null. - * - * @return the charset parameter value. - */ - public static String getCharset(ContentTypeField f) { - if (f != null) { - if (f.getCharset() != null && f.getCharset().length() > 0) { - return f.getCharset(); - } - } - return "us-ascii"; - } - - /** - * Determines if the MIME type of this field matches the given one. - * - * @param mimeType the MIME type to match against. - * @return true if the MIME type of this field matches, - * false otherwise. - */ - public boolean isMimeType(String mimeType) { - return this.mimeType.equalsIgnoreCase(mimeType); - } - - /** - * Determines if the MIME type of this field is multipart/*. - * - * @return true if this field is has a multipart/* - * MIME type, false otherwise. - */ - public boolean isMultipart() { - return mimeType.startsWith(TYPE_MULTIPART_PREFIX); - } - - public static class Parser implements FieldParser { - private static Log log = LogFactory.getLog(Parser.class); - - public Field parse(final String name, final String body, final String raw) { - ParseException parseException = null; - String mimeType = ""; - Map parameters = null; - - ContentTypeParser parser = new ContentTypeParser(new StringReader(body)); - try { - parser.parseAll(); - } - catch (ParseException e) { - if (log.isDebugEnabled()) { - log.debug("Parsing value '" + body + "': "+ e.getMessage()); - } - parseException = e; - } - catch (TokenMgrError e) { - if (log.isDebugEnabled()) { - log.debug("Parsing value '" + body + "': "+ e.getMessage()); - } - parseException = new ParseException(e.getMessage()); - } - - try { - final String type = parser.getType(); - final String subType = parser.getSubType(); - - if (type != null && subType != null) { - mimeType = (type + "/" + parser.getSubType()).toLowerCase(); - - ArrayList paramNames = parser.getParamNames(); - ArrayList paramValues = parser.getParamValues(); - - if (paramNames != null && paramValues != null) { - for (int i = 0; i < paramNames.size() && i < paramValues.size(); i++) { - if (parameters == null) - parameters = new HashMap((int)(paramNames.size() * 1.3 + 1)); - String paramName = ((String)paramNames.get(i)).toLowerCase(); - String paramValue = ((String)paramValues.get(i)); - parameters.put(paramName, paramValue); - } - } - } - } - catch (NullPointerException npe) { - } - return new ContentTypeField(name, body, raw, mimeType, parameters, parseException); - } - } -} diff --git a/src/org/apache/james/mime4j/field/ContentTypeFieldImpl.java b/src/org/apache/james/mime4j/field/ContentTypeFieldImpl.java new file mode 100644 index 000000000..2454a4d84 --- /dev/null +++ b/src/org/apache/james/mime4j/field/ContentTypeFieldImpl.java @@ -0,0 +1,208 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +import java.io.StringReader; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.field.ContentTypeField; +import org.apache.james.mime4j.field.contenttype.parser.ContentTypeParser; +import org.apache.james.mime4j.field.contenttype.parser.ParseException; +import org.apache.james.mime4j.field.contenttype.parser.TokenMgrError; +import org.apache.james.mime4j.util.ByteSequence; + +/** + * Represents a Content-Type field. + */ +public class ContentTypeFieldImpl extends AbstractField implements ContentTypeField { + private boolean parsed = false; + + private String mimeType = ""; + private Map parameters = new HashMap(); + private ParseException parseException; + + ContentTypeFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) { + super(name, body, raw, monitor); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentTypeField#getParseException() + */ + @Override + public ParseException getParseException() { + if (!parsed) + parse(); + + return parseException; + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentTypeField#getMimeType() + */ + public String getMimeType() { + if (!parsed) + parse(); + + return mimeType; + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentTypeField#getParameter(java.lang.String) + */ + public String getParameter(String name) { + if (!parsed) + parse(); + + return parameters.get(name.toLowerCase()); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentTypeField#getParameters() + */ + public Map getParameters() { + if (!parsed) + parse(); + + return Collections.unmodifiableMap(parameters); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentTypeField#isMimeType(java.lang.String) + */ + public boolean isMimeType(String mimeType) { + if (!parsed) + parse(); + + return this.mimeType.equalsIgnoreCase(mimeType); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentTypeField#isMultipart() + */ + public boolean isMultipart() { + if (!parsed) + parse(); + + return mimeType.startsWith(TYPE_MULTIPART_PREFIX); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentTypeField#getBoundary() + */ + public String getBoundary() { + return getParameter(PARAM_BOUNDARY); + } + + /** + * @see org.apache.james.mime4j.dom.field.ContentTypeField#getCharset() + */ + public String getCharset() { + return getParameter(PARAM_CHARSET); + } + + /** + * Gets the MIME type defined in the child's Content-Type field or derives a + * MIME type from the parent if child is null or hasn't got a + * MIME type value set. If child's MIME type is multipart but no boundary + * has been set the MIME type of child will be derived from the parent. + * + * @param child + * the child. + * @param parent + * the parent. + * @return the MIME type. + */ + public static String getMimeType(ContentTypeField child, + ContentTypeField parent) { + if (child == null || child.getMimeType().length() == 0 + || child.isMultipart() && child.getBoundary() == null) { + + if (parent != null && parent.isMimeType(TYPE_MULTIPART_DIGEST)) { + return TYPE_MESSAGE_RFC822; + } else { + return TYPE_TEXT_PLAIN; + } + } + + return child.getMimeType(); + } + + /** + * Gets the value of the charset parameter if set for the + * given field. Returns the default us-ascii if not set or if + * f is null. + * + * @return the charset parameter value. + */ + public static String getCharset(ContentTypeField f) { + if (f != null) { + String charset = f.getCharset(); + if (charset != null && charset.length() > 0) { + return charset; + } + } + return "us-ascii"; + } + + private void parse() { + String body = getBody(); + + ContentTypeParser parser = new ContentTypeParser(new StringReader(body)); + try { + parser.parseAll(); + } catch (ParseException e) { + parseException = e; + } catch (TokenMgrError e) { + parseException = new ParseException(e.getMessage()); + } + + final String type = parser.getType(); + final String subType = parser.getSubType(); + + if (type != null && subType != null) { + mimeType = (type + "/" + subType).toLowerCase(); + + List paramNames = parser.getParamNames(); + List paramValues = parser.getParamValues(); + + if (paramNames != null && paramValues != null) { + final int len = Math.min(paramNames.size(), paramValues.size()); + for (int i = 0; i < len; i++) { + String paramName = paramNames.get(i).toLowerCase(); + String paramValue = paramValues.get(i); + parameters.put(paramName, paramValue); + } + } + } + + parsed = true; + } + + static final FieldParser PARSER = new FieldParser() { + public ContentTypeFieldImpl parse(final String name, final String body, + final ByteSequence raw, DecodeMonitor monitor) { + return new ContentTypeFieldImpl(name, body, raw, monitor); + } + }; +} diff --git a/src/org/apache/james/mime4j/field/DateTimeField.java b/src/org/apache/james/mime4j/field/DateTimeField.java deleted file mode 100644 index fa34ceda4..000000000 --- a/src/org/apache/james/mime4j/field/DateTimeField.java +++ /dev/null @@ -1,65 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.field; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.james.mime4j.field.datetime.DateTime; -import org.apache.james.mime4j.field.datetime.parser.ParseException; - -import java.util.Date; - -public class DateTimeField extends Field { - private Date date; - private ParseException parseException; - - protected DateTimeField(String name, String body, String raw, Date date, ParseException parseException) { - super(name, body, raw); - this.date = date; - this.parseException = parseException; - } - - public Date getDate() { - return date; - } - - public ParseException getParseException() { - return parseException; - } - - public static class Parser implements FieldParser { - private static Log log = LogFactory.getLog(Parser.class); - - public Field parse(final String name, final String body, final String raw) { - Date date = null; - ParseException parseException = null; - try { - date = DateTime.parse(body).getDate(); - } - catch (ParseException e) { - if (log.isDebugEnabled()) { - log.debug("Parsing value '" + body + "': "+ e.getMessage()); - } - parseException = e; - } - return new DateTimeField(name, body, raw, date, parseException); - } - } -} diff --git a/src/org/apache/james/mime4j/field/DateTimeFieldImpl.java b/src/org/apache/james/mime4j/field/DateTimeFieldImpl.java new file mode 100644 index 000000000..f223899d1 --- /dev/null +++ b/src/org/apache/james/mime4j/field/DateTimeFieldImpl.java @@ -0,0 +1,86 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +import java.io.StringReader; +import java.util.Date; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; +import org.apache.james.mime4j.field.datetime.parser.ParseException; +import org.apache.james.mime4j.field.datetime.parser.TokenMgrError; +import org.apache.james.mime4j.util.ByteSequence; + +/** + * Date-time field such as Date or Resent-Date. + */ +public class DateTimeFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.DateTimeField { + private boolean parsed = false; + + private Date date; + private ParseException parseException; + + DateTimeFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) { + super(name, body, raw, monitor); + } + + /** + * @see org.apache.james.mime4j.dom.field.DateTimeField#getDate() + */ + public Date getDate() { + if (!parsed) + parse(); + + return date; + } + + /** + * @see org.apache.james.mime4j.dom.field.DateTimeField#getParseException() + */ + @Override + public ParseException getParseException() { + if (!parsed) + parse(); + + return parseException; + } + + private void parse() { + String body = getBody(); + + try { + date = new DateTimeParser(new StringReader(body)).parseAll() + .getDate(); + } catch (ParseException e) { + parseException = e; + } catch (TokenMgrError e) { + parseException = new ParseException(e.getMessage()); + } + + parsed = true; + } + + static final FieldParser PARSER = new FieldParser() { + public DateTimeFieldImpl parse(final String name, final String body, + final ByteSequence raw, DecodeMonitor monitor) { + return new DateTimeFieldImpl(name, body, raw, monitor); + } + }; +} diff --git a/src/org/apache/james/mime4j/field/DefaultFieldParser.java b/src/org/apache/james/mime4j/field/DefaultFieldParser.java index 3695afe3e..befa6bd64 100644 --- a/src/org/apache/james/mime4j/field/DefaultFieldParser.java +++ b/src/org/apache/james/mime4j/field/DefaultFieldParser.java @@ -1,45 +1,133 @@ -/* - * Copyright 2006 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + package org.apache.james.mime4j.field; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.field.FieldName; +import org.apache.james.mime4j.dom.field.ParsedField; +import org.apache.james.mime4j.field.AddressListFieldImpl; +import org.apache.james.mime4j.field.ContentDispositionFieldImpl; +import org.apache.james.mime4j.field.ContentTransferEncodingFieldImpl; +import org.apache.james.mime4j.field.ContentTypeFieldImpl; +import org.apache.james.mime4j.field.DateTimeFieldImpl; +import org.apache.james.mime4j.field.MailboxFieldImpl; +import org.apache.james.mime4j.field.MailboxListFieldImpl; +import org.apache.james.mime4j.field.UnstructuredFieldImpl; +import org.apache.james.mime4j.stream.RawField; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; + public class DefaultFieldParser extends DelegatingFieldParser { + + private static final DefaultFieldParser PARSER = new DefaultFieldParser(); - public DefaultFieldParser() { - setFieldParser(Field.CONTENT_TRANSFER_ENCODING, new ContentTransferEncodingField.Parser()); - setFieldParser(Field.CONTENT_TYPE, new ContentTypeField.Parser()); - - final DateTimeField.Parser dateTimeParser = new DateTimeField.Parser(); - setFieldParser(Field.DATE, dateTimeParser); - setFieldParser(Field.RESENT_DATE, dateTimeParser); - - final MailboxListField.Parser mailboxListParser = new MailboxListField.Parser(); - setFieldParser(Field.FROM, mailboxListParser); - setFieldParser(Field.RESENT_FROM, mailboxListParser); - - final MailboxField.Parser mailboxParser = new MailboxField.Parser(); - setFieldParser(Field.SENDER, mailboxParser); - setFieldParser(Field.RESENT_SENDER, mailboxParser); - - final AddressListField.Parser addressListParser = new AddressListField.Parser(); - setFieldParser(Field.TO, addressListParser); - setFieldParser(Field.RESENT_TO, addressListParser); - setFieldParser(Field.CC, addressListParser); - setFieldParser(Field.RESENT_CC, addressListParser); - setFieldParser(Field.BCC, addressListParser); - setFieldParser(Field.RESENT_BCC, addressListParser); - setFieldParser(Field.REPLY_TO, addressListParser); + + /** + * Gets the default parser used to parse fields. + * + * @return the default field parser + */ + public static DefaultFieldParser getParser() { + return PARSER; } + + + /** + * Parses the given byte sequence and returns an instance of the + * Field class. The type of the class returned depends on the + * field name; see {@link #parse(String)} for a table of field names and + * their corresponding classes. + * + * @param raw the bytes to parse. + * @param monitor a DecodeMonitor object used while parsing/decoding. + * @return a ParsedField instance. + * @throws MimeException if the raw string cannot be split into field name and body. + */ + public static ParsedField parse( + final ByteSequence raw, + final DecodeMonitor monitor) throws MimeException { + RawField rawField = new RawField(raw); + return PARSER.parse(rawField.getName(), rawField.getBody(), raw, monitor); + } + + /** + * Parses the given string and returns an instance of the + * Field class. The type of the class returned depends on + * the field name: + *

+ * + * + * + * + * + * + * + * + * + * + *
Class returnedField names
{@link ContentTypeFieldImpl}Content-Type
{@link ContentTransferEncodingFieldImpl}Content-Transfer-Encoding
{@link ContentDispositionFieldImpl}Content-Disposition
{@link DateTimeFieldImpl}Date, Resent-Date
{@link MailboxFieldImpl}Sender, Resent-Sender
{@link MailboxListFieldImpl}From, Resent-From
{@link AddressListFieldImpl}To, Cc, Bcc, Reply-To, Resent-To, Resent-Cc, Resent-Bcc
{@link UnstructuredFieldImpl}Subject and others
+ * + * @param rawStr the string to parse. + * @return a ParsedField instance. + * @throws MimeException if the raw string cannot be split into field name and body. + */ + public static ParsedField parse( + final String rawStr, + final DecodeMonitor monitor) throws MimeException { + ByteSequence raw = ContentUtil.encode(rawStr); + return parse(raw, monitor); + } + + public static ParsedField parse(final String rawStr) throws MimeException { + ByteSequence raw = ContentUtil.encode(rawStr); + return parse(raw, DecodeMonitor.SILENT); + } + + public DefaultFieldParser() { + setFieldParser(FieldName.CONTENT_TRANSFER_ENCODING, + ContentTransferEncodingFieldImpl.PARSER); + setFieldParser(FieldName.CONTENT_TYPE, ContentTypeFieldImpl.PARSER); + setFieldParser(FieldName.CONTENT_DISPOSITION, + ContentDispositionFieldImpl.PARSER); + + final FieldParser dateTimeParser = DateTimeFieldImpl.PARSER; + setFieldParser(FieldName.DATE, dateTimeParser); + setFieldParser(FieldName.RESENT_DATE, dateTimeParser); + + final FieldParser mailboxListParser = MailboxListFieldImpl.PARSER; + setFieldParser(FieldName.FROM, mailboxListParser); + setFieldParser(FieldName.RESENT_FROM, mailboxListParser); + + final FieldParser mailboxParser = MailboxFieldImpl.PARSER; + setFieldParser(FieldName.SENDER, mailboxParser); + setFieldParser(FieldName.RESENT_SENDER, mailboxParser); + + final FieldParser addressListParser = AddressListFieldImpl.PARSER; + setFieldParser(FieldName.TO, addressListParser); + setFieldParser(FieldName.RESENT_TO, addressListParser); + setFieldParser(FieldName.CC, addressListParser); + setFieldParser(FieldName.RESENT_CC, addressListParser); + setFieldParser(FieldName.BCC, addressListParser); + setFieldParser(FieldName.RESENT_BCC, addressListParser); + setFieldParser(FieldName.REPLY_TO, addressListParser); + } + } diff --git a/src/org/apache/james/mime4j/field/DelegatingFieldParser.java b/src/org/apache/james/mime4j/field/DelegatingFieldParser.java index c06160292..2934b696a 100644 --- a/src/org/apache/james/mime4j/field/DelegatingFieldParser.java +++ b/src/org/apache/james/mime4j/field/DelegatingFieldParser.java @@ -1,47 +1,56 @@ -/* - * Copyright 2006 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + package org.apache.james.mime4j.field; import java.util.HashMap; import java.util.Map; -public class DelegatingFieldParser implements FieldParser { - - private Map parsers = new HashMap(); - private FieldParser defaultParser = new UnstructuredField.Parser(); - +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.field.ParsedField; +import org.apache.james.mime4j.field.UnstructuredFieldImpl; +import org.apache.james.mime4j.util.ByteSequence; + +public class DelegatingFieldParser implements FieldParser { + private static final FieldParser DEFAULT_PARSER = UnstructuredFieldImpl.PARSER; + + private Map> parsers = new HashMap>(); + /** * Sets the parser used for the field named name. * @param name the name of the field * @param parser the parser for fields named name */ - public void setFieldParser(final String name, final FieldParser parser) { + public void setFieldParser(final String name, final FieldParser parser) { parsers.put(name.toLowerCase(), parser); } - public FieldParser getParser(final String name) { - final FieldParser field = (FieldParser) parsers.get(name.toLowerCase()); - if(field==null) { - return defaultParser; + public FieldParser getParser(final String name) { + final FieldParser field = parsers.get(name.toLowerCase()); + if (field == null) { + return DEFAULT_PARSER; } return field; } - public Field parse(final String name, final String body, final String raw) { - final FieldParser parser = getParser(name); - return parser.parse(name, body, raw); + public ParsedField parse(final String name, final String body, final ByteSequence raw, DecodeMonitor monitor) { + final FieldParser parser = getParser(name); + return parser.parse(name, body, raw, monitor); } } diff --git a/src/org/apache/james/mime4j/field/Field.java b/src/org/apache/james/mime4j/field/Field.java deleted file mode 100644 index 4dea5c5cf..000000000 --- a/src/org/apache/james/mime4j/field/Field.java +++ /dev/null @@ -1,192 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.field; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * The base class of all field classes. - * - * - * @version $Id: Field.java,v 1.6 2004/10/25 07:26:46 ntherning Exp $ - */ -public abstract class Field { - public static final String SENDER = "Sender"; - public static final String FROM = "From"; - public static final String TO = "To"; - public static final String CC = "Cc"; - public static final String BCC = "Bcc"; - public static final String REPLY_TO = "Reply-To"; - public static final String RESENT_SENDER = "Resent-Sender"; - public static final String RESENT_FROM = "Resent-From"; - public static final String RESENT_TO = "Resent-To"; - public static final String RESENT_CC = "Resent-Cc"; - public static final String RESENT_BCC = "Resent-Bcc"; - - public static final String DATE = "Date"; - public static final String RESENT_DATE = "Resent-Date"; - - public static final String SUBJECT = "Subject"; - public static final String CONTENT_TYPE = "Content-Type"; - public static final String CONTENT_TRANSFER_ENCODING = - "Content-Transfer-Encoding"; - - private static final String FIELD_NAME_PATTERN = - "^([\\x21-\\x39\\x3b-\\x7e]+)[ \t]*:"; - private static final Pattern fieldNamePattern = - Pattern.compile(FIELD_NAME_PATTERN); - - private static final DefaultFieldParser parser = new DefaultFieldParser(); - - private final String name; - private final String body; - private final String raw; - - protected Field(final String name, final String body, final String raw) { - this.name = name; - this.body = body; - this.raw = raw; - } - - /** - * Parses the given string and returns an instance of the - * Field class. The type of the class returned depends on - * the field name: - * - * - * - * - * - * - *
Field nameClass returnedContent-Typeorg.apache.james.mime4j.field.ContentTypeFieldotherorg.apache.james.mime4j.field.UnstructuredField
- * - * @param s the string to parse. - * @return a Field instance. - * @throws IllegalArgumentException on parse errors. - */ - public static Field parse(final String raw) { - - /* - * Unfold the field. - */ - final String unfolded = raw.replaceAll("\r|\n", ""); - - /* - * Split into name and value. - */ - final Matcher fieldMatcher = fieldNamePattern.matcher(unfolded); - if (!fieldMatcher.find()) { - throw new IllegalArgumentException("Invalid field in string"); - } - final String name = fieldMatcher.group(1); - - String body = unfolded.substring(fieldMatcher.end()); - if (body.length() > 0 && body.charAt(0) == ' ') { - body = body.substring(1); - } - - return parser.parse(name, body, raw); - } - - /** - * Gets the default parser used to parse fields. - * @return the default field parser - */ - public static DefaultFieldParser getParser() { - return parser; - } - - /** - * Gets the name of the field (Subject, - * From, etc). - * - * @return the field name. - */ - public String getName() { - return name; - } - - /** - * Gets the original raw field string. - * - * @return the original raw field string. - */ - public String getRaw() { - return raw; - } - - /** - * Gets the unfolded, unparsed and possibly encoded (see RFC 2047) field - * body string. - * - * @return the unfolded unparsed field body string. - */ - public String getBody() { - return body; - } - - /** - * Determines if this is a Content-Type field. - * - * @return true if this is a Content-Type field, - * false otherwise. - */ - public boolean isContentType() { - return CONTENT_TYPE.equalsIgnoreCase(name); - } - - /** - * Determines if this is a Subject field. - * - * @return true if this is a Subject field, - * false otherwise. - */ - public boolean isSubject() { - return SUBJECT.equalsIgnoreCase(name); - } - - /** - * Determines if this is a From field. - * - * @return true if this is a From field, - * false otherwise. - */ - public boolean isFrom() { - return FROM.equalsIgnoreCase(name); - } - - /** - * Determines if this is a To field. - * - * @return true if this is a To field, - * false otherwise. - */ - public boolean isTo() { - return TO.equalsIgnoreCase(name); - } - - /** - * @see #getRaw() - */ - public String toString() { - return raw; - } -} diff --git a/src/org/apache/james/mime4j/field/FieldParser.java b/src/org/apache/james/mime4j/field/FieldParser.java index 78aaf1334..fc19ddd58 100644 --- a/src/org/apache/james/mime4j/field/FieldParser.java +++ b/src/org/apache/james/mime4j/field/FieldParser.java @@ -1,21 +1,30 @@ -/* - * Copyright 2006 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + package org.apache.james.mime4j.field; -public interface FieldParser { +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.field.ParsedField; +import org.apache.james.mime4j.util.ByteSequence; + +public interface FieldParser { + + T parse(final String name, final String body, final ByteSequence raw, DecodeMonitor monitor); - Field parse(final String name, final String body, final String raw); } diff --git a/src/org/apache/james/mime4j/field/Fields.java b/src/org/apache/james/mime4j/field/Fields.java new file mode 100644 index 000000000..cf2f3e5f6 --- /dev/null +++ b/src/org/apache/james/mime4j/field/Fields.java @@ -0,0 +1,643 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; +import java.util.regex.Pattern; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.codec.EncoderUtil; +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.field.AddressListField; +import org.apache.james.mime4j.dom.field.ContentDispositionField; +import org.apache.james.mime4j.dom.field.ContentTransferEncodingField; +import org.apache.james.mime4j.dom.field.ContentTypeField; +import org.apache.james.mime4j.dom.field.DateTimeField; +import org.apache.james.mime4j.dom.field.Field; +import org.apache.james.mime4j.dom.field.FieldName; +import org.apache.james.mime4j.dom.field.MailboxField; +import org.apache.james.mime4j.dom.field.MailboxListField; +import org.apache.james.mime4j.dom.field.ParsedField; +import org.apache.james.mime4j.dom.field.UnstructuredField; +import org.apache.james.mime4j.field.AddressListFieldImpl; +import org.apache.james.mime4j.field.ContentDispositionFieldImpl; +import org.apache.james.mime4j.field.ContentTransferEncodingFieldImpl; +import org.apache.james.mime4j.field.ContentTypeFieldImpl; +import org.apache.james.mime4j.field.DateTimeFieldImpl; +import org.apache.james.mime4j.field.MailboxFieldImpl; +import org.apache.james.mime4j.field.MailboxListFieldImpl; +import org.apache.james.mime4j.field.UnstructuredFieldImpl; +import org.apache.james.mime4j.field.address.formatter.AddressFormatter; +import org.apache.james.mime4j.stream.RawField; +import org.apache.james.mime4j.util.MimeUtil; + +/** + * Factory for concrete {@link Field} instances. + */ +public class Fields { + + private static final Pattern FIELD_NAME_PATTERN = Pattern + .compile("[\\x21-\\x39\\x3b-\\x7e]+"); + + private Fields() { + } + + /** + * Creates a Content-Type field from the specified raw field value. + * The specified string gets folded into a multiple-line representation if + * necessary but is otherwise taken as is. + * + * @param contentType + * raw content type containing a MIME type and optional + * parameters. + * @return the newly created Content-Type field. + */ + public static ContentTypeField contentType(String contentType) { + return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE, + contentType); + } + + /** + * Creates a Content-Type field from the specified MIME type and + * parameters. + * + * @param mimeType + * a MIME type (such as "text/plain" or + * "application/octet-stream"). + * @param parameters + * map containing content-type parameters such as + * "boundary". + * @return the newly created Content-Type field. + */ + public static ContentTypeField contentType(String mimeType, + Map parameters) { + if (!isValidMimeType(mimeType)) + throw new IllegalArgumentException(); + + if (parameters == null || parameters.isEmpty()) { + return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE, + mimeType); + } else { + StringBuilder sb = new StringBuilder(mimeType); + for (Map.Entry entry : parameters.entrySet()) { + sb.append("; "); + sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(), + entry.getValue())); + } + String contentType = sb.toString(); + return contentType(contentType); + } + } + + /** + * Creates a Content-Transfer-Encoding field from the specified raw + * field value. + * + * @param contentTransferEncoding + * an encoding mechanism such as "7-bit" + * or "quoted-printable". + * @return the newly created Content-Transfer-Encoding field. + */ + public static ContentTransferEncodingField contentTransferEncoding( + String contentTransferEncoding) { + return parse(ContentTransferEncodingFieldImpl.PARSER, + FieldName.CONTENT_TRANSFER_ENCODING, contentTransferEncoding); + } + + /** + * Creates a Content-Disposition field from the specified raw field + * value. The specified string gets folded into a multiple-line + * representation if necessary but is otherwise taken as is. + * + * @param contentDisposition + * raw content disposition containing a disposition type and + * optional parameters. + * @return the newly created Content-Disposition field. + */ + public static ContentDispositionField contentDisposition( + String contentDisposition) { + return parse(ContentDispositionFieldImpl.PARSER, + FieldName.CONTENT_DISPOSITION, contentDisposition); + } + + /** + * Creates a Content-Disposition field from the specified + * disposition type and parameters. + * + * @param dispositionType + * a disposition type (usually "inline" + * or "attachment"). + * @param parameters + * map containing disposition parameters such as + * "filename". + * @return the newly created Content-Disposition field. + */ + public static ContentDispositionField contentDisposition( + String dispositionType, Map parameters) { + if (!isValidDispositionType(dispositionType)) + throw new IllegalArgumentException(); + + if (parameters == null || parameters.isEmpty()) { + return parse(ContentDispositionFieldImpl.PARSER, + FieldName.CONTENT_DISPOSITION, dispositionType); + } else { + StringBuilder sb = new StringBuilder(dispositionType); + for (Map.Entry entry : parameters.entrySet()) { + sb.append("; "); + sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(), + entry.getValue())); + } + String contentDisposition = sb.toString(); + return contentDisposition(contentDisposition); + } + } + + /** + * Creates a Content-Disposition field from the specified + * disposition type and filename. + * + * @param dispositionType + * a disposition type (usually "inline" + * or "attachment"). + * @param filename + * filename parameter value or null if the + * parameter should not be included. + * @return the newly created Content-Disposition field. + */ + public static ContentDispositionField contentDisposition( + String dispositionType, String filename) { + return contentDisposition(dispositionType, filename, -1, null, null, + null); + } + + /** + * Creates a Content-Disposition field from the specified values. + * + * @param dispositionType + * a disposition type (usually "inline" + * or "attachment"). + * @param filename + * filename parameter value or null if the + * parameter should not be included. + * @param size + * size parameter value or -1 if the parameter + * should not be included. + * @return the newly created Content-Disposition field. + */ + public static ContentDispositionField contentDisposition( + String dispositionType, String filename, long size) { + return contentDisposition(dispositionType, filename, size, null, null, + null); + } + + /** + * Creates a Content-Disposition field from the specified values. + * + * @param dispositionType + * a disposition type (usually "inline" + * or "attachment"). + * @param filename + * filename parameter value or null if the + * parameter should not be included. + * @param size + * size parameter value or -1 if the parameter + * should not be included. + * @param creationDate + * creation-date parameter value or null if the + * parameter should not be included. + * @param modificationDate + * modification-date parameter value or null if + * the parameter should not be included. + * @param readDate + * read-date parameter value or null if the + * parameter should not be included. + * @return the newly created Content-Disposition field. + */ + public static ContentDispositionField contentDisposition( + String dispositionType, String filename, long size, + Date creationDate, Date modificationDate, Date readDate) { + Map parameters = new HashMap(); + if (filename != null) { + parameters.put(ContentDispositionFieldImpl.PARAM_FILENAME, filename); + } + if (size >= 0) { + parameters.put(ContentDispositionFieldImpl.PARAM_SIZE, Long + .toString(size)); + } + if (creationDate != null) { + parameters.put(ContentDispositionFieldImpl.PARAM_CREATION_DATE, + MimeUtil.formatDate(creationDate, null)); + } + if (modificationDate != null) { + parameters.put(ContentDispositionFieldImpl.PARAM_MODIFICATION_DATE, + MimeUtil.formatDate(modificationDate, null)); + } + if (readDate != null) { + parameters.put(ContentDispositionFieldImpl.PARAM_READ_DATE, MimeUtil + .formatDate(readDate, null)); + } + return contentDisposition(dispositionType, parameters); + } + + /** + * Creates a Date field from the specified Date + * value. The default time zone of the host is used to format the date. + * + * @param date + * date value for the header field. + * @return the newly created Date field. + */ + public static DateTimeField date(Date date) { + return date0(FieldName.DATE, date, null); + } + + /** + * Creates a date field from the specified field name and Date + * value. The default time zone of the host is used to format the date. + * + * @param fieldName + * a field name such as Date or + * Resent-Date. + * @param date + * date value for the header field. + * @return the newly created date field. + */ + public static DateTimeField date(String fieldName, Date date) { + checkValidFieldName(fieldName); + return date0(fieldName, date, null); + } + + /** + * Creates a date field from the specified field name, Date + * and TimeZone values. + * + * @param fieldName + * a field name such as Date or + * Resent-Date. + * @param date + * date value for the header field. + * @param zone + * the time zone to be used for formatting the date. + * @return the newly created date field. + */ + public static DateTimeField date(String fieldName, Date date, TimeZone zone) { + checkValidFieldName(fieldName); + return date0(fieldName, date, zone); + } + + /** + * Creates a Message-ID field for the specified host name. + * + * @param hostname + * host name to be included in the message ID or + * null if no host name should be included. + * @return the newly created Message-ID field. + */ + public static UnstructuredField messageId(String hostname) { + String fieldValue = MimeUtil.createUniqueMessageId(hostname); + return parse(UnstructuredFieldImpl.PARSER, FieldName.MESSAGE_ID, fieldValue); + } + + /** + * Creates a Subject field from the specified string value. The + * specified string may contain non-ASCII characters. + * + * @param subject + * the subject string. + * @return the newly created Subject field. + */ + public static UnstructuredField subject(String subject) { + int usedCharacters = FieldName.SUBJECT.length() + 2; + String fieldValue = EncoderUtil.encodeIfNecessary(subject, + EncoderUtil.Usage.TEXT_TOKEN, usedCharacters); + + return parse(UnstructuredFieldImpl.PARSER, FieldName.SUBJECT, fieldValue); + } + + /** + * Creates a Sender field for the specified mailbox address. + * + * @param mailbox + * address to be included in the field. + * @return the newly created Sender field. + */ + public static MailboxField sender(Mailbox mailbox) { + return mailbox0(FieldName.SENDER, mailbox); + } + + /** + * Creates a From field for the specified mailbox address. + * + * @param mailbox + * address to be included in the field. + * @return the newly created From field. + */ + public static MailboxListField from(Mailbox mailbox) { + return mailboxList0(FieldName.FROM, Collections.singleton(mailbox)); + } + + /** + * Creates a From field for the specified mailbox addresses. + * + * @param mailboxes + * addresses to be included in the field. + * @return the newly created From field. + */ + public static MailboxListField from(Mailbox... mailboxes) { + return mailboxList0(FieldName.FROM, Arrays.asList(mailboxes)); + } + + /** + * Creates a From field for the specified mailbox addresses. + * + * @param mailboxes + * addresses to be included in the field. + * @return the newly created From field. + */ + public static MailboxListField from(Iterable mailboxes) { + return mailboxList0(FieldName.FROM, mailboxes); + } + + /** + * Creates a To field for the specified mailbox or group address. + * + * @param address + * mailbox or group address to be included in the field. + * @return the newly created To field. + */ + public static AddressListField to(Address address) { + return addressList0(FieldName.TO, Collections.singleton(address)); + } + + /** + * Creates a To field for the specified mailbox or group addresses. + * + * @param addresses + * mailbox or group addresses to be included in the field. + * @return the newly created To field. + */ + public static AddressListField to(Address... addresses) { + return addressList0(FieldName.TO, Arrays.asList(addresses)); + } + + /** + * Creates a To field for the specified mailbox or group addresses. + * + * @param addresses + * mailbox or group addresses to be included in the field. + * @return the newly created To field. + */ + public static AddressListField to(Iterable

addresses) { + return addressList0(FieldName.TO, addresses); + } + + /** + * Creates a Cc field for the specified mailbox or group address. + * + * @param address + * mailbox or group address to be included in the field. + * @return the newly created Cc field. + */ + public static AddressListField cc(Address address) { + return addressList0(FieldName.CC, Collections.singleton(address)); + } + + /** + * Creates a Cc field for the specified mailbox or group addresses. + * + * @param addresses + * mailbox or group addresses to be included in the field. + * @return the newly created Cc field. + */ + public static AddressListField cc(Address... addresses) { + return addressList0(FieldName.CC, Arrays.asList(addresses)); + } + + /** + * Creates a Cc field for the specified mailbox or group addresses. + * + * @param addresses + * mailbox or group addresses to be included in the field. + * @return the newly created Cc field. + */ + public static AddressListField cc(Iterable
addresses) { + return addressList0(FieldName.CC, addresses); + } + + /** + * Creates a Bcc field for the specified mailbox or group address. + * + * @param address + * mailbox or group address to be included in the field. + * @return the newly created Bcc field. + */ + public static AddressListField bcc(Address address) { + return addressList0(FieldName.BCC, Collections.singleton(address)); + } + + /** + * Creates a Bcc field for the specified mailbox or group addresses. + * + * @param addresses + * mailbox or group addresses to be included in the field. + * @return the newly created Bcc field. + */ + public static AddressListField bcc(Address... addresses) { + return addressList0(FieldName.BCC, Arrays.asList(addresses)); + } + + /** + * Creates a Bcc field for the specified mailbox or group addresses. + * + * @param addresses + * mailbox or group addresses to be included in the field. + * @return the newly created Bcc field. + */ + public static AddressListField bcc(Iterable
addresses) { + return addressList0(FieldName.BCC, addresses); + } + + /** + * Creates a Reply-To field for the specified mailbox or group + * address. + * + * @param address + * mailbox or group address to be included in the field. + * @return the newly created Reply-To field. + */ + public static AddressListField replyTo(Address address) { + return addressList0(FieldName.REPLY_TO, Collections.singleton(address)); + } + + /** + * Creates a Reply-To field for the specified mailbox or group + * addresses. + * + * @param addresses + * mailbox or group addresses to be included in the field. + * @return the newly created Reply-To field. + */ + public static AddressListField replyTo(Address... addresses) { + return addressList0(FieldName.REPLY_TO, Arrays.asList(addresses)); + } + + /** + * Creates a Reply-To field for the specified mailbox or group + * addresses. + * + * @param addresses + * mailbox or group addresses to be included in the field. + * @return the newly created Reply-To field. + */ + public static AddressListField replyTo(Iterable
addresses) { + return addressList0(FieldName.REPLY_TO, addresses); + } + + /** + * Creates a mailbox field from the specified field name and mailbox + * address. Valid field names are Sender and + * Resent-Sender. + * + * @param fieldName + * the name of the mailbox field (Sender or + * Resent-Sender). + * @param mailbox + * mailbox address for the field value. + * @return the newly created mailbox field. + */ + public static MailboxField mailbox(String fieldName, Mailbox mailbox) { + checkValidFieldName(fieldName); + return mailbox0(fieldName, mailbox); + } + + /** + * Creates a mailbox-list field from the specified field name and mailbox + * addresses. Valid field names are From and + * Resent-From. + * + * @param fieldName + * the name of the mailbox field (From or + * Resent-From). + * @param mailboxes + * mailbox addresses for the field value. + * @return the newly created mailbox-list field. + */ + public static MailboxListField mailboxList(String fieldName, + Iterable mailboxes) { + checkValidFieldName(fieldName); + return mailboxList0(fieldName, mailboxes); + } + + /** + * Creates an address-list field from the specified field name and mailbox + * or group addresses. Valid field names are To, + * Cc, Bcc, Reply-To, + * Resent-To, Resent-Cc and + * Resent-Bcc. + * + * @param fieldName + * the name of the mailbox field (To, + * Cc, Bcc, Reply-To, + * Resent-To, Resent-Cc or + * Resent-Bcc). + * @param addresses + * mailbox or group addresses for the field value. + * @return the newly created address-list field. + */ + public static AddressListField addressList(String fieldName, + Iterable
addresses) { + checkValidFieldName(fieldName); + return addressList0(fieldName, addresses); + } + + private static DateTimeField date0(String fieldName, Date date, + TimeZone zone) { + final String formattedDate = MimeUtil.formatDate(date, zone); + return parse(DateTimeFieldImpl.PARSER, fieldName, formattedDate); + } + + private static MailboxField mailbox0(String fieldName, Mailbox mailbox) { + String fieldValue = encodeAddresses(Collections.singleton(mailbox)); + return parse(MailboxFieldImpl.PARSER, fieldName, fieldValue); + } + + private static MailboxListField mailboxList0(String fieldName, + Iterable mailboxes) { + String fieldValue = encodeAddresses(mailboxes); + return parse(MailboxListFieldImpl.PARSER, fieldName, fieldValue); + } + + private static AddressListField addressList0(String fieldName, + Iterable
addresses) { + String fieldValue = encodeAddresses(addresses); + return parse(AddressListFieldImpl.PARSER, fieldName, fieldValue); + } + + private static void checkValidFieldName(String fieldName) { + if (!FIELD_NAME_PATTERN.matcher(fieldName).matches()) + throw new IllegalArgumentException("Invalid field name"); + } + + private static boolean isValidMimeType(String mimeType) { + if (mimeType == null) + return false; + + int idx = mimeType.indexOf('/'); + if (idx == -1) + return false; + + String type = mimeType.substring(0, idx); + String subType = mimeType.substring(idx + 1); + return EncoderUtil.isToken(type) && EncoderUtil.isToken(subType); + } + + private static boolean isValidDispositionType(String dispositionType) { + if (dispositionType == null) + return false; + + return EncoderUtil.isToken(dispositionType); + } + + private static F parse(FieldParser parser, + String fieldName, String fieldBody) { + RawField rawField = new RawField(fieldName, fieldBody); + return parser.parse(rawField.getName(), rawField.getBody(), rawField.getRaw(), + DecodeMonitor.SILENT); + } + + private static String encodeAddresses(Iterable addresses) { + StringBuilder sb = new StringBuilder(); + + for (Address address : addresses) { + if (sb.length() > 0) { + sb.append(", "); + } + AddressFormatter.encode(sb, address); + } + return sb.toString(); + } + +} diff --git a/src/org/apache/james/mime4j/field/MailboxField.java b/src/org/apache/james/mime4j/field/MailboxField.java deleted file mode 100644 index 867a38a13..000000000 --- a/src/org/apache/james/mime4j/field/MailboxField.java +++ /dev/null @@ -1,68 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.field; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.james.mime4j.field.address.AddressList; -import org.apache.james.mime4j.field.address.Mailbox; -import org.apache.james.mime4j.field.address.MailboxList; -import org.apache.james.mime4j.field.address.parser.ParseException; - -public class MailboxField extends Field { - private final Mailbox mailbox; - private final ParseException parseException; - - protected MailboxField(final String name, final String body, final String raw, final Mailbox mailbox, final ParseException parseException) { - super(name, body, raw); - this.mailbox = mailbox; - this.parseException = parseException; - } - - public Mailbox getMailbox() { - return mailbox; - } - - public ParseException getParseException() { - return parseException; - } - - public static class Parser implements FieldParser { - private static Log log = LogFactory.getLog(Parser.class); - - public Field parse(final String name, final String body, final String raw) { - Mailbox mailbox = null; - ParseException parseException = null; - try { - MailboxList mailboxList = AddressList.parse(body).flatten(); - if (mailboxList.size() > 0) { - mailbox = mailboxList.get(0); - } - } - catch (ParseException e) { - if (log.isDebugEnabled()) { - log.debug("Parsing value '" + body + "': "+ e.getMessage()); - } - parseException = e; - } - return new MailboxField(name, body, raw, mailbox, parseException); - } - } -} diff --git a/src/org/apache/james/mime4j/field/MailboxFieldImpl.java b/src/org/apache/james/mime4j/field/MailboxFieldImpl.java new file mode 100644 index 000000000..a4b648865 --- /dev/null +++ b/src/org/apache/james/mime4j/field/MailboxFieldImpl.java @@ -0,0 +1,84 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.address.MailboxList; +import org.apache.james.mime4j.field.address.parser.AddressBuilder; +import org.apache.james.mime4j.field.address.parser.ParseException; +import org.apache.james.mime4j.util.ByteSequence; + +/** + * Mailbox field such as Sender or Resent-Sender. + */ +public class MailboxFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.MailboxField { + private boolean parsed = false; + + private Mailbox mailbox; + private ParseException parseException; + + MailboxFieldImpl(final String name, final String body, final ByteSequence raw, DecodeMonitor monitor) { + super(name, body, raw, monitor); + } + + /** + * @see org.apache.james.mime4j.dom.field.MailboxField#getMailbox() + */ + public Mailbox getMailbox() { + if (!parsed) + parse(); + + return mailbox; + } + + /** + * @see org.apache.james.mime4j.dom.field.MailboxField#getParseException() + */ + @Override + public ParseException getParseException() { + if (!parsed) + parse(); + + return parseException; + } + + private void parse() { + String body = getBody(); + + try { + MailboxList mailboxList = AddressBuilder.parseAddressList(body, monitor).flatten(); + if (mailboxList.size() > 0) { + mailbox = mailboxList.get(0); + } + } catch (ParseException e) { + parseException = e; + } + + parsed = true; + } + + static final FieldParser PARSER = new FieldParser() { + public MailboxFieldImpl parse(final String name, final String body, + final ByteSequence raw, DecodeMonitor monitor) { + return new MailboxFieldImpl(name, body, raw, monitor); + } + }; +} diff --git a/src/org/apache/james/mime4j/field/MailboxListField.java b/src/org/apache/james/mime4j/field/MailboxListFieldImpl.java similarity index 51% rename from src/org/apache/james/mime4j/field/MailboxListField.java rename to src/org/apache/james/mime4j/field/MailboxListFieldImpl.java index e5b986dea..0e1cb8345 100644 --- a/src/org/apache/james/mime4j/field/MailboxListField.java +++ b/src/org/apache/james/mime4j/field/MailboxListFieldImpl.java @@ -19,47 +19,62 @@ package org.apache.james.mime4j.field; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.james.mime4j.field.address.AddressList; -import org.apache.james.mime4j.field.address.MailboxList; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.address.MailboxList; +import org.apache.james.mime4j.field.address.parser.AddressBuilder; import org.apache.james.mime4j.field.address.parser.ParseException; +import org.apache.james.mime4j.util.ByteSequence; + +/** + * Mailbox-list field such as From or Resent-From. + */ +public class MailboxListFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.MailboxListField { + private boolean parsed = false; -public class MailboxListField extends Field { - private MailboxList mailboxList; private ParseException parseException; - protected MailboxListField(final String name, final String body, final String raw, final MailboxList mailboxList, final ParseException parseException) { - super(name, body, raw); - this.mailboxList = mailboxList; - this.parseException = parseException; + MailboxListFieldImpl(final String name, final String body, final ByteSequence raw, DecodeMonitor monitor) { + super(name, body, raw, monitor); } + /** + * @see org.apache.james.mime4j.dom.field.MailboxListField#getMailboxList() + */ public MailboxList getMailboxList() { + if (!parsed) + parse(); + return mailboxList; } + /** + * @see org.apache.james.mime4j.dom.field.MailboxListField#getParseException() + */ + @Override public ParseException getParseException() { + if (!parsed) + parse(); + return parseException; } - - public static class Parser implements FieldParser { - private static Log log = LogFactory.getLog(Parser.class); - public Field parse(final String name, final String body, final String raw) { - MailboxList mailboxList = null; - ParseException parseException = null; - try { - mailboxList = AddressList.parse(body).flatten(); - } - catch (ParseException e) { - if (log.isDebugEnabled()) { - log.debug("Parsing value '" + body + "': "+ e.getMessage()); - } - parseException = e; - } - return new MailboxListField(name, body, raw, mailboxList, parseException); + private void parse() { + String body = getBody(); + + try { + mailboxList = AddressBuilder.parseAddressList(body, monitor).flatten(); + } catch (ParseException e) { + parseException = e; } + + parsed = true; } + + static final FieldParser PARSER = new FieldParser() { + public MailboxListFieldImpl parse(final String name, final String body, + final ByteSequence raw, DecodeMonitor monitor) { + return new MailboxListFieldImpl(name, body, raw, monitor); + } + }; } diff --git a/src/org/apache/james/mime4j/field/UnstructuredFieldImpl.java b/src/org/apache/james/mime4j/field/UnstructuredFieldImpl.java new file mode 100644 index 000000000..ea779e614 --- /dev/null +++ b/src/org/apache/james/mime4j/field/UnstructuredFieldImpl.java @@ -0,0 +1,62 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.codec.DecoderUtil; +import org.apache.james.mime4j.util.ByteSequence; + +/** + * Simple unstructured field such as Subject. + */ +public class UnstructuredFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.UnstructuredField { + private boolean parsed = false; + + private String value; + + UnstructuredFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) { + super(name, body, raw, monitor); + } + + /** + * @see org.apache.james.mime4j.dom.field.UnstructuredField#getValue() + */ + public String getValue() { + if (!parsed) + parse(); + + return value; + } + + private void parse() { + String body = getBody(); + + value = DecoderUtil.decodeEncodedWords(body, monitor); + + parsed = true; + } + + static final FieldParser PARSER = new FieldParser() { + public UnstructuredFieldImpl parse(final String name, final String body, + final ByteSequence raw, DecodeMonitor monitor) { + return new UnstructuredFieldImpl(name, body, raw, monitor); + } + }; +} diff --git a/src/org/apache/james/mime4j/field/address/AddressList.java b/src/org/apache/james/mime4j/field/address/AddressList.java deleted file mode 100644 index f623239d0..000000000 --- a/src/org/apache/james/mime4j/field/address/AddressList.java +++ /dev/null @@ -1,140 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.field.address; - -import org.apache.james.mime4j.field.address.parser.AddressListParser; -import org.apache.james.mime4j.field.address.parser.ParseException; - -import java.io.StringReader; -import java.util.ArrayList; - -/** - * An immutable, random-access list of Address objects. - * - * - */ -public class AddressList { - - private ArrayList addresses; - - /** - * @param addresses An ArrayList that contains only Address objects. - * @param dontCopy true iff it is not possible for the addresses ArrayList to be modified by someone else. - */ - public AddressList(ArrayList addresses, boolean dontCopy) { - if (addresses != null) - this.addresses = (dontCopy ? addresses : (ArrayList) addresses.clone()); - else - this.addresses = new ArrayList(0); - } - - /** - * The number of elements in this list. - */ - public int size() { - return addresses.size(); - } - - /** - * Gets an address. - */ - public Address get(int index) { - if (0 > index || size() <= index) - throw new IndexOutOfBoundsException(); - return (Address) addresses.get(index); - } - - /** - * Returns a flat list of all mailboxes represented - * in this address list. Use this if you don't care - * about grouping. - */ - public MailboxList flatten() { - // in the common case, all addresses are mailboxes - boolean groupDetected = false; - for (int i = 0; i < size(); i++) { - if (!(get(i) instanceof Mailbox)) { - groupDetected = true; - break; - } - } - - if (!groupDetected) - return new MailboxList(addresses, true); - - ArrayList results = new ArrayList(); - for (int i = 0; i < size(); i++) { - Address addr = get(i); - addr.addMailboxesTo(results); - } - - // copy-on-construct this time, because subclasses - // could have held onto a reference to the results - return new MailboxList(results, false); - } - - /** - * Dumps a representation of this address list to - * stdout, for debugging purposes. - */ - public void print() { - for (int i = 0; i < size(); i++) { - Address addr = get(i); - System.out.println(addr.toString()); - } - } - - - - /** - * Parse the address list string, such as the value - * of a From, To, Cc, Bcc, Sender, or Reply-To - * header. - * - * The string MUST be unfolded already. - */ - public static AddressList parse(String rawAddressList) throws ParseException { - AddressListParser parser = new AddressListParser(new StringReader(rawAddressList)); - return Builder.getInstance().buildAddressList(parser.parse()); - } - - /** - * Test console. - */ - public static void main(String[] args) throws Exception { - java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(System.in)); - while (true) { - try { - System.out.print("> "); - String line = reader.readLine(); - if (line.length() == 0 || line.toLowerCase().equals("exit") || line.toLowerCase().equals("quit")) { - System.out.println("Goodbye."); - return; - } - AddressList list = parse(line); - list.print(); - } - catch(Exception e) { - e.printStackTrace(); - Thread.sleep(300); - } - } - } -} diff --git a/src/org/apache/james/mime4j/field/address/Builder.java b/src/org/apache/james/mime4j/field/address/Builder.java deleted file mode 100644 index b17f4e3f7..000000000 --- a/src/org/apache/james/mime4j/field/address/Builder.java +++ /dev/null @@ -1,244 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.field.address; - -import java.util.ArrayList; -import java.util.Iterator; - -import org.apache.james.mime4j.decoder.DecoderUtil; -import org.apache.james.mime4j.field.address.parser.*; -import org.apache.james.mime4j.field.address.parser.ASTaddr_spec; -import org.apache.james.mime4j.field.address.parser.ASTaddress; -import org.apache.james.mime4j.field.address.parser.ASTaddress_list; -import org.apache.james.mime4j.field.address.parser.ASTangle_addr; -import org.apache.james.mime4j.field.address.parser.ASTdomain; -import org.apache.james.mime4j.field.address.parser.ASTgroup_body; -import org.apache.james.mime4j.field.address.parser.ASTlocal_part; -import org.apache.james.mime4j.field.address.parser.ASTmailbox; -import org.apache.james.mime4j.field.address.parser.ASTname_addr; -import org.apache.james.mime4j.field.address.parser.ASTphrase; -import org.apache.james.mime4j.field.address.parser.ASTroute; -import org.apache.james.mime4j.field.address.parser.Node; -import org.apache.james.mime4j.field.address.parser.SimpleNode; -import org.apache.james.mime4j.field.address.parser.Token; - -/** - * Transforms the JJTree-generated abstract syntax tree - * into a graph of org.apache.james.mime4j.field.address objects. - * - * - */ -class Builder { - - private static Builder singleton = new Builder(); - - public static Builder getInstance() { - return singleton; - } - - - - public AddressList buildAddressList(ASTaddress_list node) { - ArrayList list = new ArrayList(); - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - ASTaddress childNode = (ASTaddress) node.jjtGetChild(i); - Address address = buildAddress(childNode); - list.add(address); - } - return new AddressList(list, true); - } - - private Address buildAddress(ASTaddress node) { - ChildNodeIterator it = new ChildNodeIterator(node); - Node n = it.nextNode(); - if (n instanceof ASTaddr_spec) { - return buildAddrSpec((ASTaddr_spec)n); - } - else if (n instanceof ASTangle_addr) { - return buildAngleAddr((ASTangle_addr)n); - } - else if (n instanceof ASTphrase) { - String name = buildString((ASTphrase)n, false); - Node n2 = it.nextNode(); - if (n2 instanceof ASTgroup_body) { - return new Group(name, buildGroupBody((ASTgroup_body)n2)); - } - else if (n2 instanceof ASTangle_addr) { - name = DecoderUtil.decodeEncodedWords(name); - return new NamedMailbox(name, buildAngleAddr((ASTangle_addr)n2)); - } - else { - throw new IllegalStateException(); - } - } - else { - throw new IllegalStateException(); - } - } - - - - private MailboxList buildGroupBody(ASTgroup_body node) { - ArrayList results = new ArrayList(); - ChildNodeIterator it = new ChildNodeIterator(node); - while (it.hasNext()) { - Node n = it.nextNode(); - if (n instanceof ASTmailbox) - results.add(buildMailbox((ASTmailbox)n)); - else - throw new IllegalStateException(); - } - return new MailboxList(results, true); - } - - private Mailbox buildMailbox(ASTmailbox node) { - ChildNodeIterator it = new ChildNodeIterator(node); - Node n = it.nextNode(); - if (n instanceof ASTaddr_spec) { - return buildAddrSpec((ASTaddr_spec)n); - } - else if (n instanceof ASTangle_addr) { - return buildAngleAddr((ASTangle_addr)n); - } - else if (n instanceof ASTname_addr) { - return buildNameAddr((ASTname_addr)n); - } - else { - throw new IllegalStateException(); - } - } - - private NamedMailbox buildNameAddr(ASTname_addr node) { - ChildNodeIterator it = new ChildNodeIterator(node); - Node n = it.nextNode(); - String name; - if (n instanceof ASTphrase) { - name = buildString((ASTphrase)n, false); - } - else { - throw new IllegalStateException(); - } - - n = it.nextNode(); - if (n instanceof ASTangle_addr) { - name = DecoderUtil.decodeEncodedWords(name); - return new NamedMailbox(name, buildAngleAddr((ASTangle_addr) n)); - } - else { - throw new IllegalStateException(); - } - } - - private Mailbox buildAngleAddr(ASTangle_addr node) { - ChildNodeIterator it = new ChildNodeIterator(node); - DomainList route = null; - Node n = it.nextNode(); - if (n instanceof ASTroute) { - route = buildRoute((ASTroute)n); - n = it.nextNode(); - } - else if (n instanceof ASTaddr_spec) - ; // do nothing - else - throw new IllegalStateException(); - - if (n instanceof ASTaddr_spec) - return buildAddrSpec(route, (ASTaddr_spec)n); - else - throw new IllegalStateException(); - } - - private DomainList buildRoute(ASTroute node) { - ArrayList results = new ArrayList(node.jjtGetNumChildren()); - ChildNodeIterator it = new ChildNodeIterator(node); - while (it.hasNext()) { - Node n = it.nextNode(); - if (n instanceof ASTdomain) - results.add(buildString((ASTdomain)n, true)); - else - throw new IllegalStateException(); - } - return new DomainList(results, true); - } - - private Mailbox buildAddrSpec(ASTaddr_spec node) { - return buildAddrSpec(null, node); - } - private Mailbox buildAddrSpec(DomainList route, ASTaddr_spec node) { - ChildNodeIterator it = new ChildNodeIterator(node); - String localPart = buildString((ASTlocal_part)it.nextNode(), true); - String domain = buildString((ASTdomain)it.nextNode(), true); - return new Mailbox(route, localPart, domain); - } - - - private String buildString(SimpleNode node, boolean stripSpaces) { - Token head = node.firstToken; - Token tail = node.lastToken; - StringBuffer out = new StringBuffer(); - - while (head != tail) { - out.append(head.image); - head = head.next; - if (!stripSpaces) - addSpecials(out, head.specialToken); - } - out.append(tail.image); - - return out.toString(); - } - - private void addSpecials(StringBuffer out, Token specialToken) { - if (specialToken != null) { - addSpecials(out, specialToken.specialToken); - out.append(specialToken.image); - } - } - - private static class ChildNodeIterator implements Iterator { - - private SimpleNode simpleNode; - private int index; - private int len; - - public ChildNodeIterator(SimpleNode simpleNode) { - this.simpleNode = simpleNode; - this.len = simpleNode.jjtGetNumChildren(); - this.index = 0; - } - - public void remove() { - throw new UnsupportedOperationException(); - } - - public boolean hasNext() { - return index < len; - } - - public Object next() { - return nextNode(); - } - - public Node nextNode() { - return simpleNode.jjtGetChild(index++); - } - - } -} diff --git a/src/org/apache/james/mime4j/field/address/DomainList.java b/src/org/apache/james/mime4j/field/address/DomainList.java deleted file mode 100644 index d90b22285..000000000 --- a/src/org/apache/james/mime4j/field/address/DomainList.java +++ /dev/null @@ -1,76 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.field.address; - -import java.util.ArrayList; - -/** - * An immutable, random-access list of Strings (that - * are supposedly domain names or domain literals). - * - * - */ -public class DomainList { - private ArrayList domains; - - /** - * @param domains An ArrayList that contains only String objects. - * @param dontCopy true iff it is not possible for the domains ArrayList to be modified by someone else. - */ - public DomainList(ArrayList domains, boolean dontCopy) { - if (domains != null) - this.domains = (dontCopy ? domains : (ArrayList) domains.clone()); - else - this.domains = new ArrayList(0); - } - - /** - * The number of elements in this list. - */ - public int size() { - return domains.size(); - } - - /** - * Gets the domain name or domain literal at the - * specified index. - * @throws IndexOutOfBoundsException If index is < 0 or >= size(). - */ - public String get(int index) { - if (0 > index || size() <= index) - throw new IndexOutOfBoundsException(); - return (String) domains.get(index); - } - - /** - * Returns the list of domains formatted as a route - * string (not including the trailing ':'). - */ - public String toRouteString() { - StringBuffer out = new StringBuffer(); - for (int i = 0; i < domains.size(); i++) { - out.append("@"); - out.append(get(i)); - if (i + 1 < domains.size()) - out.append(","); - } - return out.toString(); - } -} diff --git a/src/org/apache/james/mime4j/field/address/Group.java b/src/org/apache/james/mime4j/field/address/Group.java deleted file mode 100644 index 63e294549..000000000 --- a/src/org/apache/james/mime4j/field/address/Group.java +++ /dev/null @@ -1,73 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.field.address; - -import java.util.ArrayList; - -/** - * A named group of zero or more mailboxes. - * - * - */ -public class Group extends Address { - private String name; - private MailboxList mailboxList; - - /** - * @param name The group name. - * @param mailboxes The mailboxes in this group. - */ - public Group(String name, MailboxList mailboxes) { - this.name = name; - this.mailboxList = mailboxes; - } - - /** - * Returns the group name. - */ - public String getName() { - return name; - } - - /** - * Returns the mailboxes in this group. - */ - public MailboxList getMailboxes() { - return mailboxList; - } - - public String toString() { - StringBuffer buf = new StringBuffer(); - buf.append(name); - buf.append(":"); - for (int i = 0; i < mailboxList.size(); i++) { - buf.append(mailboxList.get(i).toString()); - if (i + 1 < mailboxList.size()) - buf.append(","); - } - buf.append(";"); - return buf.toString(); - } - - protected void doAddMailboxesTo(ArrayList results) { - for (int i = 0; i < mailboxList.size(); i++) - results.add(mailboxList.get(i)); - } -} diff --git a/src/org/apache/james/mime4j/field/address/Mailbox.java b/src/org/apache/james/mime4j/field/address/Mailbox.java deleted file mode 100644 index 1bc32b818..000000000 --- a/src/org/apache/james/mime4j/field/address/Mailbox.java +++ /dev/null @@ -1,119 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.field.address; - -import java.util.ArrayList; - -/** - * Represents a single e-mail address. - * - * - */ -public class Mailbox extends Address { - private DomainList route; - private String localPart; - private String domain; - - /** - * Creates a mailbox without a route. Routes are obsolete. - * @param localPart The part of the e-mail address to the left of the "@". - * @param domain The part of the e-mail address to the right of the "@". - */ - public Mailbox(String localPart, String domain) { - this(null, localPart, domain); - } - - /** - * Creates a mailbox with a route. Routes are obsolete. - * @param route The zero or more domains that make up the route. Can be null. - * @param localPart The part of the e-mail address to the left of the "@". - * @param domain The part of the e-mail address to the right of the "@". - */ - public Mailbox(DomainList route, String localPart, String domain) { - this.route = route; - this.localPart = localPart; - this.domain = domain; - } - - /** - * Returns the route list. - */ - public DomainList getRoute() { - return route; - } - - /** - * Returns the left part of the e-mail address - * (before "@"). - */ - public String getLocalPart() { - return localPart; - } - - /** - * Returns the right part of the e-mail address - * (after "@"). - */ - public String getDomain() { - return domain; - } - - /** - * Formats the address as a string, not including - * the route. - * - * @see #getAddressString(boolean) - */ - public String getAddressString() { - return getAddressString(false); - } - - /** - * Note that this value may not be usable - * for transport purposes, only display purposes. - * - * For example, if the unparsed address was - * - * <"Joe Cheng"@joecheng.com> - * - * this method would return - * - * - * - * which is not valid for transport; the local part - * would need to be re-quoted. - * - * @param includeRoute true if the route should be included if it exists. - */ - public String getAddressString(boolean includeRoute) { - return "<" + (!includeRoute || route == null ? "" : route.toRouteString() + ":") - + localPart - + (domain == null ? "" : "@") - + domain + ">"; - } - - protected final void doAddMailboxesTo(ArrayList results) { - results.add(this); - } - - public String toString() { - return getAddressString(); - } -} diff --git a/src/org/apache/james/mime4j/field/address/NamedMailbox.java b/src/org/apache/james/mime4j/field/address/NamedMailbox.java deleted file mode 100644 index 36b800c81..000000000 --- a/src/org/apache/james/mime4j/field/address/NamedMailbox.java +++ /dev/null @@ -1,70 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.field.address; - -/** - * A Mailbox that has a name/description. - * - * - */ -public class NamedMailbox extends Mailbox { - private String name; - - /** - * @see Mailbox#Mailbox(String, String) - */ - public NamedMailbox(String name, String localPart, String domain) { - super(localPart, domain); - this.name = name; - } - - /** - * @see Mailbox#Mailbox(DomainList, String, String) - */ - public NamedMailbox(String name, DomainList route, String localPart, String domain) { - super(route, localPart, domain); - this.name = name; - } - - /** - * Creates a named mailbox based on an unnamed mailbox. - */ - public NamedMailbox(String name, Mailbox baseMailbox) { - super(baseMailbox.getRoute(), baseMailbox.getLocalPart(), baseMailbox.getDomain()); - this.name = name; - } - - /** - * Returns the name of the mailbox. - */ - public String getName() { - return this.name; - } - - /** - * Same features (or problems) as Mailbox.getAddressString(boolean), - * only more so. - * - * @see Mailbox#getAddressString(boolean) - */ - public String getAddressString(boolean includeRoute) { - return (name == null ? "" : name + " ") + super.getAddressString(includeRoute); - } -} diff --git a/src/org/apache/james/mime4j/field/address/formatter/AddressFormatter.java b/src/org/apache/james/mime4j/field/address/formatter/AddressFormatter.java new file mode 100644 index 000000000..5b0d91394 --- /dev/null +++ b/src/org/apache/james/mime4j/field/address/formatter/AddressFormatter.java @@ -0,0 +1,209 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address.formatter; + +import org.apache.james.mime4j.codec.EncoderUtil; +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.Group; +import org.apache.james.mime4j.dom.address.Mailbox; + +public class AddressFormatter { + + /** + * Formats the address as a human readable string, not including the route. + * The resulting string is intended for display purposes only and cannot be + * used for transport purposes. + * + * For example, if the unparsed address was + * + * <"Joe Cheng"@joecheng.com> + * + * this method would return + * + * + * + * which is not valid for transport; the local part would need to be + * re-quoted. + * + * @param includeRoute + * true if the route should be included if it + * exists, false otherwise. + * @return a string representation of this address intended to be displayed. + */ + public static void format(final StringBuilder sb, final Address address, boolean includeRoute) { + if (address == null) { + return; + } + if (address instanceof Mailbox) { + format(sb, (Mailbox) address, includeRoute); + } else if (address instanceof Group) { + format(sb, (Group) address, includeRoute); + } else { + throw new IllegalArgumentException("Unsuppported Address class: " + address.getClass()); + } + } + + /** + * Returns a string representation of this address that can be used for + * transport purposes. The route is never included in this representation + * because routes are obsolete and RFC 5322 states that obsolete syntactic + * forms MUST NOT be generated. + * + * @return a string representation of this address intended for transport + * purposes. + */ + public static void encode(final StringBuilder sb, final Address address) { + if (address == null) { + return; + } + if (address instanceof Mailbox) { + encode(sb, (Mailbox) address); + } else if (address instanceof Group) { + encode(sb, (Group) address); + } else { + throw new IllegalArgumentException("Unsuppported Address class: " + address.getClass()); + } + } + + public static void format(final StringBuilder sb, final Mailbox mailbox, boolean includeRoute) { + if (sb == null) { + throw new IllegalArgumentException("StringBuilder may not be null"); + } + if (mailbox == null) { + throw new IllegalArgumentException("Mailbox may not be null"); + } + includeRoute &= mailbox.getRoute() != null; + boolean includeAngleBrackets = mailbox.getName() != null || includeRoute; + if (mailbox.getName() != null) { + sb.append(mailbox.getName()); + sb.append(' '); + } + if (includeAngleBrackets) { + sb.append('<'); + } + if (includeRoute) { + sb.append(mailbox.getRoute().toRouteString()); + sb.append(':'); + } + sb.append(mailbox.getLocalPart()); + if (mailbox.getDomain() != null) { + sb.append('@'); + sb.append(mailbox.getDomain()); + } + if (includeAngleBrackets) { + sb.append('>'); + } + } + + public static String format(final Mailbox mailbox, boolean includeRoute) { + StringBuilder sb = new StringBuilder(); + format(sb, mailbox, includeRoute); + return sb.toString(); + } + + public static void encode(final StringBuilder sb, final Mailbox mailbox) { + if (sb == null) { + throw new IllegalArgumentException("StringBuilder may not be null"); + } + if (mailbox == null) { + throw new IllegalArgumentException("Mailbox may not be null"); + } + if (mailbox.getName() != null) { + sb.append(EncoderUtil.encodeAddressDisplayName(mailbox.getName())); + sb.append(" <"); + } + sb.append(EncoderUtil.encodeAddressLocalPart(mailbox.getLocalPart())); + // domain = dot-atom / domain-literal + // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS] + // dtext = %d33-90 / %d94-126 + if (mailbox.getDomain() != null) { + sb.append('@'); + sb.append(mailbox.getDomain()); + } + if (mailbox.getName() != null) { + sb.append('>'); + } + } + + public static String encode(final Mailbox mailbox) { + StringBuilder sb = new StringBuilder(); + encode(sb, mailbox); + return sb.toString(); + } + + public static void format(final StringBuilder sb, final Group group, boolean includeRoute) { + if (sb == null) { + throw new IllegalArgumentException("StringBuilder may not be null"); + } + if (group == null) { + throw new IllegalArgumentException("Group may not be null"); + } + sb.append(group.getName()); + sb.append(':'); + + boolean first = true; + for (Mailbox mailbox : group.getMailboxes()) { + if (first) { + first = false; + } else { + sb.append(','); + } + sb.append(' '); + format(sb, mailbox, includeRoute); + } + sb.append(";"); + } + + public static String format(final Group group, boolean includeRoute) { + StringBuilder sb = new StringBuilder(); + format(sb, group, includeRoute); + return sb.toString(); + } + + public static void encode(final StringBuilder sb, final Group group) { + if (sb == null) { + throw new IllegalArgumentException("StringBuilder may not be null"); + } + if (group == null) { + throw new IllegalArgumentException("Group may not be null"); + } + sb.append(EncoderUtil.encodeAddressDisplayName(group.getName())); + sb.append(':'); + boolean first = true; + for (Mailbox mailbox : group.getMailboxes()) { + if (first) { + first = false; + } else { + sb.append(','); + } + + sb.append(' '); + encode(sb, mailbox); + } + sb.append(';'); + } + + public static String encode(final Group group) { + StringBuilder sb = new StringBuilder(); + encode(sb, group); + return sb.toString(); + } + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTaddr_spec.java b/src/org/apache/james/mime4j/field/address/parser/ASTaddr_spec.java index 4d56d000b..881420d40 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ASTaddr_spec.java +++ b/src/org/apache/james/mime4j/field/address/parser/ASTaddr_spec.java @@ -1,8 +1,9 @@ -/* Generated By:JJTree: Do not edit this line. ASTaddr_spec.java */ - +/* Generated By:JJTree: Do not edit this line. ASTaddr_spec.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class ASTaddr_spec extends SimpleNode { +public +class ASTaddr_spec extends SimpleNode { public ASTaddr_spec(int id) { super(id); } @@ -17,3 +18,4 @@ public class ASTaddr_spec extends SimpleNode { return visitor.visit(this, data); } } +/* JavaCC - OriginalChecksum=750ab0a2f6a942d3f4a7a7e076d12a4d (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTaddress.java b/src/org/apache/james/mime4j/field/address/parser/ASTaddress.java index 47bdeda8e..419cb956b 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ASTaddress.java +++ b/src/org/apache/james/mime4j/field/address/parser/ASTaddress.java @@ -1,8 +1,9 @@ -/* Generated By:JJTree: Do not edit this line. ASTaddress.java */ - +/* Generated By:JJTree: Do not edit this line. ASTaddress.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class ASTaddress extends SimpleNode { +public +class ASTaddress extends SimpleNode { public ASTaddress(int id) { super(id); } @@ -17,3 +18,4 @@ public class ASTaddress extends SimpleNode { return visitor.visit(this, data); } } +/* JavaCC - OriginalChecksum=73be0a52ecfe4cf5d5a926be94fbb411 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTaddress_list.java b/src/org/apache/james/mime4j/field/address/parser/ASTaddress_list.java index 737840e38..d0a7f2433 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ASTaddress_list.java +++ b/src/org/apache/james/mime4j/field/address/parser/ASTaddress_list.java @@ -1,8 +1,9 @@ -/* Generated By:JJTree: Do not edit this line. ASTaddress_list.java */ - +/* Generated By:JJTree: Do not edit this line. ASTaddress_list.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class ASTaddress_list extends SimpleNode { +public +class ASTaddress_list extends SimpleNode { public ASTaddress_list(int id) { super(id); } @@ -17,3 +18,4 @@ public class ASTaddress_list extends SimpleNode { return visitor.visit(this, data); } } +/* JavaCC - OriginalChecksum=6615a805f4abebfcf7252d9aad462299 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTangle_addr.java b/src/org/apache/james/mime4j/field/address/parser/ASTangle_addr.java index 8cb8f421f..1a61e9345 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ASTangle_addr.java +++ b/src/org/apache/james/mime4j/field/address/parser/ASTangle_addr.java @@ -1,8 +1,9 @@ -/* Generated By:JJTree: Do not edit this line. ASTangle_addr.java */ - +/* Generated By:JJTree: Do not edit this line. ASTangle_addr.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class ASTangle_addr extends SimpleNode { +public +class ASTangle_addr extends SimpleNode { public ASTangle_addr(int id) { super(id); } @@ -17,3 +18,4 @@ public class ASTangle_addr extends SimpleNode { return visitor.visit(this, data); } } +/* JavaCC - OriginalChecksum=2201bedb23ef9d1b75dd88b2a5571384 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTdomain.java b/src/org/apache/james/mime4j/field/address/parser/ASTdomain.java index b52664386..93ec2ab16 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ASTdomain.java +++ b/src/org/apache/james/mime4j/field/address/parser/ASTdomain.java @@ -1,8 +1,9 @@ -/* Generated By:JJTree: Do not edit this line. ASTdomain.java */ - +/* Generated By:JJTree: Do not edit this line. ASTdomain.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class ASTdomain extends SimpleNode { +public +class ASTdomain extends SimpleNode { public ASTdomain(int id) { super(id); } @@ -17,3 +18,4 @@ public class ASTdomain extends SimpleNode { return visitor.visit(this, data); } } +/* JavaCC - OriginalChecksum=0105eb2d00d34d34b0665fd5ced14d52 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTgroup_body.java b/src/org/apache/james/mime4j/field/address/parser/ASTgroup_body.java index f6017b9fc..505490e79 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ASTgroup_body.java +++ b/src/org/apache/james/mime4j/field/address/parser/ASTgroup_body.java @@ -1,8 +1,9 @@ -/* Generated By:JJTree: Do not edit this line. ASTgroup_body.java */ - +/* Generated By:JJTree: Do not edit this line. ASTgroup_body.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class ASTgroup_body extends SimpleNode { +public +class ASTgroup_body extends SimpleNode { public ASTgroup_body(int id) { super(id); } @@ -17,3 +18,4 @@ public class ASTgroup_body extends SimpleNode { return visitor.visit(this, data); } } +/* JavaCC - OriginalChecksum=29b09d0a20de5b5f3d7e08c7e325d23f (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTlocal_part.java b/src/org/apache/james/mime4j/field/address/parser/ASTlocal_part.java index 5c244fa3e..bf1298a0e 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ASTlocal_part.java +++ b/src/org/apache/james/mime4j/field/address/parser/ASTlocal_part.java @@ -1,8 +1,9 @@ -/* Generated By:JJTree: Do not edit this line. ASTlocal_part.java */ - +/* Generated By:JJTree: Do not edit this line. ASTlocal_part.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class ASTlocal_part extends SimpleNode { +public +class ASTlocal_part extends SimpleNode { public ASTlocal_part(int id) { super(id); } @@ -17,3 +18,4 @@ public class ASTlocal_part extends SimpleNode { return visitor.visit(this, data); } } +/* JavaCC - OriginalChecksum=42e77dd54203428772aecd61a95fc01c (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTmailbox.java b/src/org/apache/james/mime4j/field/address/parser/ASTmailbox.java index aeb469da1..13b403b5e 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ASTmailbox.java +++ b/src/org/apache/james/mime4j/field/address/parser/ASTmailbox.java @@ -1,8 +1,9 @@ -/* Generated By:JJTree: Do not edit this line. ASTmailbox.java */ - +/* Generated By:JJTree: Do not edit this line. ASTmailbox.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class ASTmailbox extends SimpleNode { +public +class ASTmailbox extends SimpleNode { public ASTmailbox(int id) { super(id); } @@ -17,3 +18,4 @@ public class ASTmailbox extends SimpleNode { return visitor.visit(this, data); } } +/* JavaCC - OriginalChecksum=246fe7d146969407e2c7977748e2fc99 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTname_addr.java b/src/org/apache/james/mime4j/field/address/parser/ASTname_addr.java index 846c73167..67f8eb7cc 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ASTname_addr.java +++ b/src/org/apache/james/mime4j/field/address/parser/ASTname_addr.java @@ -1,8 +1,9 @@ -/* Generated By:JJTree: Do not edit this line. ASTname_addr.java */ - +/* Generated By:JJTree: Do not edit this line. ASTname_addr.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class ASTname_addr extends SimpleNode { +public +class ASTname_addr extends SimpleNode { public ASTname_addr(int id) { super(id); } @@ -17,3 +18,4 @@ public class ASTname_addr extends SimpleNode { return visitor.visit(this, data); } } +/* JavaCC - OriginalChecksum=37e69dc07dfc157b3fb2449483ff82b6 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTphrase.java b/src/org/apache/james/mime4j/field/address/parser/ASTphrase.java index 7d711c529..40d7a6788 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ASTphrase.java +++ b/src/org/apache/james/mime4j/field/address/parser/ASTphrase.java @@ -1,8 +1,9 @@ -/* Generated By:JJTree: Do not edit this line. ASTphrase.java */ - +/* Generated By:JJTree: Do not edit this line. ASTphrase.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class ASTphrase extends SimpleNode { +public +class ASTphrase extends SimpleNode { public ASTphrase(int id) { super(id); } @@ -17,3 +18,4 @@ public class ASTphrase extends SimpleNode { return visitor.visit(this, data); } } +/* JavaCC - OriginalChecksum=f1c7166e3c5b192d1f6db62b8239b0be (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTroute.java b/src/org/apache/james/mime4j/field/address/parser/ASTroute.java index 54ea11523..caae1f891 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ASTroute.java +++ b/src/org/apache/james/mime4j/field/address/parser/ASTroute.java @@ -1,8 +1,9 @@ -/* Generated By:JJTree: Do not edit this line. ASTroute.java */ - +/* Generated By:JJTree: Do not edit this line. ASTroute.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class ASTroute extends SimpleNode { +public +class ASTroute extends SimpleNode { public ASTroute(int id) { super(id); } @@ -17,3 +18,4 @@ public class ASTroute extends SimpleNode { return visitor.visit(this, data); } } +/* JavaCC - OriginalChecksum=bcec06c89402cfcb3700aefe8d5f14f9 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressBuilder.java b/src/org/apache/james/mime4j/field/address/parser/AddressBuilder.java new file mode 100644 index 000000000..5db350734 --- /dev/null +++ b/src/org/apache/james/mime4j/field/address/parser/AddressBuilder.java @@ -0,0 +1,115 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address.parser; + +import java.io.StringReader; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.AddressList; +import org.apache.james.mime4j.dom.address.Group; +import org.apache.james.mime4j.dom.address.Mailbox; + +public class AddressBuilder { + + /** + * Parses the specified raw string into an address. + * + * @param rawAddressString + * string to parse. + * @param monitor the DecodeMonitor to be used while parsing/decoding + * @return an Address object for the specified string. + * @throws ParseException if the raw string does not represent a single address. + */ + public static Address parseAddress(String rawAddressString, DecodeMonitor monitor) throws ParseException { + AddressListParser parser = new AddressListParser(new StringReader( + rawAddressString)); + return Builder.getInstance().buildAddress(parser.parseAddress(), monitor); + } + + public static Address parseAddress(String rawAddressString) throws ParseException { + return parseAddress(rawAddressString, DecodeMonitor.STRICT); + } + + /** + * Parse the address list string, such as the value of a From, To, Cc, Bcc, + * Sender, or Reply-To header. + * + * The string MUST be unfolded already. + * @param monitor the DecodeMonitor to be used while parsing/decoding + */ + public static AddressList parseAddressList(String rawAddressList, DecodeMonitor monitor) + throws ParseException { + AddressListParser parser = new AddressListParser(new StringReader( + rawAddressList)); + try { + return Builder.getInstance().buildAddressList(parser.parseAddressList(), monitor); + } catch (RuntimeException e) { + throw new ParseException(e.getMessage()); + } + } + + public static AddressList parseAddressList(String rawAddressList) throws ParseException { + return parseAddressList(rawAddressList, DecodeMonitor.STRICT); + } + + /** + * Parses the specified raw string into a mailbox address. + * + * @param rawMailboxString + * string to parse. + * @param monitor the DecodeMonitor to be used while parsing/decoding. + * @return a Mailbox object for the specified string. + * @throws ParseException + * if the raw string does not represent a single mailbox + * address. + */ + public static Mailbox parseMailbox(String rawMailboxString, DecodeMonitor monitor) throws ParseException { + AddressListParser parser = new AddressListParser(new StringReader( + rawMailboxString)); + return Builder.getInstance().buildMailbox(parser.parseMailbox(), monitor); + } + + public static Mailbox parseMailbox(String rawMailboxString) throws ParseException { + return parseMailbox(rawMailboxString, DecodeMonitor.STRICT); + } + + /** + * Parses the specified raw string into a group address. + * + * @param rawGroupString + * string to parse. + * @return a Group object for the specified string. + * @throws ParseException + * if the raw string does not represent a single group address. + */ + public static Group parseGroup(String rawGroupString, DecodeMonitor monitor) throws ParseException { + Address address = parseAddress(rawGroupString, monitor); + if (!(address instanceof Group)) + throw new ParseException("Not a group address"); + + return (Group) address; + } + + public static Group parseGroup(String rawGroupString) throws ParseException { + return parseGroup(rawGroupString, DecodeMonitor.STRICT); + } + +} diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParser.java b/src/org/apache/james/mime4j/field/address/parser/AddressListParser.java index 6cf08ac1d..c911691b4 100644 --- a/src/org/apache/james/mime4j/field/address/parser/AddressListParser.java +++ b/src/org/apache/james/mime4j/field/address/parser/AddressListParser.java @@ -1,55 +1,71 @@ /* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParser.java */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.address.parser; public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeConstants, AddressListParserConstants {/*@bgen(jjtree)*/ protected JJTAddressListParserState jjtree = new JJTAddressListParserState();public static void main(String args[]) throws ParseException { - while (true) { - try { - AddressListParser parser = new AddressListParser(System.in); - parser.parseLine(); - ((SimpleNode)parser.jjtree.rootNode()).dump("> "); - } catch (Exception x) { - x.printStackTrace(); - return; - } - } + while (true) { + try { + AddressListParser parser = new AddressListParser(System.in); + parser.parseLine(); + ((SimpleNode) parser.jjtree.rootNode()).dump("> "); + } catch (Exception x) { + x.printStackTrace(); + return; + } + } } - private static void log(String msg) { - System.out.print(msg); - } - - public ASTaddress_list parse() throws ParseException { + public ASTaddress_list parseAddressList() throws ParseException { try { - parseAll(); - return (ASTaddress_list)jjtree.rootNode(); + parseAddressList0(); + return (ASTaddress_list) jjtree.rootNode(); } catch (TokenMgrError tme) { throw new ParseException(tme.getMessage()); } } + public ASTaddress parseAddress() throws ParseException { + try { + parseAddress0(); + return (ASTaddress) jjtree.rootNode(); + } catch (TokenMgrError tme) { + throw new ParseException(tme.getMessage()); + } + } + + public ASTmailbox parseMailbox() throws ParseException { + try { + parseMailbox0(); + return (ASTmailbox) jjtree.rootNode(); + } catch (TokenMgrError tme) { + throw new ParseException(tme.getMessage()); + } + } void jjtreeOpenNodeScope(Node n) { - ((SimpleNode)n).firstToken = getToken(1); + ((SimpleNode) n).firstToken = getToken(1); } void jjtreeCloseNodeScope(Node n) { - ((SimpleNode)n).lastToken = getToken(0); + ((SimpleNode) n).lastToken = getToken(0); } final public void parseLine() throws ParseException { @@ -65,11 +81,21 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC jj_consume_token(2); } - final public void parseAll() throws ParseException { + final public void parseAddressList0() throws ParseException { address_list(); jj_consume_token(0); } + final public void parseAddress0() throws ParseException { + address(); + jj_consume_token(0); + } + + final public void parseMailbox0() throws ParseException { + mailbox(); + jj_consume_token(0); + } + final public void address_list() throws ParseException { /*@bgen(jjtree) address_list */ ASTaddress_list jjtn000 = new ASTaddress_list(JJTADDRESS_LIST); @@ -537,7 +563,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC jj_la1[17] = jj_gen; ; } - if (t.image.charAt(t.image.length() - 1) != '.' || t.kind == AddressListParserConstants.QUOTEDSTRING) + if ( t.kind == AddressListParserConstants.QUOTEDSTRING || t.image.charAt(t.image.length() - 1) != '.') {if (true) throw new ParseException("Words in local part must be separated by '.'");} switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { case DOTATOM: @@ -610,21 +636,21 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC } } - final private boolean jj_2_1(int xla) { + private boolean jj_2_1(int xla) { jj_la = xla; jj_lastpos = jj_scanpos = token; try { return !jj_3_1(); } catch(LookaheadSuccess ls) { return true; } finally { jj_save(0, xla); } } - final private boolean jj_2_2(int xla) { + private boolean jj_2_2(int xla) { jj_la = xla; jj_lastpos = jj_scanpos = token; try { return !jj_3_2(); } catch(LookaheadSuccess ls) { return true; } finally { jj_save(1, xla); } } - final private boolean jj_3R_11() { + private boolean jj_3R_11() { Token xsp; xsp = jj_scanpos; if (jj_scan_token(9)) jj_scanpos = xsp; @@ -636,7 +662,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC return false; } - final private boolean jj_3R_13() { + private boolean jj_3R_13() { Token xsp; xsp = jj_scanpos; if (jj_scan_token(9)) jj_scanpos = xsp; @@ -644,19 +670,19 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC return false; } - final private boolean jj_3R_8() { + private boolean jj_3R_8() { if (jj_3R_9()) return true; if (jj_scan_token(8)) return true; if (jj_3R_10()) return true; return false; } - final private boolean jj_3_1() { + private boolean jj_3_1() { if (jj_3R_8()) return true; return false; } - final private boolean jj_3R_12() { + private boolean jj_3R_12() { if (jj_scan_token(DOTATOM)) return true; Token xsp; while (true) { @@ -666,7 +692,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC return false; } - final private boolean jj_3R_10() { + private boolean jj_3R_10() { Token xsp; xsp = jj_scanpos; if (jj_3R_12()) { @@ -676,12 +702,12 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC return false; } - final private boolean jj_3_2() { + private boolean jj_3_2() { if (jj_3R_8()) return true; return false; } - final private boolean jj_3R_9() { + private boolean jj_3R_9() { Token xsp; xsp = jj_scanpos; if (jj_scan_token(14)) { @@ -695,35 +721,39 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC return false; } + /** Generated Token Manager. */ public AddressListParserTokenManager token_source; SimpleCharStream jj_input_stream; - public Token token, jj_nt; + /** Current token. */ + public Token token; + /** Next token. */ + public Token jj_nt; private int jj_ntk; private Token jj_scanpos, jj_lastpos; private int jj_la; - public boolean lookingAhead = false; - private boolean jj_semLA; private int jj_gen; final private int[] jj_la1 = new int[22]; static private int[] jj_la1_0; static private int[] jj_la1_1; static { - jj_la1_0(); - jj_la1_1(); + jj_la1_init_0(); + jj_la1_init_1(); } - private static void jj_la1_0() { + private static void jj_la1_init_0() { jj_la1_0 = new int[] {0x2,0x80004040,0x8,0x80004040,0x50,0x80004040,0x80004040,0x80004040,0x8,0x80004040,0x100,0x108,0x8,0x80004000,0x80004000,0x80004000,0x80004200,0x200,0x80004000,0x4200,0x200,0x44000,}; } - private static void jj_la1_1() { + private static void jj_la1_init_1() { jj_la1_1 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,}; } final private JJCalls[] jj_2_rtns = new JJCalls[2]; private boolean jj_rescan = false; private int jj_gc = 0; + /** Constructor with InputStream. */ public AddressListParser(java.io.InputStream stream) { this(stream, null); } + /** Constructor with InputStream and supplied encoding */ public AddressListParser(java.io.InputStream stream, String encoding) { try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } token_source = new AddressListParserTokenManager(jj_input_stream); @@ -734,9 +764,11 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); } + /** Reinitialise. */ public void ReInit(java.io.InputStream stream) { ReInit(stream, null); } + /** Reinitialise. */ public void ReInit(java.io.InputStream stream, String encoding) { try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } token_source.ReInit(jj_input_stream); @@ -748,6 +780,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); } + /** Constructor. */ public AddressListParser(java.io.Reader stream) { jj_input_stream = new SimpleCharStream(stream, 1, 1); token_source = new AddressListParserTokenManager(jj_input_stream); @@ -758,6 +791,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); } + /** Reinitialise. */ public void ReInit(java.io.Reader stream) { jj_input_stream.ReInit(stream, 1, 1); token_source.ReInit(jj_input_stream); @@ -769,6 +803,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); } + /** Constructor with generated Token Manager. */ public AddressListParser(AddressListParserTokenManager tm) { token_source = tm; token = new Token(); @@ -778,6 +813,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); } + /** Reinitialise. */ public void ReInit(AddressListParserTokenManager tm) { token_source = tm; token = new Token(); @@ -788,7 +824,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); } - final private Token jj_consume_token(int kind) throws ParseException { + private Token jj_consume_token(int kind) throws ParseException { Token oldToken; if ((oldToken = token).next != null) token = token.next; else token = token.next = token_source.getNextToken(); @@ -814,7 +850,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC static private final class LookaheadSuccess extends java.lang.Error { } final private LookaheadSuccess jj_ls = new LookaheadSuccess(); - final private boolean jj_scan_token(int kind) { + private boolean jj_scan_token(int kind) { if (jj_scanpos == jj_lastpos) { jj_la--; if (jj_scanpos.next == null) { @@ -835,6 +871,8 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC return false; } + +/** Get the next Token. */ final public Token getNextToken() { if (token.next != null) token = token.next; else token = token.next = token_source.getNextToken(); @@ -843,8 +881,9 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC return token; } +/** Get the specific Token. */ final public Token getToken(int index) { - Token t = lookingAhead ? jj_scanpos : token; + Token t = token; for (int i = 0; i < index; i++) { if (t.next != null) t = t.next; else t = t.next = token_source.getNextToken(); @@ -852,14 +891,14 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC return t; } - final private int jj_ntk() { + private int jj_ntk() { if ((jj_nt=token.next) == null) return (jj_ntk = (token.next=token_source.getNextToken()).kind); else return (jj_ntk = jj_nt.kind); } - private java.util.Vector jj_expentries = new java.util.Vector(); + private java.util.List jj_expentries = new java.util.ArrayList(); private int[] jj_expentry; private int jj_kind = -1; private int[] jj_lasttokens = new int[100]; @@ -874,31 +913,26 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC for (int i = 0; i < jj_endpos; i++) { jj_expentry[i] = jj_lasttokens[i]; } - boolean exists = false; - for (java.util.Enumeration e = jj_expentries.elements(); e.hasMoreElements();) { - int[] oldentry = (int[])(e.nextElement()); + jj_entries_loop: for (java.util.Iterator it = jj_expentries.iterator(); it.hasNext();) { + int[] oldentry = (int[])(it.next()); if (oldentry.length == jj_expentry.length) { - exists = true; for (int i = 0; i < jj_expentry.length; i++) { if (oldentry[i] != jj_expentry[i]) { - exists = false; - break; + continue jj_entries_loop; } } - if (exists) break; + jj_expentries.add(jj_expentry); + break jj_entries_loop; } } - if (!exists) jj_expentries.addElement(jj_expentry); if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind; } } + /** Generate ParseException. */ public ParseException generateParseException() { - jj_expentries.removeAllElements(); + jj_expentries.clear(); boolean[] la1tokens = new boolean[34]; - for (int i = 0; i < 34; i++) { - la1tokens[i] = false; - } if (jj_kind >= 0) { la1tokens[jj_kind] = true; jj_kind = -1; @@ -919,7 +953,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC if (la1tokens[i]) { jj_expentry = new int[1]; jj_expentry[0] = i; - jj_expentries.addElement(jj_expentry); + jj_expentries.add(jj_expentry); } } jj_endpos = 0; @@ -927,18 +961,20 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC jj_add_error_token(0, 0); int[][] exptokseq = new int[jj_expentries.size()][]; for (int i = 0; i < jj_expentries.size(); i++) { - exptokseq[i] = (int[])jj_expentries.elementAt(i); + exptokseq[i] = jj_expentries.get(i); } return new ParseException(token, exptokseq, tokenImage); } + /** Enable tracing. */ final public void enable_tracing() { } + /** Disable tracing. */ final public void disable_tracing() { } - final private void jj_rescan_token() { + private void jj_rescan_token() { jj_rescan = true; for (int i = 0; i < 2; i++) { try { @@ -958,7 +994,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC jj_rescan = false; } - final private void jj_save(int index, int xla) { + private void jj_save(int index, int xla) { JJCalls p = jj_2_rtns[index]; while (p.gen > jj_gen) { if (p.next == null) { p = p.next = new JJCalls(); break; } diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParser.jj b/src/org/apache/james/mime4j/field/address/parser/AddressListParser.jj deleted file mode 100644 index 685988634..000000000 --- a/src/org/apache/james/mime4j/field/address/parser/AddressListParser.jj +++ /dev/null @@ -1,595 +0,0 @@ -/*@bgen(jjtree) Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParser.jj */ -/*@egen*//**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - - -/** - * RFC2822 address list parser. - * - * Created 9/17/2004 - * by Joe Cheng - */ - -options { - STATIC=false; - LOOKAHEAD=1; - //DEBUG_PARSER=true; - //DEBUG_TOKEN_MANAGER=true; -} - -PARSER_BEGIN(AddressListParser) -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.james.mime4j.field.address.parser; - -public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeConstants/*@egen*/ {/*@bgen(jjtree)*/ - protected JJTAddressListParserState jjtree = new JJTAddressListParserState(); - -/*@egen*/ - public static void main(String args[]) throws ParseException { - while (true) { - try { - AddressListParser parser = new AddressListParser(System.in); - parser.parseLine(); - ((SimpleNode)parser.jjtree.rootNode()).dump("> "); - } catch (Exception x) { - x.printStackTrace(); - return; - } - } - } - - private static void log(String msg) { - System.out.print(msg); - } - - public ASTaddress_list parse() throws ParseException { - try { - parseAll(); - return (ASTaddress_list)jjtree.rootNode(); - } catch (TokenMgrError tme) { - throw new ParseException(tme.getMessage()); - } - } - - - void jjtreeOpenNodeScope(Node n) { - ((SimpleNode)n).firstToken = getToken(1); - } - - void jjtreeCloseNodeScope(Node n) { - ((SimpleNode)n).lastToken = getToken(0); - } -} - -PARSER_END(AddressListParser) - -void parseLine() : -{} -{ - address_list() ["\r"] "\n" -} - -void parseAll() : -{} -{ - address_list() -} - -void address_list() : -{/*@bgen(jjtree) address_list */ - ASTaddress_list jjtn000 = new ASTaddress_list(JJTADDRESS_LIST); - boolean jjtc000 = true; - jjtree.openNodeScope(jjtn000); - jjtreeOpenNodeScope(jjtn000); -/*@egen*/} -{/*@bgen(jjtree) address_list */ - try { -/*@egen*/ - [ address() ] - ( - "," - [ address() ] - )*/*@bgen(jjtree)*/ - } catch (Throwable jjte000) { - if (jjtc000) { - jjtree.clearNodeScope(jjtn000); - jjtc000 = false; - } else { - jjtree.popNode(); - } - if (jjte000 instanceof RuntimeException) { - throw (RuntimeException)jjte000; - } - if (jjte000 instanceof ParseException) { - throw (ParseException)jjte000; - } - throw (Error)jjte000; - } finally { - if (jjtc000) { - jjtree.closeNodeScope(jjtn000, true); - jjtreeCloseNodeScope(jjtn000); - } - } -/*@egen*/ -} - -void address() : -{/*@bgen(jjtree) address */ - ASTaddress jjtn000 = new ASTaddress(JJTADDRESS); - boolean jjtc000 = true; - jjtree.openNodeScope(jjtn000); - jjtreeOpenNodeScope(jjtn000); -/*@egen*/} -{/*@bgen(jjtree) address */ - try { -/*@egen*/ - LOOKAHEAD(2147483647) - addr_spec() -| angle_addr() -| ( phrase() (group_body() | angle_addr()) )/*@bgen(jjtree)*/ - } catch (Throwable jjte000) { - if (jjtc000) { - jjtree.clearNodeScope(jjtn000); - jjtc000 = false; - } else { - jjtree.popNode(); - } - if (jjte000 instanceof RuntimeException) { - throw (RuntimeException)jjte000; - } - if (jjte000 instanceof ParseException) { - throw (ParseException)jjte000; - } - throw (Error)jjte000; - } finally { - if (jjtc000) { - jjtree.closeNodeScope(jjtn000, true); - jjtreeCloseNodeScope(jjtn000); - } - } -/*@egen*/ -} - -void mailbox() : -{/*@bgen(jjtree) mailbox */ - ASTmailbox jjtn000 = new ASTmailbox(JJTMAILBOX); - boolean jjtc000 = true; - jjtree.openNodeScope(jjtn000); - jjtreeOpenNodeScope(jjtn000); -/*@egen*/} -{/*@bgen(jjtree) mailbox */ - try { -/*@egen*/ - LOOKAHEAD(2147483647) - addr_spec() -| angle_addr() -| name_addr()/*@bgen(jjtree)*/ - } catch (Throwable jjte000) { - if (jjtc000) { - jjtree.clearNodeScope(jjtn000); - jjtc000 = false; - } else { - jjtree.popNode(); - } - if (jjte000 instanceof RuntimeException) { - throw (RuntimeException)jjte000; - } - if (jjte000 instanceof ParseException) { - throw (ParseException)jjte000; - } - throw (Error)jjte000; - } finally { - if (jjtc000) { - jjtree.closeNodeScope(jjtn000, true); - jjtreeCloseNodeScope(jjtn000); - } - } -/*@egen*/ -} - -void name_addr() : -{/*@bgen(jjtree) name_addr */ - ASTname_addr jjtn000 = new ASTname_addr(JJTNAME_ADDR); - boolean jjtc000 = true; - jjtree.openNodeScope(jjtn000); - jjtreeOpenNodeScope(jjtn000); -/*@egen*/} -{/*@bgen(jjtree) name_addr */ - try { -/*@egen*/ - phrase() angle_addr()/*@bgen(jjtree)*/ - } catch (Throwable jjte000) { - if (jjtc000) { - jjtree.clearNodeScope(jjtn000); - jjtc000 = false; - } else { - jjtree.popNode(); - } - if (jjte000 instanceof RuntimeException) { - throw (RuntimeException)jjte000; - } - if (jjte000 instanceof ParseException) { - throw (ParseException)jjte000; - } - throw (Error)jjte000; - } finally { - if (jjtc000) { - jjtree.closeNodeScope(jjtn000, true); - jjtreeCloseNodeScope(jjtn000); - } - } -/*@egen*/ -} - -void group_body() : -{/*@bgen(jjtree) group_body */ - ASTgroup_body jjtn000 = new ASTgroup_body(JJTGROUP_BODY); - boolean jjtc000 = true; - jjtree.openNodeScope(jjtn000); - jjtreeOpenNodeScope(jjtn000); -/*@egen*/} -{/*@bgen(jjtree) group_body */ - try { -/*@egen*/ - ":" - [ mailbox() ] - ( - "," - [ mailbox() ] - )* - ";"/*@bgen(jjtree)*/ - } catch (Throwable jjte000) { - if (jjtc000) { - jjtree.clearNodeScope(jjtn000); - jjtc000 = false; - } else { - jjtree.popNode(); - } - if (jjte000 instanceof RuntimeException) { - throw (RuntimeException)jjte000; - } - if (jjte000 instanceof ParseException) { - throw (ParseException)jjte000; - } - throw (Error)jjte000; - } finally { - if (jjtc000) { - jjtree.closeNodeScope(jjtn000, true); - jjtreeCloseNodeScope(jjtn000); - } - } -/*@egen*/ -} - -void angle_addr() : -{/*@bgen(jjtree) angle_addr */ - ASTangle_addr jjtn000 = new ASTangle_addr(JJTANGLE_ADDR); - boolean jjtc000 = true; - jjtree.openNodeScope(jjtn000); - jjtreeOpenNodeScope(jjtn000); -/*@egen*/} -{/*@bgen(jjtree) angle_addr */ - try { -/*@egen*/ - "<" [ route() ] addr_spec() ">"/*@bgen(jjtree)*/ - } catch (Throwable jjte000) { - if (jjtc000) { - jjtree.clearNodeScope(jjtn000); - jjtc000 = false; - } else { - jjtree.popNode(); - } - if (jjte000 instanceof RuntimeException) { - throw (RuntimeException)jjte000; - } - if (jjte000 instanceof ParseException) { - throw (ParseException)jjte000; - } - throw (Error)jjte000; - } finally { - if (jjtc000) { - jjtree.closeNodeScope(jjtn000, true); - jjtreeCloseNodeScope(jjtn000); - } - } -/*@egen*/ -} - -void route() : -{/*@bgen(jjtree) route */ - ASTroute jjtn000 = new ASTroute(JJTROUTE); - boolean jjtc000 = true; - jjtree.openNodeScope(jjtn000); - jjtreeOpenNodeScope(jjtn000); -/*@egen*/} -{/*@bgen(jjtree) route */ - try { -/*@egen*/ - "@" domain() ( (",")* "@" domain() )* ":"/*@bgen(jjtree)*/ - } catch (Throwable jjte000) { - if (jjtc000) { - jjtree.clearNodeScope(jjtn000); - jjtc000 = false; - } else { - jjtree.popNode(); - } - if (jjte000 instanceof RuntimeException) { - throw (RuntimeException)jjte000; - } - if (jjte000 instanceof ParseException) { - throw (ParseException)jjte000; - } - throw (Error)jjte000; - } finally { - if (jjtc000) { - jjtree.closeNodeScope(jjtn000, true); - jjtreeCloseNodeScope(jjtn000); - } - } -/*@egen*/ -} - -void phrase() : -{/*@bgen(jjtree) phrase */ - ASTphrase jjtn000 = new ASTphrase(JJTPHRASE); - boolean jjtc000 = true; - jjtree.openNodeScope(jjtn000); - jjtreeOpenNodeScope(jjtn000); -/*@egen*/} -{/*@bgen(jjtree) phrase */ -try { -/*@egen*/ -( -| -)+/*@bgen(jjtree)*/ -} finally { - if (jjtc000) { - jjtree.closeNodeScope(jjtn000, true); - jjtreeCloseNodeScope(jjtn000); - } -} -/*@egen*/ -} - -void addr_spec() : -{/*@bgen(jjtree) addr_spec */ - ASTaddr_spec jjtn000 = new ASTaddr_spec(JJTADDR_SPEC); - boolean jjtc000 = true; - jjtree.openNodeScope(jjtn000); - jjtreeOpenNodeScope(jjtn000); -/*@egen*/} -{/*@bgen(jjtree) addr_spec */ - try { -/*@egen*/ - ( local_part() "@" domain() )/*@bgen(jjtree)*/ - } catch (Throwable jjte000) { - if (jjtc000) { - jjtree.clearNodeScope(jjtn000); - jjtc000 = false; - } else { - jjtree.popNode(); - } - if (jjte000 instanceof RuntimeException) { - throw (RuntimeException)jjte000; - } - if (jjte000 instanceof ParseException) { - throw (ParseException)jjte000; - } - throw (Error)jjte000; - } finally { - if (jjtc000) { - jjtree.closeNodeScope(jjtn000, true); - jjtreeCloseNodeScope(jjtn000); - } - } -/*@egen*/ -} - -void local_part() : -{/*@bgen(jjtree) local_part */ - ASTlocal_part jjtn000 = new ASTlocal_part(JJTLOCAL_PART); - boolean jjtc000 = true; - jjtree.openNodeScope(jjtn000); - jjtreeOpenNodeScope(jjtn000); -/*@egen*/ Token t; } -{/*@bgen(jjtree) local_part */ - try { -/*@egen*/ - ( t= | t= ) - ( [t="."] - { - if (t.image.charAt(t.image.length() - 1) != '.' || t.kind == AddressListParserConstants.QUOTEDSTRING) - throw new ParseException("Words in local part must be separated by '.'"); - } - ( t= | t= ) - )*/*@bgen(jjtree)*/ - } finally { - if (jjtc000) { - jjtree.closeNodeScope(jjtn000, true); - jjtreeCloseNodeScope(jjtn000); - } - } -/*@egen*/ -} - -void domain() : -{/*@bgen(jjtree) domain */ - ASTdomain jjtn000 = new ASTdomain(JJTDOMAIN); - boolean jjtc000 = true; - jjtree.openNodeScope(jjtn000); - jjtreeOpenNodeScope(jjtn000); -/*@egen*/ Token t; } -{/*@bgen(jjtree) domain */ - try { -/*@egen*/ - ( t= - ( [t="."] - { - if (t.image.charAt(t.image.length() - 1) != '.') - throw new ParseException("Atoms in domain names must be separated by '.'"); - } - t= - )* - ) -| /*@bgen(jjtree)*/ - } finally { - if (jjtc000) { - jjtree.closeNodeScope(jjtn000, true); - jjtreeCloseNodeScope(jjtn000); - } - } -/*@egen*/ -} - -SPECIAL_TOKEN : -{ - < WS: ( [" ", "\t"] )+ > -} - -TOKEN : -{ - < #ALPHA: ["a" - "z", "A" - "Z"] > -| < #DIGIT: ["0" - "9"] > -| < #ATEXT: ( | - | "!" | "#" | "$" | "%" - | "&" | "'" | "*" | "+" - | "-" | "/" | "=" | "?" - | "^" | "_" | "`" | "{" - | "|" | "}" | "~" - )> -| < DOTATOM: ( | "." )* > -} - -TOKEN_MGR_DECLS : -{ - // Keeps track of how many levels of comment nesting - // we've encountered. This is only used when the 2nd - // level is reached, for example ((this)), not (this). - // This is because the outermost level must be treated - // specially anyway, because the outermost ")" has a - // different token type than inner ")" instances. - static int commentNest; -} - -MORE : -{ - // domain literal - "[" : INDOMAINLITERAL -} - - -MORE : -{ - < > { image.deleteCharAt(image.length() - 2); } -| < ~["[", "]", "\\"] > -} - - -TOKEN : -{ - < DOMAINLITERAL: "]" > { matchedToken.image = image.toString(); }: DEFAULT -} - -MORE : -{ - // starts a comment - "(" : INCOMMENT -} - - -SKIP : -{ - // ends a comment - < COMMENT: ")" > : DEFAULT - // if this is ever changed to not be a SKIP, need - // to make sure matchedToken.token = token.toString() - // is called. -} - - -MORE : -{ - < > { image.deleteCharAt(image.length() - 2); } -| "(" { commentNest = 1; } : NESTED_COMMENT -| < > -} - - -MORE : -{ - < > { image.deleteCharAt(image.length() - 2); } -| "(" { ++commentNest; } -| ")" { --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); } -| < > -} - - -// QUOTED STRINGS - -MORE : -{ - "\"" { image.deleteCharAt(image.length() - 1); } : INQUOTEDSTRING -} - - -MORE : -{ - < > { image.deleteCharAt(image.length() - 2); } -| < (~["\"", "\\"])+ > -} - - -TOKEN : -{ - < QUOTEDSTRING: "\"" > { matchedToken.image = image.substring(0, image.length() - 1); } : DEFAULT -} - -// GLOBALS - -<*> -TOKEN : -{ - < #QUOTEDPAIR: "\\" > -| < #ANY: ~[] > -} - -// ERROR! -/* - -<*> -TOKEN : -{ - < UNEXPECTED_CHAR: > -} - -*/ \ No newline at end of file diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParserConstants.java b/src/org/apache/james/mime4j/field/address/parser/AddressListParserConstants.java index a21ad35db..a763ab604 100644 --- a/src/org/apache/james/mime4j/field/address/parser/AddressListParserConstants.java +++ b/src/org/apache/james/mime4j/field/address/parser/AddressListParserConstants.java @@ -1,41 +1,66 @@ /* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParserConstants.java */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.address.parser; + +/** + * Token literal values and constants. + * Generated by org.javacc.parser.OtherFilesGen#start() + */ public interface AddressListParserConstants { + /** End of File. */ int EOF = 0; + /** RegularExpression Id. */ int WS = 10; + /** RegularExpression Id. */ int ALPHA = 11; + /** RegularExpression Id. */ int DIGIT = 12; + /** RegularExpression Id. */ int ATEXT = 13; + /** RegularExpression Id. */ int DOTATOM = 14; + /** RegularExpression Id. */ int DOMAINLITERAL = 18; + /** RegularExpression Id. */ int COMMENT = 20; + /** RegularExpression Id. */ int QUOTEDSTRING = 31; + /** RegularExpression Id. */ int QUOTEDPAIR = 32; + /** RegularExpression Id. */ int ANY = 33; + /** Lexical state. */ int DEFAULT = 0; + /** Lexical state. */ int INDOMAINLITERAL = 1; + /** Lexical state. */ int INCOMMENT = 2; + /** Lexical state. */ int NESTED_COMMENT = 3; + /** Lexical state. */ int INQUOTEDSTRING = 4; + /** Literal token values. */ String[] tokenImage = { "", "\"\\r\"", diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParserTokenManager.java b/src/org/apache/james/mime4j/field/address/parser/AddressListParserTokenManager.java index df8974a3b..d6336d878 100644 --- a/src/org/apache/james/mime4j/field/address/parser/AddressListParserTokenManager.java +++ b/src/org/apache/james/mime4j/field/address/parser/AddressListParserTokenManager.java @@ -1,21 +1,25 @@ /* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParserTokenManager.java */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.address.parser; +/** Token Manager. */ public class AddressListParserTokenManager implements AddressListParserConstants { // Keeps track of how many levels of comment nesting @@ -25,7 +29,10 @@ public class AddressListParserTokenManager implements AddressListParserConstants // specially anyway, because the outermost ")" has a // different token type than inner ")" instances. static int commentNest; + + /** Debug output. */ public java.io.PrintStream debugStream = System.out; + /** Set debug output. */ public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; } private final int jjStopStringLiteralDfa_0(int pos, long active0) { @@ -39,21 +46,13 @@ private final int jjStartNfa_0(int pos, long active0) { return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1); } -private final int jjStopAtPos(int pos, int kind) +private int jjStopAtPos(int pos, int kind) { jjmatchedKind = kind; jjmatchedPos = pos; return pos + 1; } -private final int jjStartNfaWithStates_0(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_0(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_0() +private int jjMoveStringLiteralDfa0_0() { switch(curChar) { @@ -85,44 +84,13 @@ private final int jjMoveStringLiteralDfa0_0() return jjMoveNfa_0(1, 0); } } -private final void jjCheckNAdd(int state) +private int jjMoveNfa_0(int startState, int curPos) { - if (jjrounds[state] != jjround) - { - jjstateSet[jjnewStateCnt++] = state; - jjrounds[state] = jjround; - } -} -private final void jjAddStates(int start, int end) -{ - do { - jjstateSet[jjnewStateCnt++] = jjnextStates[start]; - } while (start++ != end); -} -private final void jjCheckNAddTwoStates(int state1, int state2) -{ - jjCheckNAdd(state1); - jjCheckNAdd(state2); -} -private final void jjCheckNAddStates(int start, int end) -{ - do { - jjCheckNAdd(jjnextStates[start]); - } while (start++ != end); -} -private final void jjCheckNAddStates(int start) -{ - jjCheckNAdd(jjnextStates[start]); - jjCheckNAdd(jjnextStates[start + 1]); -} -private final int jjMoveNfa_0(int startState, int curPos) -{ - int[] nextStates; int startsAt = 0; jjnewStateCnt = 3; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -130,7 +98,7 @@ private final int jjMoveNfa_0(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -168,7 +136,7 @@ private final int jjMoveNfa_0(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -188,7 +156,7 @@ private final int jjMoveNfa_0(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -221,15 +189,7 @@ private final int jjStartNfa_2(int pos, long active0) { return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1); } -private final int jjStartNfaWithStates_2(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_2(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_2() +private int jjMoveStringLiteralDfa0_2() { switch(curChar) { @@ -244,14 +204,13 @@ private final int jjMoveStringLiteralDfa0_2() static final long[] jjbitVec0 = { 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL }; -private final int jjMoveNfa_2(int startState, int curPos) +private int jjMoveNfa_2(int startState, int curPos) { - int[] nextStates; int startsAt = 0; jjnewStateCnt = 3; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -259,7 +218,7 @@ private final int jjMoveNfa_2(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -278,7 +237,7 @@ private final int jjMoveNfa_2(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -304,7 +263,7 @@ private final int jjMoveNfa_2(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -345,15 +304,7 @@ private final int jjStartNfa_4(int pos, long active0) { return jjMoveNfa_4(jjStopStringLiteralDfa_4(pos, active0), pos + 1); } -private final int jjStartNfaWithStates_4(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_4(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_4() +private int jjMoveStringLiteralDfa0_4() { switch(curChar) { @@ -363,14 +314,13 @@ private final int jjMoveStringLiteralDfa0_4() return jjMoveNfa_4(0, 0); } } -private final int jjMoveNfa_4(int startState, int curPos) +private int jjMoveNfa_4(int startState, int curPos) { - int[] nextStates; int startsAt = 0; jjnewStateCnt = 3; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -378,7 +328,7 @@ private final int jjMoveNfa_4(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -401,7 +351,7 @@ private final int jjMoveNfa_4(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -434,7 +384,7 @@ private final int jjMoveNfa_4(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -479,15 +429,7 @@ private final int jjStartNfa_3(int pos, long active0) { return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0), pos + 1); } -private final int jjStartNfaWithStates_3(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_3(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_3() +private int jjMoveStringLiteralDfa0_3() { switch(curChar) { @@ -499,14 +441,13 @@ private final int jjMoveStringLiteralDfa0_3() return jjMoveNfa_3(0, 0); } } -private final int jjMoveNfa_3(int startState, int curPos) +private int jjMoveNfa_3(int startState, int curPos) { - int[] nextStates; int startsAt = 0; jjnewStateCnt = 3; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -514,7 +455,7 @@ private final int jjMoveNfa_3(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -533,7 +474,7 @@ private final int jjMoveNfa_3(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -559,7 +500,7 @@ private final int jjMoveNfa_3(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -600,15 +541,7 @@ private final int jjStartNfa_1(int pos, long active0) { return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1); } -private final int jjStartNfaWithStates_1(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_1(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_1() +private int jjMoveStringLiteralDfa0_1() { switch(curChar) { @@ -618,14 +551,13 @@ private final int jjMoveStringLiteralDfa0_1() return jjMoveNfa_1(0, 0); } } -private final int jjMoveNfa_1(int startState, int curPos) +private int jjMoveNfa_1(int startState, int curPos) { - int[] nextStates; int startsAt = 0; jjnewStateCnt = 3; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -633,7 +565,7 @@ private final int jjMoveNfa_1(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -652,7 +584,7 @@ private final int jjMoveNfa_1(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -681,7 +613,7 @@ private final int jjMoveNfa_1(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -712,17 +644,23 @@ private final int jjMoveNfa_1(int startState, int curPos) } static final int[] jjnextStates = { }; + +/** Token literal values. */ public static final String[] jjstrLiteralImages = { "", "\15", "\12", "\54", "\72", "\73", "\74", "\76", "\100", "\56", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, }; + +/** Lexer state names. */ public static final String[] lexStateNames = { - "DEFAULT", - "INDOMAINLITERAL", - "INCOMMENT", - "NESTED_COMMENT", - "INQUOTEDSTRING", + "DEFAULT", + "INDOMAINLITERAL", + "INCOMMENT", + "NESTED_COMMENT", + "INQUOTEDSTRING", }; + +/** Lex State array. */ public static final int[] jjnewLexState = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 0, 2, 0, -1, 3, -1, -1, -1, -1, -1, 4, -1, -1, 0, -1, -1, @@ -742,19 +680,25 @@ static final long[] jjtoMore = { protected SimpleCharStream input_stream; private final int[] jjrounds = new int[3]; private final int[] jjstateSet = new int[6]; -StringBuffer image; -int jjimageLen; -int lengthOfMatch; +private final StringBuilder jjimage = new StringBuilder(); +private StringBuilder image = jjimage; +private int jjimageLen; +private int lengthOfMatch; protected char curChar; +/** Constructor. */ public AddressListParserTokenManager(SimpleCharStream stream){ if (SimpleCharStream.staticFlag) throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer."); input_stream = stream; } + +/** Constructor. */ public AddressListParserTokenManager(SimpleCharStream stream, int lexState){ this(stream); SwitchTo(lexState); } + +/** Reinitialise parser. */ public void ReInit(SimpleCharStream stream) { jjmatchedPos = jjnewStateCnt = 0; @@ -762,18 +706,22 @@ public void ReInit(SimpleCharStream stream) input_stream = stream; ReInitRounds(); } -private final void ReInitRounds() +private void ReInitRounds() { int i; jjround = 0x80000001; for (i = 3; i-- > 0;) jjrounds[i] = 0x80000000; } + +/** Reinitialise parser. */ public void ReInit(SimpleCharStream stream, int lexState) { ReInit(stream); SwitchTo(lexState); } + +/** Switch to specified lex state. */ public void SwitchTo(int lexState) { if (lexState >= 5 || lexState < 0) @@ -784,14 +732,25 @@ public void SwitchTo(int lexState) protected Token jjFillToken() { - Token t = Token.newToken(jjmatchedKind); - t.kind = jjmatchedKind; + final Token t; + final String curTokenImage; + final int beginLine; + final int endLine; + final int beginColumn; + final int endColumn; String im = jjstrLiteralImages[jjmatchedKind]; - t.image = (im == null) ? input_stream.GetImage() : im; - t.beginLine = input_stream.getBeginLine(); - t.beginColumn = input_stream.getBeginColumn(); - t.endLine = input_stream.getEndLine(); - t.endColumn = input_stream.getEndColumn(); + curTokenImage = (im == null) ? input_stream.GetImage() : im; + beginLine = input_stream.getBeginLine(); + beginColumn = input_stream.getBeginColumn(); + endLine = input_stream.getEndLine(); + endColumn = input_stream.getEndColumn(); + t = Token.newToken(jjmatchedKind, curTokenImage); + + t.beginLine = beginLine; + t.endLine = endLine; + t.beginColumn = beginColumn; + t.endColumn = endColumn; + return t; } @@ -802,28 +761,29 @@ int jjround; int jjmatchedPos; int jjmatchedKind; +/** Get the next Token. */ public Token getNextToken() { - int kind; Token specialToken = null; Token matchedToken; int curPos = 0; EOFLoop : for (;;) - { - try - { + { + try + { curChar = input_stream.BeginToken(); - } + } catch(java.io.IOException e) - { + { jjmatchedKind = 0; matchedToken = jjFillToken(); matchedToken.specialToken = specialToken; return matchedToken; } - image = null; + image = jjimage; + image.setLength(0); jjimageLen = 0; for (;;) @@ -927,62 +887,46 @@ void MoreLexicalActions() switch(jjmatchedKind) { case 16 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; image.deleteCharAt(image.length() - 2); break; case 21 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; image.deleteCharAt(image.length() - 2); break; case 22 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; commentNest = 1; break; case 24 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; image.deleteCharAt(image.length() - 2); break; case 25 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; ++commentNest; break; case 26 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); break; case 28 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; image.deleteCharAt(image.length() - 1); break; case 29 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; image.deleteCharAt(image.length() - 2); break; - default : + default : break; } } @@ -991,19 +935,35 @@ void TokenLexicalActions(Token matchedToken) switch(jjmatchedKind) { case 18 : - if (image == null) - image = new StringBuffer(); - image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); matchedToken.image = image.toString(); break; case 31 : - if (image == null) - image = new StringBuffer(); - image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); matchedToken.image = image.substring(0, image.length() - 1); break; - default : + default : break; } } +private void jjCheckNAdd(int state) +{ + if (jjrounds[state] != jjround) + { + jjstateSet[jjnewStateCnt++] = state; + jjrounds[state] = jjround; + } +} +private void jjAddStates(int start, int end) +{ + do { + jjstateSet[jjnewStateCnt++] = jjnextStates[start]; + } while (start++ != end); +} +private void jjCheckNAddTwoStates(int state1, int state2) +{ + jjCheckNAdd(state1); + jjCheckNAdd(state2); +} + } diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java b/src/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java index 5987f19d8..38e6ca7b1 100644 --- a/src/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java +++ b/src/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java @@ -1,5 +1,4 @@ -/* Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java */ - +/* Generated By:JavaCC: Do not edit this line. AddressListParserTreeConstants.java Version 5.0 */ package org.apache.james.mime4j.field.address.parser; public interface AddressListParserTreeConstants @@ -33,3 +32,4 @@ public interface AddressListParserTreeConstants "domain", }; } +/* JavaCC - OriginalChecksum=e7d2b24000a70a573955cf10036f0056 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java b/src/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java index 8ec2fe7d2..674ffc5ed 100644 --- a/src/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java +++ b/src/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java @@ -1,5 +1,4 @@ -/* Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java */ - +/* Generated By:JavaCC: Do not edit this line. AddressListParserVisitor.java Version 5.0 */ package org.apache.james.mime4j.field.address.parser; public interface AddressListParserVisitor @@ -17,3 +16,4 @@ public interface AddressListParserVisitor public Object visit(ASTlocal_part node, Object data); public Object visit(ASTdomain node, Object data); } +/* JavaCC - OriginalChecksum=f57edd9a1eb17afa5907a87b3c51e284 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/BaseNode.java b/src/org/apache/james/mime4j/field/address/parser/BaseNode.java index 780974616..b7a901f4f 100644 --- a/src/org/apache/james/mime4j/field/address/parser/BaseNode.java +++ b/src/org/apache/james/mime4j/field/address/parser/BaseNode.java @@ -19,8 +19,6 @@ package org.apache.james.mime4j.field.address.parser; -import org.apache.james.mime4j.field.address.parser.Node; -import org.apache.james.mime4j.field.address.parser.Token; public abstract class BaseNode implements Node { diff --git a/src/org/apache/james/mime4j/field/address/parser/Builder.java b/src/org/apache/james/mime4j/field/address/parser/Builder.java new file mode 100644 index 000000000..c4541a72b --- /dev/null +++ b/src/org/apache/james/mime4j/field/address/parser/Builder.java @@ -0,0 +1,229 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address.parser; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.codec.DecoderUtil; +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.AddressList; +import org.apache.james.mime4j.dom.address.DomainList; +import org.apache.james.mime4j.dom.address.Group; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.address.MailboxList; + +/** + * Transforms the JJTree-generated abstract syntax tree into a graph of + * org.apache.james.mime4j.dom.address objects. + */ +class Builder { + + private static Builder singleton = new Builder(); + + public static Builder getInstance() { + return singleton; + } + + public AddressList buildAddressList(ASTaddress_list node, DecodeMonitor monitor) throws ParseException { + List
list = new ArrayList
(); + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + ASTaddress childNode = (ASTaddress) node.jjtGetChild(i); + Address address = buildAddress(childNode, monitor); + list.add(address); + } + return new AddressList(list, true); + } + + public Address buildAddress(ASTaddress node, DecodeMonitor monitor) throws ParseException { + ChildNodeIterator it = new ChildNodeIterator(node); + Node n = it.next(); + if (n instanceof ASTaddr_spec) { + return buildAddrSpec((ASTaddr_spec) n); + } else if (n instanceof ASTangle_addr) { + return buildAngleAddr((ASTangle_addr) n); + } else if (n instanceof ASTphrase) { + String name = buildString((ASTphrase) n, false); + Node n2 = it.next(); + if (n2 instanceof ASTgroup_body) { + return new Group(name, buildGroupBody((ASTgroup_body) n2, monitor)); + } else if (n2 instanceof ASTangle_addr) { + try { + name = DecoderUtil.decodeEncodedWords(name, monitor); + } catch (IllegalArgumentException e) { + throw new ParseException(e.getMessage()); + } + Mailbox mb = buildAngleAddr((ASTangle_addr) n2); + return new Mailbox(name, mb.getRoute(), mb.getLocalPart(), + mb.getDomain()); + } else { + throw new ParseException(); + } + } else { + throw new ParseException(); + } + } + + private MailboxList buildGroupBody(ASTgroup_body node, DecodeMonitor monitor) throws ParseException { + List results = new ArrayList(); + ChildNodeIterator it = new ChildNodeIterator(node); + while (it.hasNext()) { + Node n = it.next(); + if (n instanceof ASTmailbox) + results.add(buildMailbox((ASTmailbox) n, monitor)); + else + throw new ParseException(); + } + return new MailboxList(results, true); + } + + public Mailbox buildMailbox(ASTmailbox node, DecodeMonitor monitor) throws ParseException { + ChildNodeIterator it = new ChildNodeIterator(node); + Node n = it.next(); + if (n instanceof ASTaddr_spec) { + return buildAddrSpec((ASTaddr_spec) n); + } else if (n instanceof ASTangle_addr) { + return buildAngleAddr((ASTangle_addr) n); + } else if (n instanceof ASTname_addr) { + return buildNameAddr((ASTname_addr) n, monitor); + } else { + throw new ParseException(); + } + } + + private Mailbox buildNameAddr(ASTname_addr node, DecodeMonitor monitor) throws ParseException { + ChildNodeIterator it = new ChildNodeIterator(node); + Node n = it.next(); + String name; + if (n instanceof ASTphrase) { + name = buildString((ASTphrase) n, false); + } else { + throw new ParseException(); + } + + n = it.next(); + if (n instanceof ASTangle_addr) { + try { + name = DecoderUtil.decodeEncodedWords(name, monitor); + } catch (IllegalArgumentException e) { + throw new ParseException(e.getMessage()); + } + Mailbox mb = buildAngleAddr((ASTangle_addr) n); + return new Mailbox(name, mb.getRoute(), mb.getLocalPart(), + mb.getDomain()); + } else { + throw new ParseException(); + } + } + + private Mailbox buildAngleAddr(ASTangle_addr node) throws ParseException { + ChildNodeIterator it = new ChildNodeIterator(node); + DomainList route = null; + Node n = it.next(); + if (n instanceof ASTroute) { + route = buildRoute((ASTroute) n); + n = it.next(); + } else if (n instanceof ASTaddr_spec) { + // do nothing + } + else + throw new ParseException(); + + if (n instanceof ASTaddr_spec) + return buildAddrSpec(route, (ASTaddr_spec) n); + else + throw new ParseException(); + } + + private DomainList buildRoute(ASTroute node) throws ParseException { + List results = new ArrayList(node.jjtGetNumChildren()); + ChildNodeIterator it = new ChildNodeIterator(node); + while (it.hasNext()) { + Node n = it.next(); + if (n instanceof ASTdomain) + results.add(buildString((ASTdomain) n, true)); + else + throw new ParseException(); + } + return new DomainList(results, true); + } + + private Mailbox buildAddrSpec(ASTaddr_spec node) { + return buildAddrSpec(null, node); + } + + private Mailbox buildAddrSpec(DomainList route, ASTaddr_spec node) { + ChildNodeIterator it = new ChildNodeIterator(node); + String localPart = buildString((ASTlocal_part) it.next(), true); + String domain = buildString((ASTdomain) it.next(), true); + return new Mailbox(route, localPart, domain); + } + + private String buildString(SimpleNode node, boolean stripSpaces) { + Token head = node.firstToken; + Token tail = node.lastToken; + StringBuilder out = new StringBuilder(); + + while (head != tail) { + out.append(head.image); + head = head.next; + if (!stripSpaces) + addSpecials(out, head.specialToken); + } + out.append(tail.image); + + return out.toString(); + } + + private void addSpecials(StringBuilder out, Token specialToken) { + if (specialToken != null) { + addSpecials(out, specialToken.specialToken); + out.append(specialToken.image); + } + } + + private static class ChildNodeIterator implements Iterator { + + private SimpleNode simpleNode; + private int index; + private int len; + + public ChildNodeIterator(SimpleNode simpleNode) { + this.simpleNode = simpleNode; + this.len = simpleNode.jjtGetNumChildren(); + this.index = 0; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public boolean hasNext() { + return index < len; + } + + public Node next() { + return simpleNode.jjtGetChild(index++); + } + + } +} diff --git a/src/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java b/src/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java index cca539483..0a88d8241 100644 --- a/src/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java +++ b/src/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java @@ -1,18 +1,17 @@ -/* Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java */ - +/* Generated By:JavaCC: Do not edit this line. JJTAddressListParserState.java Version 5.0 */ package org.apache.james.mime4j.field.address.parser; -class JJTAddressListParserState { - private java.util.Stack nodes; - private java.util.Stack marks; +public class JJTAddressListParserState { + private java.util.List nodes; + private java.util.List marks; - private int sp; // number of nodes on stack - private int mk; // current mark + private int sp; // number of nodes on stack + private int mk; // current mark private boolean node_created; - JJTAddressListParserState() { - nodes = new java.util.Stack(); - marks = new java.util.Stack(); + public JJTAddressListParserState() { + nodes = new java.util.ArrayList(); + marks = new java.util.ArrayList(); sp = 0; mk = 0; } @@ -20,62 +19,62 @@ class JJTAddressListParserState { /* Determines whether the current node was actually closed and pushed. This should only be called in the final user action of a node scope. */ - boolean nodeCreated() { + public boolean nodeCreated() { return node_created; } /* Call this to reinitialize the node stack. It is called automatically by the parser's ReInit() method. */ - void reset() { - nodes.removeAllElements(); - marks.removeAllElements(); + public void reset() { + nodes.clear(); + marks.clear(); sp = 0; mk = 0; } /* Returns the root node of the AST. It only makes sense to call this after a successful parse. */ - Node rootNode() { - return (Node)nodes.elementAt(0); + public Node rootNode() { + return nodes.get(0); } /* Pushes a node on to the stack. */ - void pushNode(Node n) { - nodes.push(n); + public void pushNode(Node n) { + nodes.add(n); ++sp; } /* Returns the node on the top of the stack, and remove it from the stack. */ - Node popNode() { + public Node popNode() { if (--sp < mk) { - mk = ((Integer)marks.pop()).intValue(); + mk = marks.remove(marks.size()-1); } - return (Node)nodes.pop(); + return nodes.remove(nodes.size()-1); } /* Returns the node currently on the top of the stack. */ - Node peekNode() { - return (Node)nodes.peek(); + public Node peekNode() { + return nodes.get(nodes.size()-1); } /* Returns the number of children on the stack in the current node scope. */ - int nodeArity() { + public int nodeArity() { return sp - mk; } - void clearNodeScope(Node n) { + public void clearNodeScope(Node n) { while (sp > mk) { popNode(); } - mk = ((Integer)marks.pop()).intValue(); + mk = marks.remove(marks.size()-1); } - void openNodeScope(Node n) { - marks.push(new Integer(mk)); + public void openNodeScope(Node n) { + marks.add(mk); mk = sp; n.jjtOpen(); } @@ -85,8 +84,8 @@ class JJTAddressListParserState { children. That number of nodes are popped from the stack and made the children of the definite node. Then the definite node is pushed on to the stack. */ - void closeNodeScope(Node n, int num) { - mk = ((Integer)marks.pop()).intValue(); + public void closeNodeScope(Node n, int num) { + mk = marks.remove(marks.size()-1); while (num-- > 0) { Node c = popNode(); c.jjtSetParent(n); @@ -100,24 +99,25 @@ class JJTAddressListParserState { /* A conditional node is constructed if its condition is true. All the nodes that have been pushed since the node was opened are - made children of the the conditional node, which is then pushed + made children of the conditional node, which is then pushed on to the stack. If the condition is false the node is not constructed and they are left on the stack. */ - void closeNodeScope(Node n, boolean condition) { + public void closeNodeScope(Node n, boolean condition) { if (condition) { int a = nodeArity(); - mk = ((Integer)marks.pop()).intValue(); + mk = marks.remove(marks.size()-1); while (a-- > 0) { - Node c = popNode(); - c.jjtSetParent(n); - n.jjtAddChild(c, a); + Node c = popNode(); + c.jjtSetParent(n); + n.jjtAddChild(c, a); } n.jjtClose(); pushNode(n); node_created = true; } else { - mk = ((Integer)marks.pop()).intValue(); + mk = marks.remove(marks.size()-1); node_created = false; } } } +/* JavaCC - OriginalChecksum=e61a7a50ac6b2ea6fa91b5482005b3ce (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/Node.java b/src/org/apache/james/mime4j/field/address/parser/Node.java index 158892016..8bf146b4e 100644 --- a/src/org/apache/james/mime4j/field/address/parser/Node.java +++ b/src/org/apache/james/mime4j/field/address/parser/Node.java @@ -1,12 +1,13 @@ -/* Generated By:JJTree: Do not edit this line. Node.java */ - +/* Generated By:JJTree: Do not edit this line. Node.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; /* All AST nodes must implement this interface. It provides basic machinery for constructing the parent and child relationships between nodes. */ -public interface Node { +public +interface Node { /** This method is called after the node has been made the current node. It indicates that child nodes can now be added to it. */ @@ -35,3 +36,4 @@ public interface Node { /** Accept the visitor. **/ public Object jjtAccept(AddressListParserVisitor visitor, Object data); } +/* JavaCC - OriginalChecksum=b3b1bedb94f5dcae7af8d6e02371fe1e (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/ParseException.java b/src/org/apache/james/mime4j/field/address/parser/ParseException.java index 939c6cfed..e48ce5947 100644 --- a/src/org/apache/james/mime4j/field/address/parser/ParseException.java +++ b/src/org/apache/james/mime4j/field/address/parser/ParseException.java @@ -1,19 +1,22 @@ /* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.address.parser; /** @@ -22,10 +25,15 @@ package org.apache.james.mime4j.field.address.parser; * calling the method generateParseException in the generated * parser. * - * You can modify this class to customize your error reporting - * mechanisms so long as you retain the public fields. + * Changes for Mime4J: + * extends org.apache.james.mime4j.field.ParseException + * added serialVersionUID + * added constructor ParseException(Throwable) + * default detail message is "Cannot parse field" */ -public class ParseException extends Exception { +public class ParseException extends org.apache.james.mime4j.dom.field.ParseException { + + private static final long serialVersionUID = 1L; /** * This constructor is used by the method "generateParseException" @@ -62,7 +70,12 @@ public class ParseException extends Exception { */ public ParseException() { - super(); + super("Cannot parse field"); + specialConstructor = false; + } + + public ParseException(Throwable cause) { + super(cause); specialConstructor = false; } diff --git a/src/org/apache/james/mime4j/field/address/parser/SimpleCharStream.java b/src/org/apache/james/mime4j/field/address/parser/SimpleCharStream.java index 957bbeabc..64513a966 100644 --- a/src/org/apache/james/mime4j/field/address/parser/SimpleCharStream.java +++ b/src/org/apache/james/mime4j/field/address/parser/SimpleCharStream.java @@ -1,19 +1,23 @@ -/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 4.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 5.0 */ +/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.address.parser; /** @@ -23,10 +27,12 @@ package org.apache.james.mime4j.field.address.parser; public class SimpleCharStream { +/** Whether parser is static. */ public static final boolean staticFlag = false; int bufsize; int available; int tokenBegin; +/** Position in buffer. */ public int bufpos = -1; protected int bufline[]; protected int bufcolumn[]; @@ -50,210 +56,218 @@ public class SimpleCharStream protected void ExpandBuff(boolean wrapAround) { - char[] newbuffer = new char[bufsize + 2048]; - int newbufline[] = new int[bufsize + 2048]; - int newbufcolumn[] = new int[bufsize + 2048]; + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; - try - { - if (wrapAround) - { - System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); - System.arraycopy(buffer, 0, newbuffer, - bufsize - tokenBegin, bufpos); - buffer = newbuffer; + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos); + buffer = newbuffer; - System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); - System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); - bufline = newbufline; + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; - System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); - System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); - bufcolumn = newbufcolumn; + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; - maxNextCharInd = (bufpos += (bufsize - tokenBegin)); - } - else - { - System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); - buffer = newbuffer; + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; - System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); - bufline = newbufline; + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; - System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); - bufcolumn = newbufcolumn; + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; - maxNextCharInd = (bufpos -= tokenBegin); - } - } - catch (Throwable t) - { - throw new Error(t.getMessage()); - } + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } - bufsize += 2048; - available = bufsize; - tokenBegin = 0; + bufsize += 2048; + available = bufsize; + tokenBegin = 0; } protected void FillBuff() throws java.io.IOException { - if (maxNextCharInd == available) - { - if (available == bufsize) + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) { - if (tokenBegin > 2048) - { - bufpos = maxNextCharInd = 0; - available = tokenBegin; - } - else if (tokenBegin < 0) - bufpos = maxNextCharInd = 0; - else - ExpandBuff(false); + bufpos = maxNextCharInd = 0; + available = tokenBegin; } - else if (available > tokenBegin) - available = bufsize; - else if ((tokenBegin - available) < 2048) - ExpandBuff(true); + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; else - available = tokenBegin; - } + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } - int i; - try { - if ((i = inputStream.read(buffer, maxNextCharInd, - available - maxNextCharInd)) == -1) - { - inputStream.close(); - throw new java.io.IOException(); - } - else - maxNextCharInd += i; - return; - } - catch(java.io.IOException e) { - --bufpos; - backup(0); - if (tokenBegin == -1) - tokenBegin = bufpos; - throw e; - } + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } } +/** Start. */ public char BeginToken() throws java.io.IOException { - tokenBegin = -1; - char c = readChar(); - tokenBegin = bufpos; + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; - return c; + return c; } protected void UpdateLineColumn(char c) { - column++; + column++; - if (prevCharIsLF) - { - prevCharIsLF = false; + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else line += (column = 1); - } - else if (prevCharIsCR) - { - prevCharIsCR = false; - if (c == '\n') - { - prevCharIsLF = true; - } - else - line += (column = 1); - } + } - switch (c) - { - case '\r' : - prevCharIsCR = true; - break; - case '\n' : - prevCharIsLF = true; - break; - case '\t' : - column--; - column += (tabSize - (column % tabSize)); - break; - default : - break; - } + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (tabSize - (column % tabSize)); + break; + default : + break; + } - bufline[bufpos] = line; - bufcolumn[bufpos] = column; + bufline[bufpos] = line; + bufcolumn[bufpos] = column; } +/** Read a character. */ public char readChar() throws java.io.IOException { - if (inBuf > 0) - { - --inBuf; + if (inBuf > 0) + { + --inBuf; - if (++bufpos == bufsize) - bufpos = 0; + if (++bufpos == bufsize) + bufpos = 0; - return buffer[bufpos]; - } + return buffer[bufpos]; + } - if (++bufpos >= maxNextCharInd) - FillBuff(); + if (++bufpos >= maxNextCharInd) + FillBuff(); - char c = buffer[bufpos]; + char c = buffer[bufpos]; - UpdateLineColumn(c); - return (c); + UpdateLineColumn(c); + return c; } + @Deprecated /** - * @deprecated + * @deprecated * @see #getEndColumn */ public int getColumn() { - return bufcolumn[bufpos]; + return bufcolumn[bufpos]; } + @Deprecated /** - * @deprecated + * @deprecated * @see #getEndLine */ public int getLine() { - return bufline[bufpos]; + return bufline[bufpos]; } + /** Get token end column number. */ public int getEndColumn() { - return bufcolumn[bufpos]; + return bufcolumn[bufpos]; } + /** Get token end line number. */ public int getEndLine() { return bufline[bufpos]; } + /** Get token beginning column number. */ public int getBeginColumn() { - return bufcolumn[tokenBegin]; + return bufcolumn[tokenBegin]; } + /** Get token beginning line number. */ public int getBeginLine() { - return bufline[tokenBegin]; + return bufline[tokenBegin]; } +/** Backup a number of characters. */ public void backup(int amount) { inBuf += amount; if ((bufpos -= amount) < 0) - bufpos += bufsize; + bufpos += bufsize; } + /** Constructor. */ public SimpleCharStream(java.io.Reader dstream, int startline, int startcolumn, int buffersize) { @@ -267,16 +281,20 @@ public class SimpleCharStream bufcolumn = new int[buffersize]; } + /** Constructor. */ public SimpleCharStream(java.io.Reader dstream, int startline, int startcolumn) { - this(dstream, startline, startcolumn, 4096); + this(dstream, startline, startcolumn, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.Reader dstream) { - this(dstream, 1, 1, 4096); + this(dstream, 1, 1, 4096); } + + /** Reinitialise. */ public void ReInit(java.io.Reader dstream, int startline, int startcolumn, int buffersize) { @@ -296,111 +314,128 @@ public class SimpleCharStream bufpos = -1; } + /** Reinitialise. */ public void ReInit(java.io.Reader dstream, int startline, int startcolumn) { - ReInit(dstream, startline, startcolumn, 4096); + ReInit(dstream, startline, startcolumn, 4096); } + /** Reinitialise. */ public void ReInit(java.io.Reader dstream) { - ReInit(dstream, 1, 1, 4096); + ReInit(dstream, 1, 1, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException { - this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) { - this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, int startcolumn) throws java.io.UnsupportedEncodingException { - this(dstream, encoding, startline, startcolumn, 4096); + this(dstream, encoding, startline, startcolumn, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, int startline, int startcolumn) { - this(dstream, startline, startcolumn, 4096); + this(dstream, startline, startcolumn, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException { - this(dstream, encoding, 1, 1, 4096); + this(dstream, encoding, 1, 1, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream) { - this(dstream, 1, 1, 4096); + this(dstream, 1, 1, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding, int startline, int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException { - ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) { - ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException { - ReInit(dstream, encoding, 1, 1, 4096); + ReInit(dstream, encoding, 1, 1, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream) { - ReInit(dstream, 1, 1, 4096); + ReInit(dstream, 1, 1, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding, int startline, int startcolumn) throws java.io.UnsupportedEncodingException { - ReInit(dstream, encoding, startline, startcolumn, 4096); + ReInit(dstream, encoding, startline, startcolumn, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, int startline, int startcolumn) { - ReInit(dstream, startline, startcolumn, 4096); + ReInit(dstream, startline, startcolumn, 4096); } + /** Get token literal value. */ public String GetImage() { - if (bufpos >= tokenBegin) - return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); - else - return new String(buffer, tokenBegin, bufsize - tokenBegin) + - new String(buffer, 0, bufpos + 1); + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); } + /** Get the suffix. */ public char[] GetSuffix(int len) { - char[] ret = new char[len]; + char[] ret = new char[len]; - if ((bufpos + 1) >= len) - System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); - else - { - System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, - len - bufpos - 1); - System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); - } + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } - return ret; + return ret; } + /** Reset buffer when finished. */ public void Done() { - buffer = null; - bufline = null; - bufcolumn = null; + buffer = null; + bufline = null; + bufcolumn = null; } /** @@ -408,47 +443,47 @@ public class SimpleCharStream */ public void adjustBeginLineColumn(int newLine, int newCol) { - int start = tokenBegin; - int len; + int start = tokenBegin; + int len; - if (bufpos >= tokenBegin) - { - len = bufpos - tokenBegin + inBuf + 1; - } - else - { - len = bufsize - tokenBegin + bufpos + 1 + inBuf; - } + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } - int i = 0, j = 0, k = 0; - int nextColDiff = 0, columnDiff = 0; + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; - while (i < len && - bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) - { - bufline[j] = newLine; - nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; - bufcolumn[j] = newCol + columnDiff; - columnDiff = nextColDiff; - i++; - } + while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } - if (i < len) - { - bufline[j] = newLine++; - bufcolumn[j] = newCol + columnDiff; + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; - while (i++ < len) - { - if (bufline[j = start % bufsize] != bufline[++start % bufsize]) - bufline[j] = newLine++; - else - bufline[j] = newLine; - } - } + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } - line = bufline[j]; - column = bufcolumn[j]; + line = bufline[j]; + column = bufcolumn[j]; } } +/* JavaCC - OriginalChecksum=aa11a0881c952a5f8ec0ffbd5648e1c4 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/SimpleNode.java b/src/org/apache/james/mime4j/field/address/parser/SimpleNode.java index 9bf537e60..1847a3bc9 100644 --- a/src/org/apache/james/mime4j/field/address/parser/SimpleNode.java +++ b/src/org/apache/james/mime4j/field/address/parser/SimpleNode.java @@ -1,11 +1,14 @@ -/* Generated By:JJTree: Do not edit this line. SimpleNode.java */ - +/* Generated By:JJTree: Do not edit this line. SimpleNode.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ package org.apache.james.mime4j.field.address.parser; -public class SimpleNode extends org.apache.james.mime4j.field.address.parser.BaseNode implements Node { +public +class SimpleNode extends org.apache.james.mime4j.field.address.parser.BaseNode implements Node { + protected Node parent; protected Node[] children; protected int id; + protected Object value; protected AddressListParser parser; public SimpleNode(int i) { @@ -22,7 +25,7 @@ public class SimpleNode extends org.apache.james.mime4j.field.address.parser.Bas public void jjtClose() { } - + public void jjtSetParent(Node n) { parent = n; } public Node jjtGetParent() { return parent; } @@ -45,13 +48,18 @@ public class SimpleNode extends org.apache.james.mime4j.field.address.parser.Bas return (children == null) ? 0 : children.length; } + public void jjtSetValue(Object value) { this.value = value; } + public Object jjtGetValue() { return value; } + /** Accept the visitor. **/ - public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + public Object jjtAccept(AddressListParserVisitor visitor, Object data) +{ return visitor.visit(this, data); } /** Accept the visitor. **/ - public Object childrenAccept(AddressListParserVisitor visitor, Object data) { + public Object childrenAccept(AddressListParserVisitor visitor, Object data) +{ if (children != null) { for (int i = 0; i < children.length; ++i) { children[i].jjtAccept(visitor, data); @@ -76,12 +84,13 @@ public class SimpleNode extends org.apache.james.mime4j.field.address.parser.Bas System.out.println(toString(prefix)); if (children != null) { for (int i = 0; i < children.length; ++i) { - SimpleNode n = (SimpleNode)children[i]; - if (n != null) { - n.dump(prefix + " "); - } + SimpleNode n = (SimpleNode)children[i]; + if (n != null) { + n.dump(prefix + " "); + } } } } } +/* JavaCC - OriginalChecksum=055879471a7647d34de4235b3186aa17 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/Token.java b/src/org/apache/james/mime4j/field/address/parser/Token.java index 0228aac3e..97499f407 100644 --- a/src/org/apache/james/mime4j/field/address/parser/Token.java +++ b/src/org/apache/james/mime4j/field/address/parser/Token.java @@ -1,26 +1,37 @@ -/* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* Generated By:JavaCC: Do not edit this line. Token.java Version 5.0 */ +/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.address.parser; /** * Describes the input token stream. */ -public class Token { +public class Token implements java.io.Serializable { + + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; /** * An integer that describes the kind of this token. This numbering @@ -29,12 +40,14 @@ public class Token { */ public int kind; - /** - * beginLine and beginColumn describe the position of the first character - * of this token; endLine and endColumn describe the position of the - * last character of this token. - */ - public int beginLine, beginColumn, endLine, endColumn; + /** The line number of the first character of this Token. */ + public int beginLine; + /** The column number of the first character of this Token. */ + public int beginColumn; + /** The line number of the last character of this Token. */ + public int endLine; + /** The column number of the last character of this Token. */ + public int endColumn; /** * The string image of the token. @@ -65,12 +78,46 @@ public class Token { */ public Token specialToken; + /** + * An optional attribute value of the Token. + * Tokens which are not used as syntactic sugar will often contain + * meaningful values that will be used later on by the compiler or + * interpreter. This attribute value is often different from the image. + * Any subclass of Token that actually wants to return a non-null value can + * override this method as appropriate. + */ + public Object getValue() { + return null; + } + + /** + * No-argument constructor + */ + public Token() {} + + /** + * Constructs a new token for the specified Image. + */ + public Token(int kind) + { + this(kind, null); + } + + /** + * Constructs a new token for the specified Image and Kind. + */ + public Token(int kind, String image) + { + this.kind = kind; + this.image = image; + } + /** * Returns the image. */ public String toString() { - return image; + return image; } /** @@ -78,19 +125,25 @@ public class Token { * can create and return subclass objects based on the value of ofKind. * Simply add the cases to the switch for all those special cases. * For example, if you have a subclass of Token called IDToken that - * you want to create if ofKind is ID, simlpy add something like : + * you want to create if ofKind is ID, simply add something like : * - * case MyParserConstants.ID : return new IDToken(); + * case MyParserConstants.ID : return new IDToken(ofKind, image); * * to the following switch statement. Then you can cast matchedToken - * variable to the appropriate type and use it in your lexical actions. + * variable to the appropriate type and use sit in your lexical actions. */ - public static final Token newToken(int ofKind) + public static Token newToken(int ofKind, String image) { - switch(ofKind) - { - default : return new Token(); - } + switch(ofKind) + { + default : return new Token(ofKind, image); + } + } + + public static Token newToken(int ofKind) + { + return newToken(ofKind, null); } } +/* JavaCC - OriginalChecksum=a51dc23d1faa88394342107e41df07b3 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/address/parser/TokenMgrError.java b/src/org/apache/james/mime4j/field/address/parser/TokenMgrError.java index c06a44cf3..b9302c43d 100644 --- a/src/org/apache/james/mime4j/field/address/parser/TokenMgrError.java +++ b/src/org/apache/james/mime4j/field/address/parser/TokenMgrError.java @@ -1,148 +1,165 @@ -/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 3.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */ +/* JavaCCOptions: */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.address.parser; +/** Token Manager Error. */ public class TokenMgrError extends Error { - /* - * Ordinals for various reasons why an Error of this type can be thrown. - */ - /** - * Lexical error occured. - */ - static final int LEXICAL_ERROR = 0; + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; - /** - * An attempt wass made to create a second instance of a static token manager. - */ - static final int STATIC_LEXER_ERROR = 1; + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ - /** - * Tried to change to an invalid lexical state. - */ - static final int INVALID_LEXICAL_STATE = 2; + /** + * Lexical error occurred. + */ + static final int LEXICAL_ERROR = 0; - /** - * Detected (and bailed out of) an infinite loop in the token manager. - */ - static final int LOOP_DETECTED = 3; + /** + * An attempt was made to create a second instance of a static token manager. + */ + static final int STATIC_LEXER_ERROR = 1; - /** - * Indicates the reason why the exception is thrown. It will have - * one of the above 4 values. - */ - int errorCode; + /** + * Tried to change to an invalid lexical state. + */ + static final int INVALID_LEXICAL_STATE = 2; - /** - * Replaces unprintable characters by their espaced (or unicode escaped) - * equivalents in the given string - */ - protected static final String addEscapes(String str) { - StringBuffer retval = new StringBuffer(); - char ch; - for (int i = 0; i < str.length(); i++) { - switch (str.charAt(i)) - { - case 0 : - continue; - case '\b': - retval.append("\\b"); - continue; - case '\t': - retval.append("\\t"); - continue; - case '\n': - retval.append("\\n"); - continue; - case '\f': - retval.append("\\f"); - continue; - case '\r': - retval.append("\\r"); - continue; - case '\"': - retval.append("\\\""); - continue; - case '\'': - retval.append("\\\'"); - continue; - case '\\': - retval.append("\\\\"); - continue; - default: - if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { - String s = "0000" + Integer.toString(ch, 16); - retval.append("\\u" + s.substring(s.length() - 4, s.length())); - } else { - retval.append(ch); - } - continue; - } + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + static final int LOOP_DETECTED = 3; + + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + int errorCode; + + /** + * Replaces unprintable characters by their escaped (or unicode escaped) + * equivalents in the given string + */ + protected static final String addEscapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; } - return retval.toString(); - } + } + return retval.toString(); + } - /** - * Returns a detailed message for the Error when it is thrown by the - * token manager to indicate a lexical error. - * Parameters : - * EOFSeen : indicates if EOF caused the lexicl error - * curLexState : lexical state in which this error occured - * errorLine : line number when the error occured - * errorColumn : column number when the error occured - * errorAfter : prefix that was seen before this error occured - * curchar : the offending character - * Note: You can customize the lexical error message by modifying this method. - */ - protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { - return("Lexical error at line " + - errorLine + ", column " + - errorColumn + ". Encountered: " + - (EOFSeen ? " " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + - "after : \"" + addEscapes(errorAfter) + "\""); - } + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * Parameters : + * EOFSeen : indicates if EOF caused the lexical error + * curLexState : lexical state in which this error occurred + * errorLine : line number when the error occurred + * errorColumn : column number when the error occurred + * errorAfter : prefix that was seen before this error occurred + * curchar : the offending character + * Note: You can customize the lexical error message by modifying this method. + */ + protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { + return("Lexical error at line " + + errorLine + ", column " + + errorColumn + ". Encountered: " + + (EOFSeen ? " " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + + "after : \"" + addEscapes(errorAfter) + "\""); + } - /** - * You can also modify the body of this method to customize your error messages. - * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not - * of end-users concern, so you can return something like : - * - * "Internal Error : Please file a bug report .... " - * - * from this method for such cases in the release version of your parser. - */ - public String getMessage() { - return super.getMessage(); - } + /** + * You can also modify the body of this method to customize your error messages. + * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not + * of end-users concern, so you can return something like : + * + * "Internal Error : Please file a bug report .... " + * + * from this method for such cases in the release version of your parser. + */ + public String getMessage() { + return super.getMessage(); + } - /* - * Constructors of various flavors follow. - */ + /* + * Constructors of various flavors follow. + */ - public TokenMgrError() { - } + /** No arg constructor. */ + public TokenMgrError() { + } - public TokenMgrError(String message, int reason) { - super(message); - errorCode = reason; - } + /** Constructor with message and reason. */ + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } - public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { - this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); - } + /** Full Constructor. */ + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); + } } +/* JavaCC - OriginalChecksum=c00a20fac1340b0977b68c98d352e42d (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/contentdisposition/parser/ContentDispositionParser.java b/src/org/apache/james/mime4j/field/contentdisposition/parser/ContentDispositionParser.java new file mode 100644 index 000000000..72a346135 --- /dev/null +++ b/src/org/apache/james/mime4j/field/contentdisposition/parser/ContentDispositionParser.java @@ -0,0 +1,292 @@ +/* Generated By:JavaCC: Do not edit this line. ContentDispositionParser.java */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.contentdisposition.parser; + +import java.util.List; +import java.util.ArrayList; + +public class ContentDispositionParser implements ContentDispositionParserConstants { + + private String dispositionType; + private List paramNames = new ArrayList(); + private List paramValues = new ArrayList(); + + public String getDispositionType() { + return dispositionType; + } + + public List getParamNames() { + return paramNames; + } + + public List getParamValues() { + return paramValues; + } + + public static void main(String args[]) throws ParseException { + while (true) { + try { + ContentDispositionParser parser = new ContentDispositionParser( + System.in); + parser.parseLine(); + } catch (Exception x) { + x.printStackTrace(); + return; + } + } + } + + final public void parseLine() throws ParseException { + parse(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 1: + jj_consume_token(1); + break; + default: + jj_la1[0] = jj_gen; + ; + } + jj_consume_token(2); + } + + final public void parseAll() throws ParseException { + parse(); + jj_consume_token(0); + } + + final public void parse() throws ParseException { + Token dispositionType; + dispositionType = jj_consume_token(ATOKEN); + this.dispositionType = dispositionType.image; + label_1: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 3: + ; + break; + default: + jj_la1[1] = jj_gen; + break label_1; + } + jj_consume_token(3); + parameter(); + } + } + + final public void parameter() throws ParseException { + Token attrib; + String val; + attrib = jj_consume_token(ATOKEN); + jj_consume_token(4); + val = value(); + paramNames.add(attrib.image); + paramValues.add(val); + } + + final public String value() throws ParseException { + Token t; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case ATOKEN: + t = jj_consume_token(ATOKEN); + break; + case DIGITS: + t = jj_consume_token(DIGITS); + break; + case QUOTEDSTRING: + t = jj_consume_token(QUOTEDSTRING); + break; + default: + jj_la1[2] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + {if (true) return t.image;} + throw new Error("Missing return statement in function"); + } + + /** Generated Token Manager. */ + public ContentDispositionParserTokenManager token_source; + SimpleCharStream jj_input_stream; + /** Current token. */ + public Token token; + /** Next token. */ + public Token jj_nt; + private int jj_ntk; + private int jj_gen; + final private int[] jj_la1 = new int[3]; + static private int[] jj_la1_0; + static { + jj_la1_init_0(); + } + private static void jj_la1_init_0() { + jj_la1_0 = new int[] {0x2,0x8,0x1c0000,}; + } + + /** Constructor with InputStream. */ + public ContentDispositionParser(java.io.InputStream stream) { + this(stream, null); + } + /** Constructor with InputStream and supplied encoding */ + public ContentDispositionParser(java.io.InputStream stream, String encoding) { + try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source = new ContentDispositionParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream stream) { + ReInit(stream, null); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream stream, String encoding) { + try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + /** Constructor. */ + public ContentDispositionParser(java.io.Reader stream) { + jj_input_stream = new SimpleCharStream(stream, 1, 1); + token_source = new ContentDispositionParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader stream) { + jj_input_stream.ReInit(stream, 1, 1); + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + /** Constructor with generated Token Manager. */ + public ContentDispositionParser(ContentDispositionParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(ContentDispositionParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + private Token jj_consume_token(int kind) throws ParseException { + Token oldToken; + if ((oldToken = token).next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + if (token.kind == kind) { + jj_gen++; + return token; + } + token = oldToken; + jj_kind = kind; + throw generateParseException(); + } + + +/** Get the next Token. */ + final public Token getNextToken() { + if (token.next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + jj_gen++; + return token; + } + +/** Get the specific Token. */ + final public Token getToken(int index) { + Token t = token; + for (int i = 0; i < index; i++) { + if (t.next != null) t = t.next; + else t = t.next = token_source.getNextToken(); + } + return t; + } + + private int jj_ntk() { + if ((jj_nt=token.next) == null) + return (jj_ntk = (token.next=token_source.getNextToken()).kind); + else + return (jj_ntk = jj_nt.kind); + } + + private java.util.List jj_expentries = new java.util.ArrayList(); + private int[] jj_expentry; + private int jj_kind = -1; + + /** Generate ParseException. */ + public ParseException generateParseException() { + jj_expentries.clear(); + boolean[] la1tokens = new boolean[23]; + if (jj_kind >= 0) { + la1tokens[jj_kind] = true; + jj_kind = -1; + } + for (int i = 0; i < 3; i++) { + if (jj_la1[i] == jj_gen) { + for (int j = 0; j < 32; j++) { + if ((jj_la1_0[i] & (1<", + "\"\\r\"", + "\"\\n\"", + "\";\"", + "\"=\"", + "", + "\"(\"", + "\")\"", + "", + "\"(\"", + "", + "", + "\"(\"", + "\")\"", + "", + "\"\\\"\"", + "", + "", + "\"\\\"\"", + "", + "", + "", + "", + }; + +} diff --git a/src/org/apache/james/mime4j/field/contentdisposition/parser/ContentDispositionParserTokenManager.java b/src/org/apache/james/mime4j/field/contentdisposition/parser/ContentDispositionParserTokenManager.java new file mode 100644 index 000000000..08dfd51dc --- /dev/null +++ b/src/org/apache/james/mime4j/field/contentdisposition/parser/ContentDispositionParserTokenManager.java @@ -0,0 +1,857 @@ +/* Generated By:JavaCC: Do not edit this line. ContentDispositionParserTokenManager.java */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.contentdisposition.parser; +import java.util.List; +import java.util.ArrayList; + +/** Token Manager. */ +public class ContentDispositionParserTokenManager implements ContentDispositionParserConstants +{ + // Keeps track of how many levels of comment nesting + // we've encountered. This is only used when the 2nd + // level is reached, for example ((this)), not (this). + // This is because the outermost level must be treated + // specially anyway, because the outermost ")" has a + // different token type than inner ")" instances. + static int commentNest; + + /** Debug output. */ + public java.io.PrintStream debugStream = System.out; + /** Set debug output. */ + public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; } +private final int jjStopStringLiteralDfa_0(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_0(int pos, long active0) +{ + return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1); +} +private int jjStopAtPos(int pos, int kind) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + return pos + 1; +} +private int jjMoveStringLiteralDfa0_0() +{ + switch(curChar) + { + case 10: + return jjStartNfaWithStates_0(0, 2, 2); + case 13: + return jjStartNfaWithStates_0(0, 1, 2); + case 34: + return jjStopAtPos(0, 15); + case 40: + return jjStopAtPos(0, 6); + case 59: + return jjStopAtPos(0, 3); + case 61: + return jjStopAtPos(0, 4); + default : + return jjMoveNfa_0(3, 0); + } +} +private int jjStartNfaWithStates_0(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_0(state, pos + 1); +} +static final long[] jjbitVec0 = { + 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL +}; +private int jjMoveNfa_0(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 3: + if ((0x3ff6cfafffffdffL & l) != 0L) + { + if (kind > 20) + kind = 20; + jjCheckNAdd(2); + } + else if ((0x100000200L & l) != 0L) + { + if (kind > 5) + kind = 5; + jjCheckNAdd(0); + } + if ((0x3ff000000000000L & l) != 0L) + { + if (kind > 19) + kind = 19; + jjCheckNAdd(1); + } + break; + case 0: + if ((0x100000200L & l) == 0L) + break; + kind = 5; + jjCheckNAdd(0); + break; + case 1: + if ((0x3ff000000000000L & l) == 0L) + break; + if (kind > 19) + kind = 19; + jjCheckNAdd(1); + break; + case 2: + if ((0x3ff6cfafffffdffL & l) == 0L) + break; + if (kind > 20) + kind = 20; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 3: + case 2: + if ((0xffffffffc7fffffeL & l) == 0L) + break; + kind = 20; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 3: + case 2: + if ((jjbitVec0[i2] & l2) == 0L) + break; + if (kind > 20) + kind = 20; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_1(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_1(int pos, long active0) +{ + return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_1() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 9); + case 41: + return jjStopAtPos(0, 7); + default : + return jjMoveNfa_1(0, 0); + } +} +private int jjMoveNfa_1(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 10) + kind = 10; + break; + case 1: + if (kind > 8) + kind = 8; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 10) + kind = 10; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 8) + kind = 8; + break; + case 2: + if (kind > 10) + kind = 10; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 10) + kind = 10; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 8) + kind = 8; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_3(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_3(int pos, long active0) +{ + return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_3() +{ + switch(curChar) + { + case 34: + return jjStopAtPos(0, 18); + default : + return jjMoveNfa_3(0, 0); + } +} +private int jjMoveNfa_3(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + case 2: + if ((0xfffffffbffffffffL & l) == 0L) + break; + if (kind > 17) + kind = 17; + jjCheckNAdd(2); + break; + case 1: + if (kind > 16) + kind = 16; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0xffffffffefffffffL & l) != 0L) + { + if (kind > 17) + kind = 17; + jjCheckNAdd(2); + } + else if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 16) + kind = 16; + break; + case 2: + if ((0xffffffffefffffffL & l) == 0L) + break; + if (kind > 17) + kind = 17; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + case 2: + if ((jjbitVec0[i2] & l2) == 0L) + break; + if (kind > 17) + kind = 17; + jjCheckNAdd(2); + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 16) + kind = 16; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_2(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_2(int pos, long active0) +{ + return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_2() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 12); + case 41: + return jjStopAtPos(0, 13); + default : + return jjMoveNfa_2(0, 0); + } +} +private int jjMoveNfa_2(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 14) + kind = 14; + break; + case 1: + if (kind > 11) + kind = 11; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 14) + kind = 14; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 11) + kind = 11; + break; + case 2: + if (kind > 14) + kind = 14; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 14) + kind = 14; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 11) + kind = 11; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +static final int[] jjnextStates = { +}; + +/** Token literal values. */ +public static final String[] jjstrLiteralImages = { +"", "\15", "\12", "\73", "\75", null, null, null, null, null, null, null, null, +null, null, null, null, null, null, null, null, null, null, }; + +/** Lexer state names. */ +public static final String[] lexStateNames = { + "DEFAULT", + "INCOMMENT", + "NESTED_COMMENT", + "INQUOTEDSTRING", +}; + +/** Lex State array. */ +public static final int[] jjnewLexState = { + -1, -1, -1, -1, -1, -1, 1, 0, -1, 2, -1, -1, -1, -1, -1, 3, -1, -1, 0, -1, -1, -1, -1, +}; +static final long[] jjtoToken = { + 0x1c001fL, +}; +static final long[] jjtoSkip = { + 0xa0L, +}; +static final long[] jjtoSpecial = { + 0x20L, +}; +static final long[] jjtoMore = { + 0x3ff40L, +}; +protected SimpleCharStream input_stream; +private final int[] jjrounds = new int[3]; +private final int[] jjstateSet = new int[6]; +private final StringBuilder jjimage = new StringBuilder(); +private StringBuilder image = jjimage; +private int jjimageLen; +private int lengthOfMatch; +protected char curChar; +/** Constructor. */ +public ContentDispositionParserTokenManager(SimpleCharStream stream){ + if (SimpleCharStream.staticFlag) + throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer."); + input_stream = stream; +} + +/** Constructor. */ +public ContentDispositionParserTokenManager(SimpleCharStream stream, int lexState){ + this(stream); + SwitchTo(lexState); +} + +/** Reinitialise parser. */ +public void ReInit(SimpleCharStream stream) +{ + jjmatchedPos = jjnewStateCnt = 0; + curLexState = defaultLexState; + input_stream = stream; + ReInitRounds(); +} +private void ReInitRounds() +{ + int i; + jjround = 0x80000001; + for (i = 3; i-- > 0;) + jjrounds[i] = 0x80000000; +} + +/** Reinitialise parser. */ +public void ReInit(SimpleCharStream stream, int lexState) +{ + ReInit(stream); + SwitchTo(lexState); +} + +/** Switch to specified lex state. */ +public void SwitchTo(int lexState) +{ + if (lexState >= 4 || lexState < 0) + throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE); + else + curLexState = lexState; +} + +protected Token jjFillToken() +{ + final Token t; + final String curTokenImage; + final int beginLine; + final int endLine; + final int beginColumn; + final int endColumn; + String im = jjstrLiteralImages[jjmatchedKind]; + curTokenImage = (im == null) ? input_stream.GetImage() : im; + beginLine = input_stream.getBeginLine(); + beginColumn = input_stream.getBeginColumn(); + endLine = input_stream.getEndLine(); + endColumn = input_stream.getEndColumn(); + t = Token.newToken(jjmatchedKind, curTokenImage); + + t.beginLine = beginLine; + t.endLine = endLine; + t.beginColumn = beginColumn; + t.endColumn = endColumn; + + return t; +} + +int curLexState = 0; +int defaultLexState = 0; +int jjnewStateCnt; +int jjround; +int jjmatchedPos; +int jjmatchedKind; + +/** Get the next Token. */ +public Token getNextToken() +{ + Token specialToken = null; + Token matchedToken; + int curPos = 0; + + EOFLoop : + for (;;) + { + try + { + curChar = input_stream.BeginToken(); + } + catch(java.io.IOException e) + { + jjmatchedKind = 0; + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + return matchedToken; + } + image = jjimage; + image.setLength(0); + jjimageLen = 0; + + for (;;) + { + switch(curLexState) + { + case 0: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_0(); + break; + case 1: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_1(); + break; + case 2: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_2(); + break; + case 3: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_3(); + break; + } + if (jjmatchedKind != 0x7fffffff) + { + if (jjmatchedPos + 1 < curPos) + input_stream.backup(curPos - jjmatchedPos - 1); + if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + TokenLexicalActions(matchedToken); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + return matchedToken; + } + else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + if ((jjtoSpecial[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + if (specialToken == null) + specialToken = matchedToken; + else + { + matchedToken.specialToken = specialToken; + specialToken = (specialToken.next = matchedToken); + } + } + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + continue EOFLoop; + } + MoreLexicalActions(); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + curPos = 0; + jjmatchedKind = 0x7fffffff; + try { + curChar = input_stream.readChar(); + continue; + } + catch (java.io.IOException e1) { } + } + int error_line = input_stream.getEndLine(); + int error_column = input_stream.getEndColumn(); + String error_after = null; + boolean EOFSeen = false; + try { input_stream.readChar(); input_stream.backup(1); } + catch (java.io.IOException e1) { + EOFSeen = true; + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + if (curChar == '\n' || curChar == '\r') { + error_line++; + error_column = 0; + } + else + error_column++; + } + if (!EOFSeen) { + input_stream.backup(1); + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + } + throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR); + } + } +} + +void MoreLexicalActions() +{ + jjimageLen += (lengthOfMatch = jjmatchedPos + 1); + switch(jjmatchedKind) + { + case 8 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 9 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + commentNest = 1; + break; + case 11 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 12 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + ++commentNest; + break; + case 13 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); + break; + case 15 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 1); + break; + case 16 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + default : + break; + } +} +void TokenLexicalActions(Token matchedToken) +{ + switch(jjmatchedKind) + { + case 18 : + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + matchedToken.image = image.substring(0, image.length() - 1); + break; + default : + break; + } +} +private void jjCheckNAdd(int state) +{ + if (jjrounds[state] != jjround) + { + jjstateSet[jjnewStateCnt++] = state; + jjrounds[state] = jjround; + } +} +private void jjAddStates(int start, int end) +{ + do { + jjstateSet[jjnewStateCnt++] = jjnextStates[start]; + } while (start++ != end); +} +private void jjCheckNAddTwoStates(int state1, int state2) +{ + jjCheckNAdd(state1); + jjCheckNAdd(state2); +} + +} diff --git a/src/org/apache/james/mime4j/field/contentdisposition/parser/ParseException.java b/src/org/apache/james/mime4j/field/contentdisposition/parser/ParseException.java new file mode 100644 index 000000000..ca3278dec --- /dev/null +++ b/src/org/apache/james/mime4j/field/contentdisposition/parser/ParseException.java @@ -0,0 +1,220 @@ +/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.contentdisposition.parser; + +/** + * This exception is thrown when parse errors are encountered. + * You can explicitly create objects of this exception type by + * calling the method generateParseException in the generated + * parser. + * + * Changes for Mime4J: + * extends org.apache.james.mime4j.field.ParseException + * added serialVersionUID + * added constructor ParseException(Throwable) + * default detail message is "Cannot parse field" + */ +public class ParseException extends org.apache.james.mime4j.dom.field.ParseException { + + private static final long serialVersionUID = 1L; + + /** + * This constructor is used by the method "generateParseException" + * in the generated parser. Calling this constructor generates + * a new object of this type with the fields "currentToken", + * "expectedTokenSequences", and "tokenImage" set. The boolean + * flag "specialConstructor" is also set to true to indicate that + * this constructor was used to create this object. + * This constructor calls its super class with the empty string + * to force the "toString" method of parent class "Throwable" to + * print the error message in the form: + * ParseException: + */ + public ParseException(Token currentTokenVal, + int[][] expectedTokenSequencesVal, + String[] tokenImageVal + ) + { + super(""); + specialConstructor = true; + currentToken = currentTokenVal; + expectedTokenSequences = expectedTokenSequencesVal; + tokenImage = tokenImageVal; + } + + /** + * The following constructors are for use by you for whatever + * purpose you can think of. Constructing the exception in this + * manner makes the exception behave in the normal way - i.e., as + * documented in the class "Throwable". The fields "errorToken", + * "expectedTokenSequences", and "tokenImage" do not contain + * relevant information. The JavaCC generated code does not use + * these constructors. + */ + + public ParseException() { + super("Cannot parse field"); + specialConstructor = false; + } + + public ParseException(Throwable cause) { + super(cause); + specialConstructor = false; + } + + public ParseException(String message) { + super(message); + specialConstructor = false; + } + + /** + * This variable determines which constructor was used to create + * this object and thereby affects the semantics of the + * "getMessage" method (see below). + */ + protected boolean specialConstructor; + + /** + * This is the last token that has been consumed successfully. If + * this object has been created due to a parse error, the token + * followng this token will (therefore) be the first error token. + */ + public Token currentToken; + + /** + * Each entry in this array is an array of integers. Each array + * of integers represents a sequence of tokens (by their ordinal + * values) that is expected at this point of the parse. + */ + public int[][] expectedTokenSequences; + + /** + * This is a reference to the "tokenImage" array of the generated + * parser within which the parse error occurred. This array is + * defined in the generated ...Constants interface. + */ + public String[] tokenImage; + + /** + * This method has the standard behavior when this object has been + * created using the standard constructors. Otherwise, it uses + * "currentToken" and "expectedTokenSequences" to generate a parse + * error message and returns it. If this object has been created + * due to a parse error, and you do not catch it (it gets thrown + * from the parser), then this method is called during the printing + * of the final stack trace, and hence the correct error message + * gets displayed. + */ + public String getMessage() { + if (!specialConstructor) { + return super.getMessage(); + } + StringBuffer expected = new StringBuffer(); + int maxSize = 0; + for (int i = 0; i < expectedTokenSequences.length; i++) { + if (maxSize < expectedTokenSequences[i].length) { + maxSize = expectedTokenSequences[i].length; + } + for (int j = 0; j < expectedTokenSequences[i].length; j++) { + expected.append(tokenImage[expectedTokenSequences[i][j]]).append(" "); + } + if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) { + expected.append("..."); + } + expected.append(eol).append(" "); + } + String retval = "Encountered \""; + Token tok = currentToken.next; + for (int i = 0; i < maxSize; i++) { + if (i != 0) retval += " "; + if (tok.kind == 0) { + retval += tokenImage[0]; + break; + } + retval += add_escapes(tok.image); + tok = tok.next; + } + retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn; + retval += "." + eol; + if (expectedTokenSequences.length == 1) { + retval += "Was expecting:" + eol + " "; + } else { + retval += "Was expecting one of:" + eol + " "; + } + retval += expected.toString(); + return retval; + } + + /** + * The end of line string for this machine. + */ + protected String eol = System.getProperty("line.separator", "\n"); + + /** + * Used to convert raw characters to their escaped version + * when these raw version cannot be used as part of an ASCII + * string literal. + */ + protected String add_escapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + +} diff --git a/src/org/apache/james/mime4j/field/contentdisposition/parser/SimpleCharStream.java b/src/org/apache/james/mime4j/field/contentdisposition/parser/SimpleCharStream.java new file mode 100644 index 000000000..92d2a4d5a --- /dev/null +++ b/src/org/apache/james/mime4j/field/contentdisposition/parser/SimpleCharStream.java @@ -0,0 +1,489 @@ +/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 5.0 */ +/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.contentdisposition.parser; + +/** + * An implementation of interface CharStream, where the stream is assumed to + * contain only ASCII characters (without unicode processing). + */ + +public class SimpleCharStream +{ +/** Whether parser is static. */ + public static final boolean staticFlag = false; + int bufsize; + int available; + int tokenBegin; +/** Position in buffer. */ + public int bufpos = -1; + protected int bufline[]; + protected int bufcolumn[]; + + protected int column = 0; + protected int line = 1; + + protected boolean prevCharIsCR = false; + protected boolean prevCharIsLF = false; + + protected java.io.Reader inputStream; + + protected char[] buffer; + protected int maxNextCharInd = 0; + protected int inBuf = 0; + protected int tabSize = 8; + + protected void setTabSize(int i) { tabSize = i; } + protected int getTabSize(int i) { return tabSize; } + + + protected void ExpandBuff(boolean wrapAround) + { + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; + + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } + + + bufsize += 2048; + available = bufsize; + tokenBegin = 0; + } + + protected void FillBuff() throws java.io.IOException + { + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) + { + bufpos = maxNextCharInd = 0; + available = tokenBegin; + } + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; + else + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } + + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } + } + +/** Start. */ + public char BeginToken() throws java.io.IOException + { + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; + + return c; + } + + protected void UpdateLineColumn(char c) + { + column++; + + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else + line += (column = 1); + } + + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (tabSize - (column % tabSize)); + break; + default : + break; + } + + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + +/** Read a character. */ + public char readChar() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + if (++bufpos == bufsize) + bufpos = 0; + + return buffer[bufpos]; + } + + if (++bufpos >= maxNextCharInd) + FillBuff(); + + char c = buffer[bufpos]; + + UpdateLineColumn(c); + return c; + } + + @Deprecated + /** + * @deprecated + * @see #getEndColumn + */ + + public int getColumn() { + return bufcolumn[bufpos]; + } + + @Deprecated + /** + * @deprecated + * @see #getEndLine + */ + + public int getLine() { + return bufline[bufpos]; + } + + /** Get token end column number. */ + public int getEndColumn() { + return bufcolumn[bufpos]; + } + + /** Get token end line number. */ + public int getEndLine() { + return bufline[bufpos]; + } + + /** Get token beginning column number. */ + public int getBeginColumn() { + return bufcolumn[tokenBegin]; + } + + /** Get token beginning line number. */ + public int getBeginLine() { + return bufline[tokenBegin]; + } + +/** Backup a number of characters. */ + public void backup(int amount) { + + inBuf += amount; + if ((bufpos -= amount) < 0) + bufpos += bufsize; + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream) + { + this(dstream, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + if (buffer == null || buffersize != buffer.length) + { + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + prevCharIsLF = prevCharIsCR = false; + tokenBegin = inBuf = maxNextCharInd = 0; + bufpos = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream) + { + ReInit(dstream, 1, 1, 4096); + } + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, 1, 1, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream) + { + this(dstream, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream) + { + ReInit(dstream, 1, 1, 4096); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, startline, startcolumn, 4096); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + /** Get token literal value. */ + public String GetImage() + { + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); + } + + /** Get the suffix. */ + public char[] GetSuffix(int len) + { + char[] ret = new char[len]; + + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } + + return ret; + } + + /** Reset buffer when finished. */ + public void Done() + { + buffer = null; + bufline = null; + bufcolumn = null; + } + + /** + * Method to adjust line and column numbers for the start of a token. + */ + public void adjustBeginLineColumn(int newLine, int newCol) + { + int start = tokenBegin; + int len; + + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } + + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; + + while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } + + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; + + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } + + line = bufline[j]; + column = bufcolumn[j]; + } + +} +/* JavaCC - OriginalChecksum=3a0a96dd40a434f5b242b9ed9202faa4 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/contentdisposition/parser/Token.java b/src/org/apache/james/mime4j/field/contentdisposition/parser/Token.java new file mode 100644 index 000000000..26ff91167 --- /dev/null +++ b/src/org/apache/james/mime4j/field/contentdisposition/parser/Token.java @@ -0,0 +1,149 @@ +/* Generated By:JavaCC: Do not edit this line. Token.java Version 5.0 */ +/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.contentdisposition.parser; + +/** + * Describes the input token stream. + */ + +public class Token implements java.io.Serializable { + + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; + + /** + * An integer that describes the kind of this token. This numbering + * system is determined by JavaCCParser, and a table of these numbers is + * stored in the file ...Constants.java. + */ + public int kind; + + /** The line number of the first character of this Token. */ + public int beginLine; + /** The column number of the first character of this Token. */ + public int beginColumn; + /** The line number of the last character of this Token. */ + public int endLine; + /** The column number of the last character of this Token. */ + public int endColumn; + + /** + * The string image of the token. + */ + public String image; + + /** + * A reference to the next regular (non-special) token from the input + * stream. If this is the last token from the input stream, or if the + * token manager has not read tokens beyond this one, this field is + * set to null. This is true only if this token is also a regular + * token. Otherwise, see below for a description of the contents of + * this field. + */ + public Token next; + + /** + * This field is used to access special tokens that occur prior to this + * token, but after the immediately preceding regular (non-special) token. + * If there are no such special tokens, this field is set to null. + * When there are more than one such special token, this field refers + * to the last of these special tokens, which in turn refers to the next + * previous special token through its specialToken field, and so on + * until the first special token (whose specialToken field is null). + * The next fields of special tokens refer to other special tokens that + * immediately follow it (without an intervening regular token). If there + * is no such token, this field is null. + */ + public Token specialToken; + + /** + * An optional attribute value of the Token. + * Tokens which are not used as syntactic sugar will often contain + * meaningful values that will be used later on by the compiler or + * interpreter. This attribute value is often different from the image. + * Any subclass of Token that actually wants to return a non-null value can + * override this method as appropriate. + */ + public Object getValue() { + return null; + } + + /** + * No-argument constructor + */ + public Token() {} + + /** + * Constructs a new token for the specified Image. + */ + public Token(int kind) + { + this(kind, null); + } + + /** + * Constructs a new token for the specified Image and Kind. + */ + public Token(int kind, String image) + { + this.kind = kind; + this.image = image; + } + + /** + * Returns the image. + */ + public String toString() + { + return image; + } + + /** + * Returns a new Token object, by default. However, if you want, you + * can create and return subclass objects based on the value of ofKind. + * Simply add the cases to the switch for all those special cases. + * For example, if you have a subclass of Token called IDToken that + * you want to create if ofKind is ID, simply add something like : + * + * case MyParserConstants.ID : return new IDToken(ofKind, image); + * + * to the following switch statement. Then you can cast matchedToken + * variable to the appropriate type and use sit in your lexical actions. + */ + public static Token newToken(int ofKind, String image) + { + switch(ofKind) + { + default : return new Token(ofKind, image); + } + } + + public static Token newToken(int ofKind) + { + return newToken(ofKind, null); + } + +} +/* JavaCC - OriginalChecksum=b1ee9aaf7cbe47fcd350a0df848362b5 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/contentdisposition/parser/TokenMgrError.java b/src/org/apache/james/mime4j/field/contentdisposition/parser/TokenMgrError.java new file mode 100644 index 000000000..a2712abe3 --- /dev/null +++ b/src/org/apache/james/mime4j/field/contentdisposition/parser/TokenMgrError.java @@ -0,0 +1,165 @@ +/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */ +/* JavaCCOptions: */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.contentdisposition.parser; + +/** Token Manager Error. */ +public class TokenMgrError extends Error +{ + + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; + + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ + + /** + * Lexical error occurred. + */ + static final int LEXICAL_ERROR = 0; + + /** + * An attempt was made to create a second instance of a static token manager. + */ + static final int STATIC_LEXER_ERROR = 1; + + /** + * Tried to change to an invalid lexical state. + */ + static final int INVALID_LEXICAL_STATE = 2; + + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + static final int LOOP_DETECTED = 3; + + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + int errorCode; + + /** + * Replaces unprintable characters by their escaped (or unicode escaped) + * equivalents in the given string + */ + protected static final String addEscapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * Parameters : + * EOFSeen : indicates if EOF caused the lexical error + * curLexState : lexical state in which this error occurred + * errorLine : line number when the error occurred + * errorColumn : column number when the error occurred + * errorAfter : prefix that was seen before this error occurred + * curchar : the offending character + * Note: You can customize the lexical error message by modifying this method. + */ + protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { + return("Lexical error at line " + + errorLine + ", column " + + errorColumn + ". Encountered: " + + (EOFSeen ? " " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + + "after : \"" + addEscapes(errorAfter) + "\""); + } + + /** + * You can also modify the body of this method to customize your error messages. + * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not + * of end-users concern, so you can return something like : + * + * "Internal Error : Please file a bug report .... " + * + * from this method for such cases in the release version of your parser. + */ + public String getMessage() { + return super.getMessage(); + } + + /* + * Constructors of various flavors follow. + */ + + /** No arg constructor. */ + public TokenMgrError() { + } + + /** Constructor with message and reason. */ + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } + + /** Full Constructor. */ + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); + } +} +/* JavaCC - OriginalChecksum=9fa2b884a567f978be9291126c2a21c0 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParser.java b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParser.java index 64e829a51..32a9b65b6 100644 --- a/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParser.java +++ b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParser.java @@ -1,34 +1,38 @@ /* Generated By:JavaCC: Do not edit this line. ContentTypeParser.java */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.contenttype.parser; +import java.util.List; import java.util.ArrayList; public class ContentTypeParser implements ContentTypeParserConstants { private String type; private String subtype; - private ArrayList paramNames = new ArrayList(); - private ArrayList paramValues = new ArrayList(); + private List paramNames = new ArrayList(); + private List paramValues = new ArrayList(); public String getType() { return type; } public String getSubType() { return subtype; } - public ArrayList getParamNames() { return paramNames; } - public ArrayList getParamValues() { return paramValues; } + public List getParamNames() { return paramNames; } + public List getParamValues() { return paramValues; } public static void main(String args[]) throws ParseException { while (true) { @@ -99,6 +103,9 @@ public class ContentTypeParser implements ContentTypeParserConstants { case ATOKEN: t = jj_consume_token(ATOKEN); break; + case DIGITS: + t = jj_consume_token(DIGITS); + break; case QUOTEDSTRING: t = jj_consume_token(QUOTEDSTRING); break; @@ -111,23 +118,29 @@ public class ContentTypeParser implements ContentTypeParserConstants { throw new Error("Missing return statement in function"); } + /** Generated Token Manager. */ public ContentTypeParserTokenManager token_source; SimpleCharStream jj_input_stream; - public Token token, jj_nt; + /** Current token. */ + public Token token; + /** Next token. */ + public Token jj_nt; private int jj_ntk; private int jj_gen; final private int[] jj_la1 = new int[3]; static private int[] jj_la1_0; static { - jj_la1_0(); + jj_la1_init_0(); } - private static void jj_la1_0() { - jj_la1_0 = new int[] {0x2,0x10,0x280000,}; + private static void jj_la1_init_0() { + jj_la1_0 = new int[] {0x2,0x10,0x380000,}; } + /** Constructor with InputStream. */ public ContentTypeParser(java.io.InputStream stream) { this(stream, null); } + /** Constructor with InputStream and supplied encoding */ public ContentTypeParser(java.io.InputStream stream, String encoding) { try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } token_source = new ContentTypeParserTokenManager(jj_input_stream); @@ -137,9 +150,11 @@ public class ContentTypeParser implements ContentTypeParserConstants { for (int i = 0; i < 3; i++) jj_la1[i] = -1; } + /** Reinitialise. */ public void ReInit(java.io.InputStream stream) { ReInit(stream, null); } + /** Reinitialise. */ public void ReInit(java.io.InputStream stream, String encoding) { try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } token_source.ReInit(jj_input_stream); @@ -149,6 +164,7 @@ public class ContentTypeParser implements ContentTypeParserConstants { for (int i = 0; i < 3; i++) jj_la1[i] = -1; } + /** Constructor. */ public ContentTypeParser(java.io.Reader stream) { jj_input_stream = new SimpleCharStream(stream, 1, 1); token_source = new ContentTypeParserTokenManager(jj_input_stream); @@ -158,6 +174,7 @@ public class ContentTypeParser implements ContentTypeParserConstants { for (int i = 0; i < 3; i++) jj_la1[i] = -1; } + /** Reinitialise. */ public void ReInit(java.io.Reader stream) { jj_input_stream.ReInit(stream, 1, 1); token_source.ReInit(jj_input_stream); @@ -167,6 +184,7 @@ public class ContentTypeParser implements ContentTypeParserConstants { for (int i = 0; i < 3; i++) jj_la1[i] = -1; } + /** Constructor with generated Token Manager. */ public ContentTypeParser(ContentTypeParserTokenManager tm) { token_source = tm; token = new Token(); @@ -175,6 +193,7 @@ public class ContentTypeParser implements ContentTypeParserConstants { for (int i = 0; i < 3; i++) jj_la1[i] = -1; } + /** Reinitialise. */ public void ReInit(ContentTypeParserTokenManager tm) { token_source = tm; token = new Token(); @@ -183,7 +202,7 @@ public class ContentTypeParser implements ContentTypeParserConstants { for (int i = 0; i < 3; i++) jj_la1[i] = -1; } - final private Token jj_consume_token(int kind) throws ParseException { + private Token jj_consume_token(int kind) throws ParseException { Token oldToken; if ((oldToken = token).next != null) token = token.next; else token = token.next = token_source.getNextToken(); @@ -197,6 +216,8 @@ public class ContentTypeParser implements ContentTypeParserConstants { throw generateParseException(); } + +/** Get the next Token. */ final public Token getNextToken() { if (token.next != null) token = token.next; else token = token.next = token_source.getNextToken(); @@ -205,6 +226,7 @@ public class ContentTypeParser implements ContentTypeParserConstants { return token; } +/** Get the specific Token. */ final public Token getToken(int index) { Token t = token; for (int i = 0; i < index; i++) { @@ -214,23 +236,21 @@ public class ContentTypeParser implements ContentTypeParserConstants { return t; } - final private int jj_ntk() { + private int jj_ntk() { if ((jj_nt=token.next) == null) return (jj_ntk = (token.next=token_source.getNextToken()).kind); else return (jj_ntk = jj_nt.kind); } - private java.util.Vector jj_expentries = new java.util.Vector(); + private java.util.List jj_expentries = new java.util.ArrayList(); private int[] jj_expentry; private int jj_kind = -1; + /** Generate ParseException. */ public ParseException generateParseException() { - jj_expentries.removeAllElements(); + jj_expentries.clear(); boolean[] la1tokens = new boolean[24]; - for (int i = 0; i < 24; i++) { - la1tokens[i] = false; - } if (jj_kind >= 0) { la1tokens[jj_kind] = true; jj_kind = -1; @@ -248,19 +268,21 @@ public class ContentTypeParser implements ContentTypeParserConstants { if (la1tokens[i]) { jj_expentry = new int[1]; jj_expentry[0] = i; - jj_expentries.addElement(jj_expentry); + jj_expentries.add(jj_expentry); } } int[][] exptokseq = new int[jj_expentries.size()][]; for (int i = 0; i < jj_expentries.size(); i++) { - exptokseq[i] = (int[])jj_expentries.elementAt(i); + exptokseq[i] = jj_expentries.get(i); } return new ParseException(token, exptokseq, tokenImage); } + /** Enable tracing. */ final public void enable_tracing() { } + /** Disable tracing. */ final public void disable_tracing() { } diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserConstants.java b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserConstants.java index 8a763287d..e4d06265d 100644 --- a/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserConstants.java +++ b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserConstants.java @@ -1,37 +1,58 @@ /* Generated By:JavaCC: Do not edit this line. ContentTypeParserConstants.java */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.contenttype.parser; + +/** + * Token literal values and constants. + * Generated by org.javacc.parser.OtherFilesGen#start() + */ public interface ContentTypeParserConstants { + /** End of File. */ int EOF = 0; + /** RegularExpression Id. */ int WS = 6; + /** RegularExpression Id. */ int COMMENT = 8; + /** RegularExpression Id. */ int QUOTEDSTRING = 19; + /** RegularExpression Id. */ int DIGITS = 20; + /** RegularExpression Id. */ int ATOKEN = 21; + /** RegularExpression Id. */ int QUOTEDPAIR = 22; + /** RegularExpression Id. */ int ANY = 23; + /** Lexical state. */ int DEFAULT = 0; + /** Lexical state. */ int INCOMMENT = 1; + /** Lexical state. */ int NESTED_COMMENT = 2; + /** Lexical state. */ int INQUOTEDSTRING = 3; + /** Literal token values. */ String[] tokenImage = { "", "\"\\r\"", diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserTokenManager.java b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserTokenManager.java index 05d940db5..175d660c4 100644 --- a/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserTokenManager.java +++ b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserTokenManager.java @@ -1,22 +1,27 @@ /* Generated By:JavaCC: Do not edit this line. ContentTypeParserTokenManager.java */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.contenttype.parser; +import java.util.List; import java.util.ArrayList; +/** Token Manager. */ public class ContentTypeParserTokenManager implements ContentTypeParserConstants { // Keeps track of how many levels of comment nesting @@ -26,7 +31,10 @@ public class ContentTypeParserTokenManager implements ContentTypeParserConstants // specially anyway, because the outermost ")" has a // different token type than inner ")" instances. static int commentNest; + + /** Debug output. */ public java.io.PrintStream debugStream = System.out; + /** Set debug output. */ public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; } private final int jjStopStringLiteralDfa_0(int pos, long active0) { @@ -40,21 +48,13 @@ private final int jjStartNfa_0(int pos, long active0) { return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1); } -private final int jjStopAtPos(int pos, int kind) +private int jjStopAtPos(int pos, int kind) { jjmatchedKind = kind; jjmatchedPos = pos; return pos + 1; } -private final int jjStartNfaWithStates_0(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_0(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_0() +private int jjMoveStringLiteralDfa0_0() { switch(curChar) { @@ -76,47 +76,24 @@ private final int jjMoveStringLiteralDfa0_0() return jjMoveNfa_0(3, 0); } } -private final void jjCheckNAdd(int state) +private int jjStartNfaWithStates_0(int pos, int kind, int state) { - if (jjrounds[state] != jjround) - { - jjstateSet[jjnewStateCnt++] = state; - jjrounds[state] = jjround; - } -} -private final void jjAddStates(int start, int end) -{ - do { - jjstateSet[jjnewStateCnt++] = jjnextStates[start]; - } while (start++ != end); -} -private final void jjCheckNAddTwoStates(int state1, int state2) -{ - jjCheckNAdd(state1); - jjCheckNAdd(state2); -} -private final void jjCheckNAddStates(int start, int end) -{ - do { - jjCheckNAdd(jjnextStates[start]); - } while (start++ != end); -} -private final void jjCheckNAddStates(int start) -{ - jjCheckNAdd(jjnextStates[start]); - jjCheckNAdd(jjnextStates[start + 1]); + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_0(state, pos + 1); } static final long[] jjbitVec0 = { 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL }; -private final int jjMoveNfa_0(int startState, int curPos) +private int jjMoveNfa_0(int startState, int curPos) { - int[] nextStates; int startsAt = 0; jjnewStateCnt = 3; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -124,7 +101,7 @@ private final int jjMoveNfa_0(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -175,7 +152,7 @@ private final int jjMoveNfa_0(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -194,7 +171,7 @@ private final int jjMoveNfa_0(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -235,15 +212,7 @@ private final int jjStartNfa_1(int pos, long active0) { return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1); } -private final int jjStartNfaWithStates_1(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_1(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_1() +private int jjMoveStringLiteralDfa0_1() { switch(curChar) { @@ -255,14 +224,13 @@ private final int jjMoveStringLiteralDfa0_1() return jjMoveNfa_1(0, 0); } } -private final int jjMoveNfa_1(int startState, int curPos) +private int jjMoveNfa_1(int startState, int curPos) { - int[] nextStates; int startsAt = 0; jjnewStateCnt = 3; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -270,7 +238,7 @@ private final int jjMoveNfa_1(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -289,7 +257,7 @@ private final int jjMoveNfa_1(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -315,7 +283,7 @@ private final int jjMoveNfa_1(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -356,15 +324,7 @@ private final int jjStartNfa_3(int pos, long active0) { return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0), pos + 1); } -private final int jjStartNfaWithStates_3(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_3(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_3() +private int jjMoveStringLiteralDfa0_3() { switch(curChar) { @@ -374,14 +334,13 @@ private final int jjMoveStringLiteralDfa0_3() return jjMoveNfa_3(0, 0); } } -private final int jjMoveNfa_3(int startState, int curPos) +private int jjMoveNfa_3(int startState, int curPos) { - int[] nextStates; int startsAt = 0; jjnewStateCnt = 3; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -389,7 +348,7 @@ private final int jjMoveNfa_3(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -412,7 +371,7 @@ private final int jjMoveNfa_3(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -445,7 +404,7 @@ private final int jjMoveNfa_3(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -490,15 +449,7 @@ private final int jjStartNfa_2(int pos, long active0) { return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1); } -private final int jjStartNfaWithStates_2(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_2(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_2() +private int jjMoveStringLiteralDfa0_2() { switch(curChar) { @@ -510,14 +461,13 @@ private final int jjMoveStringLiteralDfa0_2() return jjMoveNfa_2(0, 0); } } -private final int jjMoveNfa_2(int startState, int curPos) +private int jjMoveNfa_2(int startState, int curPos) { - int[] nextStates; int startsAt = 0; jjnewStateCnt = 3; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -525,7 +475,7 @@ private final int jjMoveNfa_2(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -544,7 +494,7 @@ private final int jjMoveNfa_2(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -570,7 +520,7 @@ private final int jjMoveNfa_2(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -601,15 +551,21 @@ private final int jjMoveNfa_2(int startState, int curPos) } static final int[] jjnextStates = { }; + +/** Token literal values. */ public static final String[] jjstrLiteralImages = { "", "\15", "\12", "\57", "\73", "\75", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, }; + +/** Lexer state names. */ public static final String[] lexStateNames = { - "DEFAULT", - "INCOMMENT", - "NESTED_COMMENT", - "INQUOTEDSTRING", + "DEFAULT", + "INCOMMENT", + "NESTED_COMMENT", + "INQUOTEDSTRING", }; + +/** Lex State array. */ public static final int[] jjnewLexState = { -1, -1, -1, -1, -1, -1, -1, 1, 0, -1, 2, -1, -1, -1, -1, -1, 3, -1, -1, 0, -1, -1, -1, -1, }; @@ -628,19 +584,25 @@ static final long[] jjtoMore = { protected SimpleCharStream input_stream; private final int[] jjrounds = new int[3]; private final int[] jjstateSet = new int[6]; -StringBuffer image; -int jjimageLen; -int lengthOfMatch; +private final StringBuilder jjimage = new StringBuilder(); +private StringBuilder image = jjimage; +private int jjimageLen; +private int lengthOfMatch; protected char curChar; +/** Constructor. */ public ContentTypeParserTokenManager(SimpleCharStream stream){ if (SimpleCharStream.staticFlag) throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer."); input_stream = stream; } + +/** Constructor. */ public ContentTypeParserTokenManager(SimpleCharStream stream, int lexState){ this(stream); SwitchTo(lexState); } + +/** Reinitialise parser. */ public void ReInit(SimpleCharStream stream) { jjmatchedPos = jjnewStateCnt = 0; @@ -648,18 +610,22 @@ public void ReInit(SimpleCharStream stream) input_stream = stream; ReInitRounds(); } -private final void ReInitRounds() +private void ReInitRounds() { int i; jjround = 0x80000001; for (i = 3; i-- > 0;) jjrounds[i] = 0x80000000; } + +/** Reinitialise parser. */ public void ReInit(SimpleCharStream stream, int lexState) { ReInit(stream); SwitchTo(lexState); } + +/** Switch to specified lex state. */ public void SwitchTo(int lexState) { if (lexState >= 4 || lexState < 0) @@ -670,14 +636,25 @@ public void SwitchTo(int lexState) protected Token jjFillToken() { - Token t = Token.newToken(jjmatchedKind); - t.kind = jjmatchedKind; + final Token t; + final String curTokenImage; + final int beginLine; + final int endLine; + final int beginColumn; + final int endColumn; String im = jjstrLiteralImages[jjmatchedKind]; - t.image = (im == null) ? input_stream.GetImage() : im; - t.beginLine = input_stream.getBeginLine(); - t.beginColumn = input_stream.getBeginColumn(); - t.endLine = input_stream.getEndLine(); - t.endColumn = input_stream.getEndColumn(); + curTokenImage = (im == null) ? input_stream.GetImage() : im; + beginLine = input_stream.getBeginLine(); + beginColumn = input_stream.getBeginColumn(); + endLine = input_stream.getEndLine(); + endColumn = input_stream.getEndColumn(); + t = Token.newToken(jjmatchedKind, curTokenImage); + + t.beginLine = beginLine; + t.endLine = endLine; + t.beginColumn = beginColumn; + t.endColumn = endColumn; + return t; } @@ -688,28 +665,29 @@ int jjround; int jjmatchedPos; int jjmatchedKind; +/** Get the next Token. */ public Token getNextToken() { - int kind; Token specialToken = null; Token matchedToken; int curPos = 0; EOFLoop : for (;;) - { - try - { + { + try + { curChar = input_stream.BeginToken(); - } + } catch(java.io.IOException e) - { + { jjmatchedKind = 0; matchedToken = jjFillToken(); matchedToken.specialToken = specialToken; return matchedToken; } - image = null; + image = jjimage; + image.setLength(0); jjimageLen = 0; for (;;) @@ -808,55 +786,41 @@ void MoreLexicalActions() switch(jjmatchedKind) { case 9 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; image.deleteCharAt(image.length() - 2); break; case 10 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; commentNest = 1; break; case 12 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; image.deleteCharAt(image.length() - 2); break; case 13 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; ++commentNest; break; case 14 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); break; case 16 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; image.deleteCharAt(image.length() - 1); break; case 17 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; image.deleteCharAt(image.length() - 2); break; - default : + default : break; } } @@ -865,13 +829,31 @@ void TokenLexicalActions(Token matchedToken) switch(jjmatchedKind) { case 19 : - if (image == null) - image = new StringBuffer(); - image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); matchedToken.image = image.substring(0, image.length() - 1); break; - default : + default : break; } } +private void jjCheckNAdd(int state) +{ + if (jjrounds[state] != jjround) + { + jjstateSet[jjnewStateCnt++] = state; + jjrounds[state] = jjround; + } +} +private void jjAddStates(int start, int end) +{ + do { + jjstateSet[jjnewStateCnt++] = jjnextStates[start]; + } while (start++ != end); +} +private void jjCheckNAddTwoStates(int state1, int state2) +{ + jjCheckNAdd(state1); + jjCheckNAdd(state2); +} + } diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/ParseException.java b/src/org/apache/james/mime4j/field/contenttype/parser/ParseException.java index 2c5dc8275..3a4c67028 100644 --- a/src/org/apache/james/mime4j/field/contenttype/parser/ParseException.java +++ b/src/org/apache/james/mime4j/field/contenttype/parser/ParseException.java @@ -1,19 +1,22 @@ /* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.contenttype.parser; /** @@ -22,10 +25,15 @@ package org.apache.james.mime4j.field.contenttype.parser; * calling the method generateParseException in the generated * parser. * - * You can modify this class to customize your error reporting - * mechanisms so long as you retain the public fields. + * Changes for Mime4J: + * extends org.apache.james.mime4j.field.ParseException + * added serialVersionUID + * added constructor ParseException(Throwable) + * default detail message is "Cannot parse field" */ -public class ParseException extends Exception { +public class ParseException extends org.apache.james.mime4j.dom.field.ParseException { + + private static final long serialVersionUID = 1L; /** * This constructor is used by the method "generateParseException" @@ -62,7 +70,12 @@ public class ParseException extends Exception { */ public ParseException() { - super(); + super("Cannot parse field"); + specialConstructor = false; + } + + public ParseException(Throwable cause) { + super(cause); specialConstructor = false; } diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/SimpleCharStream.java b/src/org/apache/james/mime4j/field/contenttype/parser/SimpleCharStream.java index c139e4f38..dd43e5ffe 100644 --- a/src/org/apache/james/mime4j/field/contenttype/parser/SimpleCharStream.java +++ b/src/org/apache/james/mime4j/field/contenttype/parser/SimpleCharStream.java @@ -1,19 +1,23 @@ -/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 4.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 5.0 */ +/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.contenttype.parser; /** @@ -23,10 +27,12 @@ package org.apache.james.mime4j.field.contenttype.parser; public class SimpleCharStream { +/** Whether parser is static. */ public static final boolean staticFlag = false; int bufsize; int available; int tokenBegin; +/** Position in buffer. */ public int bufpos = -1; protected int bufline[]; protected int bufcolumn[]; @@ -50,210 +56,218 @@ public class SimpleCharStream protected void ExpandBuff(boolean wrapAround) { - char[] newbuffer = new char[bufsize + 2048]; - int newbufline[] = new int[bufsize + 2048]; - int newbufcolumn[] = new int[bufsize + 2048]; + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; - try - { - if (wrapAround) - { - System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); - System.arraycopy(buffer, 0, newbuffer, - bufsize - tokenBegin, bufpos); - buffer = newbuffer; + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos); + buffer = newbuffer; - System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); - System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); - bufline = newbufline; + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; - System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); - System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); - bufcolumn = newbufcolumn; + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; - maxNextCharInd = (bufpos += (bufsize - tokenBegin)); - } - else - { - System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); - buffer = newbuffer; + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; - System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); - bufline = newbufline; + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; - System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); - bufcolumn = newbufcolumn; + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; - maxNextCharInd = (bufpos -= tokenBegin); - } - } - catch (Throwable t) - { - throw new Error(t.getMessage()); - } + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } - bufsize += 2048; - available = bufsize; - tokenBegin = 0; + bufsize += 2048; + available = bufsize; + tokenBegin = 0; } protected void FillBuff() throws java.io.IOException { - if (maxNextCharInd == available) - { - if (available == bufsize) + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) { - if (tokenBegin > 2048) - { - bufpos = maxNextCharInd = 0; - available = tokenBegin; - } - else if (tokenBegin < 0) - bufpos = maxNextCharInd = 0; - else - ExpandBuff(false); + bufpos = maxNextCharInd = 0; + available = tokenBegin; } - else if (available > tokenBegin) - available = bufsize; - else if ((tokenBegin - available) < 2048) - ExpandBuff(true); + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; else - available = tokenBegin; - } + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } - int i; - try { - if ((i = inputStream.read(buffer, maxNextCharInd, - available - maxNextCharInd)) == -1) - { - inputStream.close(); - throw new java.io.IOException(); - } - else - maxNextCharInd += i; - return; - } - catch(java.io.IOException e) { - --bufpos; - backup(0); - if (tokenBegin == -1) - tokenBegin = bufpos; - throw e; - } + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } } +/** Start. */ public char BeginToken() throws java.io.IOException { - tokenBegin = -1; - char c = readChar(); - tokenBegin = bufpos; + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; - return c; + return c; } protected void UpdateLineColumn(char c) { - column++; + column++; - if (prevCharIsLF) - { - prevCharIsLF = false; + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else line += (column = 1); - } - else if (prevCharIsCR) - { - prevCharIsCR = false; - if (c == '\n') - { - prevCharIsLF = true; - } - else - line += (column = 1); - } + } - switch (c) - { - case '\r' : - prevCharIsCR = true; - break; - case '\n' : - prevCharIsLF = true; - break; - case '\t' : - column--; - column += (tabSize - (column % tabSize)); - break; - default : - break; - } + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (tabSize - (column % tabSize)); + break; + default : + break; + } - bufline[bufpos] = line; - bufcolumn[bufpos] = column; + bufline[bufpos] = line; + bufcolumn[bufpos] = column; } +/** Read a character. */ public char readChar() throws java.io.IOException { - if (inBuf > 0) - { - --inBuf; + if (inBuf > 0) + { + --inBuf; - if (++bufpos == bufsize) - bufpos = 0; + if (++bufpos == bufsize) + bufpos = 0; - return buffer[bufpos]; - } + return buffer[bufpos]; + } - if (++bufpos >= maxNextCharInd) - FillBuff(); + if (++bufpos >= maxNextCharInd) + FillBuff(); - char c = buffer[bufpos]; + char c = buffer[bufpos]; - UpdateLineColumn(c); - return (c); + UpdateLineColumn(c); + return c; } + @Deprecated /** - * @deprecated + * @deprecated * @see #getEndColumn */ public int getColumn() { - return bufcolumn[bufpos]; + return bufcolumn[bufpos]; } + @Deprecated /** - * @deprecated + * @deprecated * @see #getEndLine */ public int getLine() { - return bufline[bufpos]; + return bufline[bufpos]; } + /** Get token end column number. */ public int getEndColumn() { - return bufcolumn[bufpos]; + return bufcolumn[bufpos]; } + /** Get token end line number. */ public int getEndLine() { return bufline[bufpos]; } + /** Get token beginning column number. */ public int getBeginColumn() { - return bufcolumn[tokenBegin]; + return bufcolumn[tokenBegin]; } + /** Get token beginning line number. */ public int getBeginLine() { - return bufline[tokenBegin]; + return bufline[tokenBegin]; } +/** Backup a number of characters. */ public void backup(int amount) { inBuf += amount; if ((bufpos -= amount) < 0) - bufpos += bufsize; + bufpos += bufsize; } + /** Constructor. */ public SimpleCharStream(java.io.Reader dstream, int startline, int startcolumn, int buffersize) { @@ -267,16 +281,20 @@ public class SimpleCharStream bufcolumn = new int[buffersize]; } + /** Constructor. */ public SimpleCharStream(java.io.Reader dstream, int startline, int startcolumn) { - this(dstream, startline, startcolumn, 4096); + this(dstream, startline, startcolumn, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.Reader dstream) { - this(dstream, 1, 1, 4096); + this(dstream, 1, 1, 4096); } + + /** Reinitialise. */ public void ReInit(java.io.Reader dstream, int startline, int startcolumn, int buffersize) { @@ -296,111 +314,128 @@ public class SimpleCharStream bufpos = -1; } + /** Reinitialise. */ public void ReInit(java.io.Reader dstream, int startline, int startcolumn) { - ReInit(dstream, startline, startcolumn, 4096); + ReInit(dstream, startline, startcolumn, 4096); } + /** Reinitialise. */ public void ReInit(java.io.Reader dstream) { - ReInit(dstream, 1, 1, 4096); + ReInit(dstream, 1, 1, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException { - this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) { - this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, int startcolumn) throws java.io.UnsupportedEncodingException { - this(dstream, encoding, startline, startcolumn, 4096); + this(dstream, encoding, startline, startcolumn, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, int startline, int startcolumn) { - this(dstream, startline, startcolumn, 4096); + this(dstream, startline, startcolumn, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException { - this(dstream, encoding, 1, 1, 4096); + this(dstream, encoding, 1, 1, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream) { - this(dstream, 1, 1, 4096); + this(dstream, 1, 1, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding, int startline, int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException { - ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) { - ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException { - ReInit(dstream, encoding, 1, 1, 4096); + ReInit(dstream, encoding, 1, 1, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream) { - ReInit(dstream, 1, 1, 4096); + ReInit(dstream, 1, 1, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding, int startline, int startcolumn) throws java.io.UnsupportedEncodingException { - ReInit(dstream, encoding, startline, startcolumn, 4096); + ReInit(dstream, encoding, startline, startcolumn, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, int startline, int startcolumn) { - ReInit(dstream, startline, startcolumn, 4096); + ReInit(dstream, startline, startcolumn, 4096); } + /** Get token literal value. */ public String GetImage() { - if (bufpos >= tokenBegin) - return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); - else - return new String(buffer, tokenBegin, bufsize - tokenBegin) + - new String(buffer, 0, bufpos + 1); + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); } + /** Get the suffix. */ public char[] GetSuffix(int len) { - char[] ret = new char[len]; + char[] ret = new char[len]; - if ((bufpos + 1) >= len) - System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); - else - { - System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, - len - bufpos - 1); - System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); - } + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } - return ret; + return ret; } + /** Reset buffer when finished. */ public void Done() { - buffer = null; - bufline = null; - bufcolumn = null; + buffer = null; + bufline = null; + bufcolumn = null; } /** @@ -408,47 +443,47 @@ public class SimpleCharStream */ public void adjustBeginLineColumn(int newLine, int newCol) { - int start = tokenBegin; - int len; + int start = tokenBegin; + int len; - if (bufpos >= tokenBegin) - { - len = bufpos - tokenBegin + inBuf + 1; - } - else - { - len = bufsize - tokenBegin + bufpos + 1 + inBuf; - } + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } - int i = 0, j = 0, k = 0; - int nextColDiff = 0, columnDiff = 0; + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; - while (i < len && - bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) - { - bufline[j] = newLine; - nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; - bufcolumn[j] = newCol + columnDiff; - columnDiff = nextColDiff; - i++; - } + while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } - if (i < len) - { - bufline[j] = newLine++; - bufcolumn[j] = newCol + columnDiff; + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; - while (i++ < len) - { - if (bufline[j = start % bufsize] != bufline[++start % bufsize]) - bufline[j] = newLine++; - else - bufline[j] = newLine; - } - } + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } - line = bufline[j]; - column = bufcolumn[j]; + line = bufline[j]; + column = bufcolumn[j]; } } +/* JavaCC - OriginalChecksum=6c6aad23fd4c0ff4139b17758b7e8610 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/Token.java b/src/org/apache/james/mime4j/field/contenttype/parser/Token.java index 5bef6cf02..aa509517f 100644 --- a/src/org/apache/james/mime4j/field/contenttype/parser/Token.java +++ b/src/org/apache/james/mime4j/field/contenttype/parser/Token.java @@ -1,26 +1,37 @@ -/* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* Generated By:JavaCC: Do not edit this line. Token.java Version 5.0 */ +/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.contenttype.parser; /** * Describes the input token stream. */ -public class Token { +public class Token implements java.io.Serializable { + + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; /** * An integer that describes the kind of this token. This numbering @@ -29,12 +40,14 @@ public class Token { */ public int kind; - /** - * beginLine and beginColumn describe the position of the first character - * of this token; endLine and endColumn describe the position of the - * last character of this token. - */ - public int beginLine, beginColumn, endLine, endColumn; + /** The line number of the first character of this Token. */ + public int beginLine; + /** The column number of the first character of this Token. */ + public int beginColumn; + /** The line number of the last character of this Token. */ + public int endLine; + /** The column number of the last character of this Token. */ + public int endColumn; /** * The string image of the token. @@ -65,12 +78,46 @@ public class Token { */ public Token specialToken; + /** + * An optional attribute value of the Token. + * Tokens which are not used as syntactic sugar will often contain + * meaningful values that will be used later on by the compiler or + * interpreter. This attribute value is often different from the image. + * Any subclass of Token that actually wants to return a non-null value can + * override this method as appropriate. + */ + public Object getValue() { + return null; + } + + /** + * No-argument constructor + */ + public Token() {} + + /** + * Constructs a new token for the specified Image. + */ + public Token(int kind) + { + this(kind, null); + } + + /** + * Constructs a new token for the specified Image and Kind. + */ + public Token(int kind, String image) + { + this.kind = kind; + this.image = image; + } + /** * Returns the image. */ public String toString() { - return image; + return image; } /** @@ -78,19 +125,25 @@ public class Token { * can create and return subclass objects based on the value of ofKind. * Simply add the cases to the switch for all those special cases. * For example, if you have a subclass of Token called IDToken that - * you want to create if ofKind is ID, simlpy add something like : + * you want to create if ofKind is ID, simply add something like : * - * case MyParserConstants.ID : return new IDToken(); + * case MyParserConstants.ID : return new IDToken(ofKind, image); * * to the following switch statement. Then you can cast matchedToken - * variable to the appropriate type and use it in your lexical actions. + * variable to the appropriate type and use sit in your lexical actions. */ - public static final Token newToken(int ofKind) + public static Token newToken(int ofKind, String image) { - switch(ofKind) - { - default : return new Token(); - } + switch(ofKind) + { + default : return new Token(ofKind, image); + } + } + + public static Token newToken(int ofKind) + { + return newToken(ofKind, null); } } +/* JavaCC - OriginalChecksum=3d41123d7d22080e3366761725b1c1fe (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/TokenMgrError.java b/src/org/apache/james/mime4j/field/contenttype/parser/TokenMgrError.java index 4a490efac..5849559d1 100644 --- a/src/org/apache/james/mime4j/field/contenttype/parser/TokenMgrError.java +++ b/src/org/apache/james/mime4j/field/contenttype/parser/TokenMgrError.java @@ -1,148 +1,165 @@ -/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 3.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */ +/* JavaCCOptions: */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.contenttype.parser; +/** Token Manager Error. */ public class TokenMgrError extends Error { - /* - * Ordinals for various reasons why an Error of this type can be thrown. - */ - /** - * Lexical error occured. - */ - static final int LEXICAL_ERROR = 0; + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; - /** - * An attempt wass made to create a second instance of a static token manager. - */ - static final int STATIC_LEXER_ERROR = 1; + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ - /** - * Tried to change to an invalid lexical state. - */ - static final int INVALID_LEXICAL_STATE = 2; + /** + * Lexical error occurred. + */ + static final int LEXICAL_ERROR = 0; - /** - * Detected (and bailed out of) an infinite loop in the token manager. - */ - static final int LOOP_DETECTED = 3; + /** + * An attempt was made to create a second instance of a static token manager. + */ + static final int STATIC_LEXER_ERROR = 1; - /** - * Indicates the reason why the exception is thrown. It will have - * one of the above 4 values. - */ - int errorCode; + /** + * Tried to change to an invalid lexical state. + */ + static final int INVALID_LEXICAL_STATE = 2; - /** - * Replaces unprintable characters by their espaced (or unicode escaped) - * equivalents in the given string - */ - protected static final String addEscapes(String str) { - StringBuffer retval = new StringBuffer(); - char ch; - for (int i = 0; i < str.length(); i++) { - switch (str.charAt(i)) - { - case 0 : - continue; - case '\b': - retval.append("\\b"); - continue; - case '\t': - retval.append("\\t"); - continue; - case '\n': - retval.append("\\n"); - continue; - case '\f': - retval.append("\\f"); - continue; - case '\r': - retval.append("\\r"); - continue; - case '\"': - retval.append("\\\""); - continue; - case '\'': - retval.append("\\\'"); - continue; - case '\\': - retval.append("\\\\"); - continue; - default: - if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { - String s = "0000" + Integer.toString(ch, 16); - retval.append("\\u" + s.substring(s.length() - 4, s.length())); - } else { - retval.append(ch); - } - continue; - } + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + static final int LOOP_DETECTED = 3; + + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + int errorCode; + + /** + * Replaces unprintable characters by their escaped (or unicode escaped) + * equivalents in the given string + */ + protected static final String addEscapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; } - return retval.toString(); - } + } + return retval.toString(); + } - /** - * Returns a detailed message for the Error when it is thrown by the - * token manager to indicate a lexical error. - * Parameters : - * EOFSeen : indicates if EOF caused the lexicl error - * curLexState : lexical state in which this error occured - * errorLine : line number when the error occured - * errorColumn : column number when the error occured - * errorAfter : prefix that was seen before this error occured - * curchar : the offending character - * Note: You can customize the lexical error message by modifying this method. - */ - protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { - return("Lexical error at line " + - errorLine + ", column " + - errorColumn + ". Encountered: " + - (EOFSeen ? " " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + - "after : \"" + addEscapes(errorAfter) + "\""); - } + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * Parameters : + * EOFSeen : indicates if EOF caused the lexical error + * curLexState : lexical state in which this error occurred + * errorLine : line number when the error occurred + * errorColumn : column number when the error occurred + * errorAfter : prefix that was seen before this error occurred + * curchar : the offending character + * Note: You can customize the lexical error message by modifying this method. + */ + protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { + return("Lexical error at line " + + errorLine + ", column " + + errorColumn + ". Encountered: " + + (EOFSeen ? " " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + + "after : \"" + addEscapes(errorAfter) + "\""); + } - /** - * You can also modify the body of this method to customize your error messages. - * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not - * of end-users concern, so you can return something like : - * - * "Internal Error : Please file a bug report .... " - * - * from this method for such cases in the release version of your parser. - */ - public String getMessage() { - return super.getMessage(); - } + /** + * You can also modify the body of this method to customize your error messages. + * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not + * of end-users concern, so you can return something like : + * + * "Internal Error : Please file a bug report .... " + * + * from this method for such cases in the release version of your parser. + */ + public String getMessage() { + return super.getMessage(); + } - /* - * Constructors of various flavors follow. - */ + /* + * Constructors of various flavors follow. + */ - public TokenMgrError() { - } + /** No arg constructor. */ + public TokenMgrError() { + } - public TokenMgrError(String message, int reason) { - super(message); - errorCode = reason; - } + /** Constructor with message and reason. */ + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } - public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { - this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); - } + /** Full Constructor. */ + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); + } } +/* JavaCC - OriginalChecksum=75c4cf7db7d0ac3344c6bae5c180f189 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParser.java b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParser.java index 94b517208..a9af27ac5 100644 --- a/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParser.java +++ b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParser.java @@ -1,24 +1,25 @@ /* Generated By:JavaCC: Do not edit this line. DateTimeParser.java */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.datetime.parser; -import org.apache.james.mime4j.field.datetime.DateTime; - -import java.util.Calendar; +import org.apache.james.mime4j.dom.datetime.DateTime; public class DateTimeParser implements DateTimeParserConstants { private static final boolean ignoreMilitaryZoneOffset = true; @@ -406,28 +407,34 @@ public class DateTimeParser implements DateTimeParserConstants { throw new Error("Missing return statement in function"); } + /** Generated Token Manager. */ public DateTimeParserTokenManager token_source; SimpleCharStream jj_input_stream; - public Token token, jj_nt; + /** Current token. */ + public Token token; + /** Next token. */ + public Token jj_nt; private int jj_ntk; private int jj_gen; final private int[] jj_la1 = new int[7]; static private int[] jj_la1_0; static private int[] jj_la1_1; static { - jj_la1_0(); - jj_la1_1(); + jj_la1_init_0(); + jj_la1_init_1(); } - private static void jj_la1_0() { + private static void jj_la1_init_0() { jj_la1_0 = new int[] {0x2,0x7f0,0x7f0,0x7ff800,0x800000,0xff000000,0xfe000000,}; } - private static void jj_la1_1() { + private static void jj_la1_init_1() { jj_la1_1 = new int[] {0x0,0x0,0x0,0x0,0x0,0xf,0xf,}; } + /** Constructor with InputStream. */ public DateTimeParser(java.io.InputStream stream) { this(stream, null); } + /** Constructor with InputStream and supplied encoding */ public DateTimeParser(java.io.InputStream stream, String encoding) { try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } token_source = new DateTimeParserTokenManager(jj_input_stream); @@ -437,9 +444,11 @@ public class DateTimeParser implements DateTimeParserConstants { for (int i = 0; i < 7; i++) jj_la1[i] = -1; } + /** Reinitialise. */ public void ReInit(java.io.InputStream stream) { ReInit(stream, null); } + /** Reinitialise. */ public void ReInit(java.io.InputStream stream, String encoding) { try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } token_source.ReInit(jj_input_stream); @@ -449,6 +458,7 @@ public class DateTimeParser implements DateTimeParserConstants { for (int i = 0; i < 7; i++) jj_la1[i] = -1; } + /** Constructor. */ public DateTimeParser(java.io.Reader stream) { jj_input_stream = new SimpleCharStream(stream, 1, 1); token_source = new DateTimeParserTokenManager(jj_input_stream); @@ -458,6 +468,7 @@ public class DateTimeParser implements DateTimeParserConstants { for (int i = 0; i < 7; i++) jj_la1[i] = -1; } + /** Reinitialise. */ public void ReInit(java.io.Reader stream) { jj_input_stream.ReInit(stream, 1, 1); token_source.ReInit(jj_input_stream); @@ -467,6 +478,7 @@ public class DateTimeParser implements DateTimeParserConstants { for (int i = 0; i < 7; i++) jj_la1[i] = -1; } + /** Constructor with generated Token Manager. */ public DateTimeParser(DateTimeParserTokenManager tm) { token_source = tm; token = new Token(); @@ -475,6 +487,7 @@ public class DateTimeParser implements DateTimeParserConstants { for (int i = 0; i < 7; i++) jj_la1[i] = -1; } + /** Reinitialise. */ public void ReInit(DateTimeParserTokenManager tm) { token_source = tm; token = new Token(); @@ -483,7 +496,7 @@ public class DateTimeParser implements DateTimeParserConstants { for (int i = 0; i < 7; i++) jj_la1[i] = -1; } - final private Token jj_consume_token(int kind) throws ParseException { + private Token jj_consume_token(int kind) throws ParseException { Token oldToken; if ((oldToken = token).next != null) token = token.next; else token = token.next = token_source.getNextToken(); @@ -497,6 +510,8 @@ public class DateTimeParser implements DateTimeParserConstants { throw generateParseException(); } + +/** Get the next Token. */ final public Token getNextToken() { if (token.next != null) token = token.next; else token = token.next = token_source.getNextToken(); @@ -505,6 +520,7 @@ public class DateTimeParser implements DateTimeParserConstants { return token; } +/** Get the specific Token. */ final public Token getToken(int index) { Token t = token; for (int i = 0; i < index; i++) { @@ -514,23 +530,21 @@ public class DateTimeParser implements DateTimeParserConstants { return t; } - final private int jj_ntk() { + private int jj_ntk() { if ((jj_nt=token.next) == null) return (jj_ntk = (token.next=token_source.getNextToken()).kind); else return (jj_ntk = jj_nt.kind); } - private java.util.Vector jj_expentries = new java.util.Vector(); + private java.util.List jj_expentries = new java.util.ArrayList(); private int[] jj_expentry; private int jj_kind = -1; + /** Generate ParseException. */ public ParseException generateParseException() { - jj_expentries.removeAllElements(); + jj_expentries.clear(); boolean[] la1tokens = new boolean[49]; - for (int i = 0; i < 49; i++) { - la1tokens[i] = false; - } if (jj_kind >= 0) { la1tokens[jj_kind] = true; jj_kind = -1; @@ -551,19 +565,21 @@ public class DateTimeParser implements DateTimeParserConstants { if (la1tokens[i]) { jj_expentry = new int[1]; jj_expentry[0] = i; - jj_expentries.addElement(jj_expentry); + jj_expentries.add(jj_expentry); } } int[][] exptokseq = new int[jj_expentries.size()][]; for (int i = 0; i < jj_expentries.size(); i++) { - exptokseq[i] = (int[])jj_expentries.elementAt(i); + exptokseq[i] = jj_expentries.get(i); } return new ParseException(token, exptokseq, tokenImage); } + /** Enable tracing. */ final public void enable_tracing() { } + /** Disable tracing. */ final public void disable_tracing() { } diff --git a/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserConstants.java b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserConstants.java index 17389d816..638f5bbbd 100644 --- a/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserConstants.java +++ b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserConstants.java @@ -1,36 +1,56 @@ /* Generated By:JavaCC: Do not edit this line. DateTimeParserConstants.java */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.datetime.parser; + +/** + * Token literal values and constants. + * Generated by org.javacc.parser.OtherFilesGen#start() + */ public interface DateTimeParserConstants { + /** End of File. */ int EOF = 0; + /** RegularExpression Id. */ int OFFSETDIR = 24; + /** RegularExpression Id. */ int MILITARY_ZONE = 35; + /** RegularExpression Id. */ int WS = 36; + /** RegularExpression Id. */ int COMMENT = 38; + /** RegularExpression Id. */ int DIGITS = 46; + /** RegularExpression Id. */ int QUOTEDPAIR = 47; + /** RegularExpression Id. */ int ANY = 48; + /** Lexical state. */ int DEFAULT = 0; + /** Lexical state. */ int INCOMMENT = 1; + /** Lexical state. */ int NESTED_COMMENT = 2; + /** Literal token values. */ String[] tokenImage = { "", "\"\\r\"", diff --git a/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserTokenManager.java b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserTokenManager.java index e75998cf2..858b0509d 100644 --- a/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserTokenManager.java +++ b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserTokenManager.java @@ -1,23 +1,26 @@ /* Generated By:JavaCC: Do not edit this line. DateTimeParserTokenManager.java */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.datetime.parser; -import org.apache.james.mime4j.field.datetime.DateTime; -import java.util.Calendar; +import org.apache.james.mime4j.dom.datetime.DateTime; +/** Token Manager. */ public class DateTimeParserTokenManager implements DateTimeParserConstants { // Keeps track of how many levels of comment nesting @@ -27,7 +30,10 @@ public class DateTimeParserTokenManager implements DateTimeParserConstants // specially anyway, because the outermost ")" has a // different token type than inner ")" instances. static int commentNest; + + /** Debug output. */ public java.io.PrintStream debugStream = System.out; + /** Set debug output. */ public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; } private final int jjStopStringLiteralDfa_0(int pos, long active0) { @@ -59,21 +65,13 @@ private final int jjStartNfa_0(int pos, long active0) { return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1); } -private final int jjStopAtPos(int pos, int kind) +private int jjStopAtPos(int pos, int kind) { jjmatchedKind = kind; jjmatchedPos = pos; return pos + 1; } -private final int jjStartNfaWithStates_0(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_0(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_0() +private int jjMoveStringLiteralDfa0_0() { switch(curChar) { @@ -121,7 +119,7 @@ private final int jjMoveStringLiteralDfa0_0() return jjMoveNfa_0(0, 0); } } -private final int jjMoveStringLiteralDfa1_0(long active0) +private int jjMoveStringLiteralDfa1_0(long active0) { try { curChar = input_stream.readChar(); } catch(java.io.IOException e) { @@ -161,10 +159,10 @@ private final int jjMoveStringLiteralDfa1_0(long active0) } return jjStartNfa_0(0, active0); } -private final int jjMoveStringLiteralDfa2_0(long old0, long active0) +private int jjMoveStringLiteralDfa2_0(long old0, long active0) { if (((active0 &= old0)) == 0L) - return jjStartNfa_0(0, old0); + return jjStartNfa_0(0, old0); try { curChar = input_stream.readChar(); } catch(java.io.IOException e) { jjStopStringLiteralDfa_0(1, active0); @@ -263,44 +261,13 @@ private final int jjMoveStringLiteralDfa2_0(long old0, long active0) } return jjStartNfa_0(1, active0); } -private final void jjCheckNAdd(int state) +private int jjMoveNfa_0(int startState, int curPos) { - if (jjrounds[state] != jjround) - { - jjstateSet[jjnewStateCnt++] = state; - jjrounds[state] = jjround; - } -} -private final void jjAddStates(int start, int end) -{ - do { - jjstateSet[jjnewStateCnt++] = jjnextStates[start]; - } while (start++ != end); -} -private final void jjCheckNAddTwoStates(int state1, int state2) -{ - jjCheckNAdd(state1); - jjCheckNAdd(state2); -} -private final void jjCheckNAddStates(int start, int end) -{ - do { - jjCheckNAdd(jjnextStates[start]); - } while (start++ != end); -} -private final void jjCheckNAddStates(int start) -{ - jjCheckNAdd(jjnextStates[start]); - jjCheckNAdd(jjnextStates[start + 1]); -} -private final int jjMoveNfa_0(int startState, int curPos) -{ - int[] nextStates; int startsAt = 0; jjnewStateCnt = 4; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -308,7 +275,7 @@ private final int jjMoveNfa_0(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -350,7 +317,7 @@ private final int jjMoveNfa_0(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -366,7 +333,7 @@ private final int jjMoveNfa_0(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -399,15 +366,7 @@ private final int jjStartNfa_1(int pos, long active0) { return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1); } -private final int jjStartNfaWithStates_1(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_1(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_1() +private int jjMoveStringLiteralDfa0_1() { switch(curChar) { @@ -422,14 +381,13 @@ private final int jjMoveStringLiteralDfa0_1() static final long[] jjbitVec0 = { 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL }; -private final int jjMoveNfa_1(int startState, int curPos) +private int jjMoveNfa_1(int startState, int curPos) { - int[] nextStates; int startsAt = 0; jjnewStateCnt = 3; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -437,7 +395,7 @@ private final int jjMoveNfa_1(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -456,7 +414,7 @@ private final int jjMoveNfa_1(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -482,7 +440,7 @@ private final int jjMoveNfa_1(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -523,15 +481,7 @@ private final int jjStartNfa_2(int pos, long active0) { return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1); } -private final int jjStartNfaWithStates_2(int pos, int kind, int state) -{ - jjmatchedKind = kind; - jjmatchedPos = pos; - try { curChar = input_stream.readChar(); } - catch(java.io.IOException e) { return pos + 1; } - return jjMoveNfa_2(state, pos + 1); -} -private final int jjMoveStringLiteralDfa0_2() +private int jjMoveStringLiteralDfa0_2() { switch(curChar) { @@ -543,14 +493,13 @@ private final int jjMoveStringLiteralDfa0_2() return jjMoveNfa_2(0, 0); } } -private final int jjMoveNfa_2(int startState, int curPos) +private int jjMoveNfa_2(int startState, int curPos) { - int[] nextStates; int startsAt = 0; jjnewStateCnt = 3; int i = 1; jjstateSet[0] = startState; - int j, kind = 0x7fffffff; + int kind = 0x7fffffff; for (;;) { if (++jjround == 0x7fffffff) @@ -558,7 +507,7 @@ private final int jjMoveNfa_2(int startState, int curPos) if (curChar < 64) { long l = 1L << curChar; - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -577,7 +526,7 @@ private final int jjMoveNfa_2(int startState, int curPos) else if (curChar < 128) { long l = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -603,7 +552,7 @@ private final int jjMoveNfa_2(int startState, int curPos) { int i2 = (curChar & 0xff) >> 6; long l2 = 1L << (curChar & 077); - MatchLoop: do + do { switch(jjstateSet[--i]) { @@ -634,6 +583,8 @@ private final int jjMoveNfa_2(int startState, int curPos) } static final int[] jjnextStates = { }; + +/** Token literal values. */ public static final String[] jjstrLiteralImages = { "", "\15", "\12", "\54", "\115\157\156", "\124\165\145", "\127\145\144", "\124\150\165", "\106\162\151", "\123\141\164", "\123\165\156", "\112\141\156", @@ -643,11 +594,15 @@ public static final String[] jjstrLiteralImages = { "\103\123\124", "\103\104\124", "\115\123\124", "\115\104\124", "\120\123\124", "\120\104\124", null, null, null, null, null, null, null, null, null, null, null, null, null, null, }; + +/** Lexer state names. */ public static final String[] lexStateNames = { - "DEFAULT", - "INCOMMENT", - "NESTED_COMMENT", + "DEFAULT", + "INCOMMENT", + "NESTED_COMMENT", }; + +/** Lex State array. */ public static final int[] jjnewLexState = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 0, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, @@ -667,19 +622,25 @@ static final long[] jjtoMore = { protected SimpleCharStream input_stream; private final int[] jjrounds = new int[4]; private final int[] jjstateSet = new int[8]; -StringBuffer image; -int jjimageLen; -int lengthOfMatch; +private final StringBuilder jjimage = new StringBuilder(); +private StringBuilder image = jjimage; +private int jjimageLen; +private int lengthOfMatch; protected char curChar; +/** Constructor. */ public DateTimeParserTokenManager(SimpleCharStream stream){ if (SimpleCharStream.staticFlag) throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer."); input_stream = stream; } + +/** Constructor. */ public DateTimeParserTokenManager(SimpleCharStream stream, int lexState){ this(stream); SwitchTo(lexState); } + +/** Reinitialise parser. */ public void ReInit(SimpleCharStream stream) { jjmatchedPos = jjnewStateCnt = 0; @@ -687,18 +648,22 @@ public void ReInit(SimpleCharStream stream) input_stream = stream; ReInitRounds(); } -private final void ReInitRounds() +private void ReInitRounds() { int i; jjround = 0x80000001; for (i = 4; i-- > 0;) jjrounds[i] = 0x80000000; } + +/** Reinitialise parser. */ public void ReInit(SimpleCharStream stream, int lexState) { ReInit(stream); SwitchTo(lexState); } + +/** Switch to specified lex state. */ public void SwitchTo(int lexState) { if (lexState >= 3 || lexState < 0) @@ -709,14 +674,25 @@ public void SwitchTo(int lexState) protected Token jjFillToken() { - Token t = Token.newToken(jjmatchedKind); - t.kind = jjmatchedKind; + final Token t; + final String curTokenImage; + final int beginLine; + final int endLine; + final int beginColumn; + final int endColumn; String im = jjstrLiteralImages[jjmatchedKind]; - t.image = (im == null) ? input_stream.GetImage() : im; - t.beginLine = input_stream.getBeginLine(); - t.beginColumn = input_stream.getBeginColumn(); - t.endLine = input_stream.getEndLine(); - t.endColumn = input_stream.getEndColumn(); + curTokenImage = (im == null) ? input_stream.GetImage() : im; + beginLine = input_stream.getBeginLine(); + beginColumn = input_stream.getBeginColumn(); + endLine = input_stream.getEndLine(); + endColumn = input_stream.getEndColumn(); + t = Token.newToken(jjmatchedKind, curTokenImage); + + t.beginLine = beginLine; + t.endLine = endLine; + t.beginColumn = beginColumn; + t.endColumn = endColumn; + return t; } @@ -727,28 +703,29 @@ int jjround; int jjmatchedPos; int jjmatchedKind; +/** Get the next Token. */ public Token getNextToken() { - int kind; Token specialToken = null; Token matchedToken; int curPos = 0; EOFLoop : for (;;) - { - try - { + { + try + { curChar = input_stream.BeginToken(); - } + } catch(java.io.IOException e) - { + { jjmatchedKind = 0; matchedToken = jjFillToken(); matchedToken.specialToken = specialToken; return matchedToken; } - image = null; + image = jjimage; + image.setLength(0); jjimageLen = 0; for (;;) @@ -841,42 +818,52 @@ void MoreLexicalActions() switch(jjmatchedKind) { case 39 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; image.deleteCharAt(image.length() - 2); break; case 40 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; commentNest = 1; break; case 42 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; image.deleteCharAt(image.length() - 2); break; case 43 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; ++commentNest; break; case 44 : - if (image == null) - image = new StringBuffer(); image.append(input_stream.GetSuffix(jjimageLen)); jjimageLen = 0; --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); break; - default : + default : break; } } +private void jjCheckNAdd(int state) +{ + if (jjrounds[state] != jjround) + { + jjstateSet[jjnewStateCnt++] = state; + jjrounds[state] = jjround; + } +} +private void jjAddStates(int start, int end) +{ + do { + jjstateSet[jjnewStateCnt++] = jjnextStates[start]; + } while (start++ != end); +} +private void jjCheckNAddTwoStates(int state1, int state2) +{ + jjCheckNAdd(state1); + jjCheckNAdd(state2); +} + } diff --git a/src/org/apache/james/mime4j/field/datetime/parser/ParseException.java b/src/org/apache/james/mime4j/field/datetime/parser/ParseException.java index 418699107..ee78a79a3 100644 --- a/src/org/apache/james/mime4j/field/datetime/parser/ParseException.java +++ b/src/org/apache/james/mime4j/field/datetime/parser/ParseException.java @@ -1,19 +1,22 @@ /* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.datetime.parser; /** @@ -22,10 +25,15 @@ package org.apache.james.mime4j.field.datetime.parser; * calling the method generateParseException in the generated * parser. * - * You can modify this class to customize your error reporting - * mechanisms so long as you retain the public fields. + * Changes for Mime4J: + * extends org.apache.james.mime4j.field.ParseException + * added serialVersionUID + * added constructor ParseException(Throwable) + * default detail message is "Cannot parse field" */ -public class ParseException extends Exception { +public class ParseException extends org.apache.james.mime4j.dom.field.ParseException { + + private static final long serialVersionUID = 1L; /** * This constructor is used by the method "generateParseException" @@ -62,7 +70,12 @@ public class ParseException extends Exception { */ public ParseException() { - super(); + super("Cannot parse field"); + specialConstructor = false; + } + + public ParseException(Throwable cause) { + super(cause); specialConstructor = false; } diff --git a/src/org/apache/james/mime4j/field/datetime/parser/SimpleCharStream.java b/src/org/apache/james/mime4j/field/datetime/parser/SimpleCharStream.java index d44a9018b..0455cf1ec 100644 --- a/src/org/apache/james/mime4j/field/datetime/parser/SimpleCharStream.java +++ b/src/org/apache/james/mime4j/field/datetime/parser/SimpleCharStream.java @@ -1,19 +1,23 @@ -/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 4.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 5.0 */ +/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.datetime.parser; /** @@ -23,10 +27,12 @@ package org.apache.james.mime4j.field.datetime.parser; public class SimpleCharStream { +/** Whether parser is static. */ public static final boolean staticFlag = false; int bufsize; int available; int tokenBegin; +/** Position in buffer. */ public int bufpos = -1; protected int bufline[]; protected int bufcolumn[]; @@ -50,210 +56,218 @@ public class SimpleCharStream protected void ExpandBuff(boolean wrapAround) { - char[] newbuffer = new char[bufsize + 2048]; - int newbufline[] = new int[bufsize + 2048]; - int newbufcolumn[] = new int[bufsize + 2048]; + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; - try - { - if (wrapAround) - { - System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); - System.arraycopy(buffer, 0, newbuffer, - bufsize - tokenBegin, bufpos); - buffer = newbuffer; + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos); + buffer = newbuffer; - System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); - System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); - bufline = newbufline; + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; - System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); - System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); - bufcolumn = newbufcolumn; + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; - maxNextCharInd = (bufpos += (bufsize - tokenBegin)); - } - else - { - System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); - buffer = newbuffer; + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; - System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); - bufline = newbufline; + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; - System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); - bufcolumn = newbufcolumn; + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; - maxNextCharInd = (bufpos -= tokenBegin); - } - } - catch (Throwable t) - { - throw new Error(t.getMessage()); - } + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } - bufsize += 2048; - available = bufsize; - tokenBegin = 0; + bufsize += 2048; + available = bufsize; + tokenBegin = 0; } protected void FillBuff() throws java.io.IOException { - if (maxNextCharInd == available) - { - if (available == bufsize) + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) { - if (tokenBegin > 2048) - { - bufpos = maxNextCharInd = 0; - available = tokenBegin; - } - else if (tokenBegin < 0) - bufpos = maxNextCharInd = 0; - else - ExpandBuff(false); + bufpos = maxNextCharInd = 0; + available = tokenBegin; } - else if (available > tokenBegin) - available = bufsize; - else if ((tokenBegin - available) < 2048) - ExpandBuff(true); + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; else - available = tokenBegin; - } + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } - int i; - try { - if ((i = inputStream.read(buffer, maxNextCharInd, - available - maxNextCharInd)) == -1) - { - inputStream.close(); - throw new java.io.IOException(); - } - else - maxNextCharInd += i; - return; - } - catch(java.io.IOException e) { - --bufpos; - backup(0); - if (tokenBegin == -1) - tokenBegin = bufpos; - throw e; - } + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } } +/** Start. */ public char BeginToken() throws java.io.IOException { - tokenBegin = -1; - char c = readChar(); - tokenBegin = bufpos; + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; - return c; + return c; } protected void UpdateLineColumn(char c) { - column++; + column++; - if (prevCharIsLF) - { - prevCharIsLF = false; + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else line += (column = 1); - } - else if (prevCharIsCR) - { - prevCharIsCR = false; - if (c == '\n') - { - prevCharIsLF = true; - } - else - line += (column = 1); - } + } - switch (c) - { - case '\r' : - prevCharIsCR = true; - break; - case '\n' : - prevCharIsLF = true; - break; - case '\t' : - column--; - column += (tabSize - (column % tabSize)); - break; - default : - break; - } + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (tabSize - (column % tabSize)); + break; + default : + break; + } - bufline[bufpos] = line; - bufcolumn[bufpos] = column; + bufline[bufpos] = line; + bufcolumn[bufpos] = column; } +/** Read a character. */ public char readChar() throws java.io.IOException { - if (inBuf > 0) - { - --inBuf; + if (inBuf > 0) + { + --inBuf; - if (++bufpos == bufsize) - bufpos = 0; + if (++bufpos == bufsize) + bufpos = 0; - return buffer[bufpos]; - } + return buffer[bufpos]; + } - if (++bufpos >= maxNextCharInd) - FillBuff(); + if (++bufpos >= maxNextCharInd) + FillBuff(); - char c = buffer[bufpos]; + char c = buffer[bufpos]; - UpdateLineColumn(c); - return (c); + UpdateLineColumn(c); + return c; } + @Deprecated /** - * @deprecated + * @deprecated * @see #getEndColumn */ public int getColumn() { - return bufcolumn[bufpos]; + return bufcolumn[bufpos]; } + @Deprecated /** - * @deprecated + * @deprecated * @see #getEndLine */ public int getLine() { - return bufline[bufpos]; + return bufline[bufpos]; } + /** Get token end column number. */ public int getEndColumn() { - return bufcolumn[bufpos]; + return bufcolumn[bufpos]; } + /** Get token end line number. */ public int getEndLine() { return bufline[bufpos]; } + /** Get token beginning column number. */ public int getBeginColumn() { - return bufcolumn[tokenBegin]; + return bufcolumn[tokenBegin]; } + /** Get token beginning line number. */ public int getBeginLine() { - return bufline[tokenBegin]; + return bufline[tokenBegin]; } +/** Backup a number of characters. */ public void backup(int amount) { inBuf += amount; if ((bufpos -= amount) < 0) - bufpos += bufsize; + bufpos += bufsize; } + /** Constructor. */ public SimpleCharStream(java.io.Reader dstream, int startline, int startcolumn, int buffersize) { @@ -267,16 +281,20 @@ public class SimpleCharStream bufcolumn = new int[buffersize]; } + /** Constructor. */ public SimpleCharStream(java.io.Reader dstream, int startline, int startcolumn) { - this(dstream, startline, startcolumn, 4096); + this(dstream, startline, startcolumn, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.Reader dstream) { - this(dstream, 1, 1, 4096); + this(dstream, 1, 1, 4096); } + + /** Reinitialise. */ public void ReInit(java.io.Reader dstream, int startline, int startcolumn, int buffersize) { @@ -296,111 +314,128 @@ public class SimpleCharStream bufpos = -1; } + /** Reinitialise. */ public void ReInit(java.io.Reader dstream, int startline, int startcolumn) { - ReInit(dstream, startline, startcolumn, 4096); + ReInit(dstream, startline, startcolumn, 4096); } + /** Reinitialise. */ public void ReInit(java.io.Reader dstream) { - ReInit(dstream, 1, 1, 4096); + ReInit(dstream, 1, 1, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException { - this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) { - this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, int startcolumn) throws java.io.UnsupportedEncodingException { - this(dstream, encoding, startline, startcolumn, 4096); + this(dstream, encoding, startline, startcolumn, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, int startline, int startcolumn) { - this(dstream, startline, startcolumn, 4096); + this(dstream, startline, startcolumn, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException { - this(dstream, encoding, 1, 1, 4096); + this(dstream, encoding, 1, 1, 4096); } + /** Constructor. */ public SimpleCharStream(java.io.InputStream dstream) { - this(dstream, 1, 1, 4096); + this(dstream, 1, 1, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding, int startline, int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException { - ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) { - ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException { - ReInit(dstream, encoding, 1, 1, 4096); + ReInit(dstream, encoding, 1, 1, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream) { - ReInit(dstream, 1, 1, 4096); + ReInit(dstream, 1, 1, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, String encoding, int startline, int startcolumn) throws java.io.UnsupportedEncodingException { - ReInit(dstream, encoding, startline, startcolumn, 4096); + ReInit(dstream, encoding, startline, startcolumn, 4096); } + /** Reinitialise. */ public void ReInit(java.io.InputStream dstream, int startline, int startcolumn) { - ReInit(dstream, startline, startcolumn, 4096); + ReInit(dstream, startline, startcolumn, 4096); } + /** Get token literal value. */ public String GetImage() { - if (bufpos >= tokenBegin) - return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); - else - return new String(buffer, tokenBegin, bufsize - tokenBegin) + - new String(buffer, 0, bufpos + 1); + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); } + /** Get the suffix. */ public char[] GetSuffix(int len) { - char[] ret = new char[len]; + char[] ret = new char[len]; - if ((bufpos + 1) >= len) - System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); - else - { - System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, - len - bufpos - 1); - System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); - } + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } - return ret; + return ret; } + /** Reset buffer when finished. */ public void Done() { - buffer = null; - bufline = null; - bufcolumn = null; + buffer = null; + bufline = null; + bufcolumn = null; } /** @@ -408,47 +443,47 @@ public class SimpleCharStream */ public void adjustBeginLineColumn(int newLine, int newCol) { - int start = tokenBegin; - int len; + int start = tokenBegin; + int len; - if (bufpos >= tokenBegin) - { - len = bufpos - tokenBegin + inBuf + 1; - } - else - { - len = bufsize - tokenBegin + bufpos + 1 + inBuf; - } + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } - int i = 0, j = 0, k = 0; - int nextColDiff = 0, columnDiff = 0; + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; - while (i < len && - bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) - { - bufline[j] = newLine; - nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; - bufcolumn[j] = newCol + columnDiff; - columnDiff = nextColDiff; - i++; - } + while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } - if (i < len) - { - bufline[j] = newLine++; - bufcolumn[j] = newCol + columnDiff; + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; - while (i++ < len) - { - if (bufline[j = start % bufsize] != bufline[++start % bufsize]) - bufline[j] = newLine++; - else - bufline[j] = newLine; - } - } + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } - line = bufline[j]; - column = bufcolumn[j]; + line = bufline[j]; + column = bufcolumn[j]; } } +/* JavaCC - OriginalChecksum=48cd9bc600ae4a033ab56787d790871f (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/datetime/parser/Token.java b/src/org/apache/james/mime4j/field/datetime/parser/Token.java index 52d101ed0..986b62b89 100644 --- a/src/org/apache/james/mime4j/field/datetime/parser/Token.java +++ b/src/org/apache/james/mime4j/field/datetime/parser/Token.java @@ -1,26 +1,37 @@ -/* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* Generated By:JavaCC: Do not edit this line. Token.java Version 5.0 */ +/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.datetime.parser; /** * Describes the input token stream. */ -public class Token { +public class Token implements java.io.Serializable { + + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; /** * An integer that describes the kind of this token. This numbering @@ -29,12 +40,14 @@ public class Token { */ public int kind; - /** - * beginLine and beginColumn describe the position of the first character - * of this token; endLine and endColumn describe the position of the - * last character of this token. - */ - public int beginLine, beginColumn, endLine, endColumn; + /** The line number of the first character of this Token. */ + public int beginLine; + /** The column number of the first character of this Token. */ + public int beginColumn; + /** The line number of the last character of this Token. */ + public int endLine; + /** The column number of the last character of this Token. */ + public int endColumn; /** * The string image of the token. @@ -65,12 +78,46 @@ public class Token { */ public Token specialToken; + /** + * An optional attribute value of the Token. + * Tokens which are not used as syntactic sugar will often contain + * meaningful values that will be used later on by the compiler or + * interpreter. This attribute value is often different from the image. + * Any subclass of Token that actually wants to return a non-null value can + * override this method as appropriate. + */ + public Object getValue() { + return null; + } + + /** + * No-argument constructor + */ + public Token() {} + + /** + * Constructs a new token for the specified Image. + */ + public Token(int kind) + { + this(kind, null); + } + + /** + * Constructs a new token for the specified Image and Kind. + */ + public Token(int kind, String image) + { + this.kind = kind; + this.image = image; + } + /** * Returns the image. */ public String toString() { - return image; + return image; } /** @@ -78,19 +125,25 @@ public class Token { * can create and return subclass objects based on the value of ofKind. * Simply add the cases to the switch for all those special cases. * For example, if you have a subclass of Token called IDToken that - * you want to create if ofKind is ID, simlpy add something like : + * you want to create if ofKind is ID, simply add something like : * - * case MyParserConstants.ID : return new IDToken(); + * case MyParserConstants.ID : return new IDToken(ofKind, image); * * to the following switch statement. Then you can cast matchedToken - * variable to the appropriate type and use it in your lexical actions. + * variable to the appropriate type and use sit in your lexical actions. */ - public static final Token newToken(int ofKind) + public static Token newToken(int ofKind, String image) { - switch(ofKind) - { - default : return new Token(); - } + switch(ofKind) + { + default : return new Token(ofKind, image); + } + } + + public static Token newToken(int ofKind) + { + return newToken(ofKind, null); } } +/* JavaCC - OriginalChecksum=33f05393bf2567608f7c35f8b9169767 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/datetime/parser/TokenMgrError.java b/src/org/apache/james/mime4j/field/datetime/parser/TokenMgrError.java index 973255070..14fcc43a8 100644 --- a/src/org/apache/james/mime4j/field/datetime/parser/TokenMgrError.java +++ b/src/org/apache/james/mime4j/field/datetime/parser/TokenMgrError.java @@ -1,148 +1,165 @@ -/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 3.0 */ -/* - * Copyright 2004 the mime4j project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */ +/* JavaCCOptions: */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ package org.apache.james.mime4j.field.datetime.parser; +/** Token Manager Error. */ public class TokenMgrError extends Error { - /* - * Ordinals for various reasons why an Error of this type can be thrown. - */ - /** - * Lexical error occured. - */ - static final int LEXICAL_ERROR = 0; + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; - /** - * An attempt wass made to create a second instance of a static token manager. - */ - static final int STATIC_LEXER_ERROR = 1; + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ - /** - * Tried to change to an invalid lexical state. - */ - static final int INVALID_LEXICAL_STATE = 2; + /** + * Lexical error occurred. + */ + static final int LEXICAL_ERROR = 0; - /** - * Detected (and bailed out of) an infinite loop in the token manager. - */ - static final int LOOP_DETECTED = 3; + /** + * An attempt was made to create a second instance of a static token manager. + */ + static final int STATIC_LEXER_ERROR = 1; - /** - * Indicates the reason why the exception is thrown. It will have - * one of the above 4 values. - */ - int errorCode; + /** + * Tried to change to an invalid lexical state. + */ + static final int INVALID_LEXICAL_STATE = 2; - /** - * Replaces unprintable characters by their espaced (or unicode escaped) - * equivalents in the given string - */ - protected static final String addEscapes(String str) { - StringBuffer retval = new StringBuffer(); - char ch; - for (int i = 0; i < str.length(); i++) { - switch (str.charAt(i)) - { - case 0 : - continue; - case '\b': - retval.append("\\b"); - continue; - case '\t': - retval.append("\\t"); - continue; - case '\n': - retval.append("\\n"); - continue; - case '\f': - retval.append("\\f"); - continue; - case '\r': - retval.append("\\r"); - continue; - case '\"': - retval.append("\\\""); - continue; - case '\'': - retval.append("\\\'"); - continue; - case '\\': - retval.append("\\\\"); - continue; - default: - if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { - String s = "0000" + Integer.toString(ch, 16); - retval.append("\\u" + s.substring(s.length() - 4, s.length())); - } else { - retval.append(ch); - } - continue; - } + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + static final int LOOP_DETECTED = 3; + + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + int errorCode; + + /** + * Replaces unprintable characters by their escaped (or unicode escaped) + * equivalents in the given string + */ + protected static final String addEscapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; } - return retval.toString(); - } + } + return retval.toString(); + } - /** - * Returns a detailed message for the Error when it is thrown by the - * token manager to indicate a lexical error. - * Parameters : - * EOFSeen : indicates if EOF caused the lexicl error - * curLexState : lexical state in which this error occured - * errorLine : line number when the error occured - * errorColumn : column number when the error occured - * errorAfter : prefix that was seen before this error occured - * curchar : the offending character - * Note: You can customize the lexical error message by modifying this method. - */ - protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { - return("Lexical error at line " + - errorLine + ", column " + - errorColumn + ". Encountered: " + - (EOFSeen ? " " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + - "after : \"" + addEscapes(errorAfter) + "\""); - } + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * Parameters : + * EOFSeen : indicates if EOF caused the lexical error + * curLexState : lexical state in which this error occurred + * errorLine : line number when the error occurred + * errorColumn : column number when the error occurred + * errorAfter : prefix that was seen before this error occurred + * curchar : the offending character + * Note: You can customize the lexical error message by modifying this method. + */ + protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { + return("Lexical error at line " + + errorLine + ", column " + + errorColumn + ". Encountered: " + + (EOFSeen ? " " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + + "after : \"" + addEscapes(errorAfter) + "\""); + } - /** - * You can also modify the body of this method to customize your error messages. - * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not - * of end-users concern, so you can return something like : - * - * "Internal Error : Please file a bug report .... " - * - * from this method for such cases in the release version of your parser. - */ - public String getMessage() { - return super.getMessage(); - } + /** + * You can also modify the body of this method to customize your error messages. + * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not + * of end-users concern, so you can return something like : + * + * "Internal Error : Please file a bug report .... " + * + * from this method for such cases in the release version of your parser. + */ + public String getMessage() { + return super.getMessage(); + } - /* - * Constructors of various flavors follow. - */ + /* + * Constructors of various flavors follow. + */ - public TokenMgrError() { - } + /** No arg constructor. */ + public TokenMgrError() { + } - public TokenMgrError(String message, int reason) { - super(message); - errorCode = reason; - } + /** Constructor with message and reason. */ + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } - public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { - this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); - } + /** Full Constructor. */ + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); + } } +/* JavaCC - OriginalChecksum=375b294dc4b23ad797e2b1145d1bfa1e (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/language/parser/ContentLanguageParser.java b/src/org/apache/james/mime4j/field/language/parser/ContentLanguageParser.java new file mode 100644 index 000000000..643cf159c --- /dev/null +++ b/src/org/apache/james/mime4j/field/language/parser/ContentLanguageParser.java @@ -0,0 +1,272 @@ +/* Generated By:JavaCC: Do not edit this line. ContentLanguageParser.java */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.language.parser; + +import java.util.ArrayList; +import java.util.List; + +public class ContentLanguageParser implements ContentLanguageParserConstants { + private List languages = new ArrayList(); + + /** + * Parses the input into a list of language tags. + * @return list of language tag Strings + */ + public List parse() throws ParseException { + try { + return doParse(); + } catch (TokenMgrError e) { + // An issue with the TOKENiser + // but it's not polite to throw an Error + // when executing on a server + throw new ParseException(e); + } + } + + final private List doParse() throws ParseException { + language(); + label_1: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 1: + ; + break; + default: + jj_la1[0] = jj_gen; + break label_1; + } + jj_consume_token(1); + language(); + } + {if (true) return languages;} + throw new Error("Missing return statement in function"); + } + + final public String language() throws ParseException { + Token token; + StringBuffer languageTag = new StringBuffer(); + String result; + token = jj_consume_token(ALPHA); + languageTag.append(token.image); + label_2: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 2: + case ALPHANUM: + ; + break; + default: + jj_la1[1] = jj_gen; + break label_2; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 2: + jj_consume_token(2); + // This keeps TOKENising simple + token = jj_consume_token(ALPHA); + languageTag.append('-'); + languageTag.append(token.image); + break; + case ALPHANUM: + token = jj_consume_token(ALPHANUM); + languageTag.append('-'); + languageTag.append(token.image); + break; + default: + jj_la1[2] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + result = languageTag.toString(); + languages.add(result); + {if (true) return result;} + throw new Error("Missing return statement in function"); + } + + /** Generated Token Manager. */ + public ContentLanguageParserTokenManager token_source; + SimpleCharStream jj_input_stream; + /** Current token. */ + public Token token; + /** Next token. */ + public Token jj_nt; + private int jj_ntk; + private int jj_gen; + final private int[] jj_la1 = new int[3]; + static private int[] jj_la1_0; + static { + jj_la1_init_0(); + } + private static void jj_la1_init_0() { + jj_la1_0 = new int[] {0x2,0x80004,0x80004,}; + } + + /** Constructor with InputStream. */ + public ContentLanguageParser(java.io.InputStream stream) { + this(stream, null); + } + /** Constructor with InputStream and supplied encoding */ + public ContentLanguageParser(java.io.InputStream stream, String encoding) { + try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source = new ContentLanguageParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream stream) { + ReInit(stream, null); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream stream, String encoding) { + try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + /** Constructor. */ + public ContentLanguageParser(java.io.Reader stream) { + jj_input_stream = new SimpleCharStream(stream, 1, 1); + token_source = new ContentLanguageParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader stream) { + jj_input_stream.ReInit(stream, 1, 1); + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + /** Constructor with generated Token Manager. */ + public ContentLanguageParser(ContentLanguageParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(ContentLanguageParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + private Token jj_consume_token(int kind) throws ParseException { + Token oldToken; + if ((oldToken = token).next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + if (token.kind == kind) { + jj_gen++; + return token; + } + token = oldToken; + jj_kind = kind; + throw generateParseException(); + } + + +/** Get the next Token. */ + final public Token getNextToken() { + if (token.next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + jj_gen++; + return token; + } + +/** Get the specific Token. */ + final public Token getToken(int index) { + Token t = token; + for (int i = 0; i < index; i++) { + if (t.next != null) t = t.next; + else t = t.next = token_source.getNextToken(); + } + return t; + } + + private int jj_ntk() { + if ((jj_nt=token.next) == null) + return (jj_ntk = (token.next=token_source.getNextToken()).kind); + else + return (jj_ntk = jj_nt.kind); + } + + private java.util.List jj_expentries = new java.util.ArrayList(); + private int[] jj_expentry; + private int jj_kind = -1; + + /** Generate ParseException. */ + public ParseException generateParseException() { + jj_expentries.clear(); + boolean[] la1tokens = new boolean[23]; + if (jj_kind >= 0) { + la1tokens[jj_kind] = true; + jj_kind = -1; + } + for (int i = 0; i < 3; i++) { + if (jj_la1[i] == jj_gen) { + for (int j = 0; j < 32; j++) { + if ((jj_la1_0[i] & (1<", + "\",\"", + "\"-\"", + "", + "\"(\"", + "\")\"", + "", + "\"(\"", + "", + "", + "\"(\"", + "\")\"", + "", + "\"\\\"\"", + "", + "", + "\"\\\"\"", + "", + "", + "", + "\".\"", + "", + "", + }; + +} diff --git a/src/org/apache/james/mime4j/field/language/parser/ContentLanguageParserTokenManager.java b/src/org/apache/james/mime4j/field/language/parser/ContentLanguageParserTokenManager.java new file mode 100644 index 000000000..1e66b9a86 --- /dev/null +++ b/src/org/apache/james/mime4j/field/language/parser/ContentLanguageParserTokenManager.java @@ -0,0 +1,860 @@ +/* Generated By:JavaCC: Do not edit this line. ContentLanguageParserTokenManager.java */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.language.parser; +import java.util.ArrayList; +import java.util.List; + +/** Token Manager. */ +public class ContentLanguageParserTokenManager implements ContentLanguageParserConstants +{ + // Keeps track of how many levels of comment nesting + // we've encountered. This is only used when the 2nd + // level is reached, for example ((this)), not (this). + // This is because the outermost level must be treated + // specially anyway, because the outermost ")" has a + // different token type than inner ")" instances. + int commentNest; + + /** Debug output. */ + public java.io.PrintStream debugStream = System.out; + /** Set debug output. */ + public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; } +private final int jjStopStringLiteralDfa_0(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_0(int pos, long active0) +{ + return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1); +} +private int jjStopAtPos(int pos, int kind) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + return pos + 1; +} +private int jjMoveStringLiteralDfa0_0() +{ + switch(curChar) + { + case 34: + return jjStopAtPos(0, 13); + case 40: + return jjStopAtPos(0, 4); + case 44: + return jjStopAtPos(0, 1); + case 45: + return jjStopAtPos(0, 2); + case 46: + return jjStopAtPos(0, 20); + default : + return jjMoveNfa_0(4, 0); + } +} +private int jjMoveNfa_0(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 4; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 4: + if ((0x3ff000000000000L & l) != 0L) + { + if (kind > 19) + kind = 19; + jjCheckNAdd(3); + } + else if ((0x100002600L & l) != 0L) + { + if (kind > 3) + kind = 3; + jjCheckNAdd(0); + } + if ((0x3ff000000000000L & l) != 0L) + { + if (kind > 17) + kind = 17; + jjCheckNAdd(1); + } + break; + case 0: + if ((0x100002600L & l) == 0L) + break; + kind = 3; + jjCheckNAdd(0); + break; + case 1: + if ((0x3ff000000000000L & l) == 0L) + break; + if (kind > 17) + kind = 17; + jjCheckNAdd(1); + break; + case 3: + if ((0x3ff000000000000L & l) == 0L) + break; + if (kind > 19) + kind = 19; + jjCheckNAdd(3); + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 4: + if ((0x7fffffe07fffffeL & l) != 0L) + { + if (kind > 19) + kind = 19; + jjCheckNAdd(3); + } + if ((0x7fffffe07fffffeL & l) != 0L) + { + if (kind > 18) + kind = 18; + jjCheckNAdd(2); + } + break; + case 2: + if ((0x7fffffe07fffffeL & l) == 0L) + break; + if (kind > 18) + kind = 18; + jjCheckNAdd(2); + break; + case 3: + if ((0x7fffffe07fffffeL & l) == 0L) + break; + if (kind > 19) + kind = 19; + jjCheckNAdd(3); + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 4 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_1(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_1(int pos, long active0) +{ + return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_1() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 7); + case 41: + return jjStopAtPos(0, 5); + default : + return jjMoveNfa_1(0, 0); + } +} +static final long[] jjbitVec0 = { + 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL +}; +private int jjMoveNfa_1(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 8) + kind = 8; + break; + case 1: + if (kind > 6) + kind = 6; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 8) + kind = 8; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 6) + kind = 6; + break; + case 2: + if (kind > 8) + kind = 8; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 8) + kind = 8; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 6) + kind = 6; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_3(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_3(int pos, long active0) +{ + return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_3() +{ + switch(curChar) + { + case 34: + return jjStopAtPos(0, 16); + default : + return jjMoveNfa_3(0, 0); + } +} +private int jjMoveNfa_3(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + case 2: + if ((0xfffffffbffffffffL & l) == 0L) + break; + if (kind > 15) + kind = 15; + jjCheckNAdd(2); + break; + case 1: + if (kind > 14) + kind = 14; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0xffffffffefffffffL & l) != 0L) + { + if (kind > 15) + kind = 15; + jjCheckNAdd(2); + } + else if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 14) + kind = 14; + break; + case 2: + if ((0xffffffffefffffffL & l) == 0L) + break; + if (kind > 15) + kind = 15; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + case 2: + if ((jjbitVec0[i2] & l2) == 0L) + break; + if (kind > 15) + kind = 15; + jjCheckNAdd(2); + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 14) + kind = 14; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_2(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_2(int pos, long active0) +{ + return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_2() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 10); + case 41: + return jjStopAtPos(0, 11); + default : + return jjMoveNfa_2(0, 0); + } +} +private int jjMoveNfa_2(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 12) + kind = 12; + break; + case 1: + if (kind > 9) + kind = 9; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 12) + kind = 12; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 9) + kind = 9; + break; + case 2: + if (kind > 12) + kind = 12; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 12) + kind = 12; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 9) + kind = 9; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +static final int[] jjnextStates = { +}; + +/** Token literal values. */ +public static final String[] jjstrLiteralImages = { +"", "\54", "\55", null, null, null, null, null, null, null, null, null, null, +null, null, null, null, null, null, null, "\56", null, null, }; + +/** Lexer state names. */ +public static final String[] lexStateNames = { + "DEFAULT", + "INCOMMENT", + "NESTED_COMMENT", + "INQUOTEDSTRING", +}; + +/** Lex State array. */ +public static final int[] jjnewLexState = { + -1, -1, -1, -1, 1, 0, -1, 2, -1, -1, -1, -1, -1, 3, -1, -1, 0, -1, -1, -1, -1, -1, -1, +}; +static final long[] jjtoToken = { + 0x1f0007L, +}; +static final long[] jjtoSkip = { + 0x28L, +}; +static final long[] jjtoSpecial = { + 0x8L, +}; +static final long[] jjtoMore = { + 0xffd0L, +}; +protected SimpleCharStream input_stream; +private final int[] jjrounds = new int[4]; +private final int[] jjstateSet = new int[8]; +private final StringBuilder jjimage = new StringBuilder(); +private StringBuilder image = jjimage; +private int jjimageLen; +private int lengthOfMatch; +protected char curChar; +/** Constructor. */ +public ContentLanguageParserTokenManager(SimpleCharStream stream){ + if (SimpleCharStream.staticFlag) + throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer."); + input_stream = stream; +} + +/** Constructor. */ +public ContentLanguageParserTokenManager(SimpleCharStream stream, int lexState){ + this(stream); + SwitchTo(lexState); +} + +/** Reinitialise parser. */ +public void ReInit(SimpleCharStream stream) +{ + jjmatchedPos = jjnewStateCnt = 0; + curLexState = defaultLexState; + input_stream = stream; + ReInitRounds(); +} +private void ReInitRounds() +{ + int i; + jjround = 0x80000001; + for (i = 4; i-- > 0;) + jjrounds[i] = 0x80000000; +} + +/** Reinitialise parser. */ +public void ReInit(SimpleCharStream stream, int lexState) +{ + ReInit(stream); + SwitchTo(lexState); +} + +/** Switch to specified lex state. */ +public void SwitchTo(int lexState) +{ + if (lexState >= 4 || lexState < 0) + throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE); + else + curLexState = lexState; +} + +protected Token jjFillToken() +{ + final Token t; + final String curTokenImage; + final int beginLine; + final int endLine; + final int beginColumn; + final int endColumn; + String im = jjstrLiteralImages[jjmatchedKind]; + curTokenImage = (im == null) ? input_stream.GetImage() : im; + beginLine = input_stream.getBeginLine(); + beginColumn = input_stream.getBeginColumn(); + endLine = input_stream.getEndLine(); + endColumn = input_stream.getEndColumn(); + t = Token.newToken(jjmatchedKind, curTokenImage); + + t.beginLine = beginLine; + t.endLine = endLine; + t.beginColumn = beginColumn; + t.endColumn = endColumn; + + return t; +} + +int curLexState = 0; +int defaultLexState = 0; +int jjnewStateCnt; +int jjround; +int jjmatchedPos; +int jjmatchedKind; + +/** Get the next Token. */ +public Token getNextToken() +{ + Token specialToken = null; + Token matchedToken; + int curPos = 0; + + EOFLoop : + for (;;) + { + try + { + curChar = input_stream.BeginToken(); + } + catch(java.io.IOException e) + { + jjmatchedKind = 0; + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + return matchedToken; + } + image = jjimage; + image.setLength(0); + jjimageLen = 0; + + for (;;) + { + switch(curLexState) + { + case 0: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_0(); + break; + case 1: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_1(); + break; + case 2: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_2(); + break; + case 3: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_3(); + break; + } + if (jjmatchedKind != 0x7fffffff) + { + if (jjmatchedPos + 1 < curPos) + input_stream.backup(curPos - jjmatchedPos - 1); + if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + TokenLexicalActions(matchedToken); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + return matchedToken; + } + else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + if ((jjtoSpecial[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + if (specialToken == null) + specialToken = matchedToken; + else + { + matchedToken.specialToken = specialToken; + specialToken = (specialToken.next = matchedToken); + } + } + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + continue EOFLoop; + } + MoreLexicalActions(); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + curPos = 0; + jjmatchedKind = 0x7fffffff; + try { + curChar = input_stream.readChar(); + continue; + } + catch (java.io.IOException e1) { } + } + int error_line = input_stream.getEndLine(); + int error_column = input_stream.getEndColumn(); + String error_after = null; + boolean EOFSeen = false; + try { input_stream.readChar(); input_stream.backup(1); } + catch (java.io.IOException e1) { + EOFSeen = true; + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + if (curChar == '\n' || curChar == '\r') { + error_line++; + error_column = 0; + } + else + error_column++; + } + if (!EOFSeen) { + input_stream.backup(1); + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + } + throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR); + } + } +} + +void MoreLexicalActions() +{ + jjimageLen += (lengthOfMatch = jjmatchedPos + 1); + switch(jjmatchedKind) + { + case 6 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 7 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + commentNest = 1; + break; + case 9 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 10 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + ++commentNest; + break; + case 11 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); + break; + case 13 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 1); + break; + case 14 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + default : + break; + } +} +void TokenLexicalActions(Token matchedToken) +{ + switch(jjmatchedKind) + { + case 16 : + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + matchedToken.image = image.substring(0, image.length() - 1); + break; + default : + break; + } +} +private void jjCheckNAdd(int state) +{ + if (jjrounds[state] != jjround) + { + jjstateSet[jjnewStateCnt++] = state; + jjrounds[state] = jjround; + } +} +private void jjAddStates(int start, int end) +{ + do { + jjstateSet[jjnewStateCnt++] = jjnextStates[start]; + } while (start++ != end); +} +private void jjCheckNAddTwoStates(int state1, int state2) +{ + jjCheckNAdd(state1); + jjCheckNAdd(state2); +} + +} diff --git a/src/org/apache/james/mime4j/field/language/parser/ParseException.java b/src/org/apache/james/mime4j/field/language/parser/ParseException.java new file mode 100644 index 000000000..ad8c7aba8 --- /dev/null +++ b/src/org/apache/james/mime4j/field/language/parser/ParseException.java @@ -0,0 +1,220 @@ +/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.language.parser; + +/** + * This exception is thrown when parse errors are encountered. + * You can explicitly create objects of this exception type by + * calling the method generateParseException in the generated + * parser. + * + * Changes for Mime4J: + * extends org.apache.james.mime4j.field.ParseException + * added serialVersionUID + * added constructor ParseException(Throwable) + * default detail message is "Cannot parse field" + */ +public class ParseException extends org.apache.james.mime4j.dom.field.ParseException { + + private static final long serialVersionUID = 1L; + + /** + * This constructor is used by the method "generateParseException" + * in the generated parser. Calling this constructor generates + * a new object of this type with the fields "currentToken", + * "expectedTokenSequences", and "tokenImage" set. The boolean + * flag "specialConstructor" is also set to true to indicate that + * this constructor was used to create this object. + * This constructor calls its super class with the empty string + * to force the "toString" method of parent class "Throwable" to + * print the error message in the form: + * ParseException: + */ + public ParseException(Token currentTokenVal, + int[][] expectedTokenSequencesVal, + String[] tokenImageVal + ) + { + super(""); + specialConstructor = true; + currentToken = currentTokenVal; + expectedTokenSequences = expectedTokenSequencesVal; + tokenImage = tokenImageVal; + } + + /** + * The following constructors are for use by you for whatever + * purpose you can think of. Constructing the exception in this + * manner makes the exception behave in the normal way - i.e., as + * documented in the class "Throwable". The fields "errorToken", + * "expectedTokenSequences", and "tokenImage" do not contain + * relevant information. The JavaCC generated code does not use + * these constructors. + */ + + public ParseException() { + super("Cannot parse field"); + specialConstructor = false; + } + + public ParseException(Throwable cause) { + super(cause); + specialConstructor = false; + } + + public ParseException(String message) { + super(message); + specialConstructor = false; + } + + /** + * This variable determines which constructor was used to create + * this object and thereby affects the semantics of the + * "getMessage" method (see below). + */ + protected boolean specialConstructor; + + /** + * This is the last token that has been consumed successfully. If + * this object has been created due to a parse error, the token + * followng this token will (therefore) be the first error token. + */ + public Token currentToken; + + /** + * Each entry in this array is an array of integers. Each array + * of integers represents a sequence of tokens (by their ordinal + * values) that is expected at this point of the parse. + */ + public int[][] expectedTokenSequences; + + /** + * This is a reference to the "tokenImage" array of the generated + * parser within which the parse error occurred. This array is + * defined in the generated ...Constants interface. + */ + public String[] tokenImage; + + /** + * This method has the standard behavior when this object has been + * created using the standard constructors. Otherwise, it uses + * "currentToken" and "expectedTokenSequences" to generate a parse + * error message and returns it. If this object has been created + * due to a parse error, and you do not catch it (it gets thrown + * from the parser), then this method is called during the printing + * of the final stack trace, and hence the correct error message + * gets displayed. + */ + public String getMessage() { + if (!specialConstructor) { + return super.getMessage(); + } + StringBuffer expected = new StringBuffer(); + int maxSize = 0; + for (int i = 0; i < expectedTokenSequences.length; i++) { + if (maxSize < expectedTokenSequences[i].length) { + maxSize = expectedTokenSequences[i].length; + } + for (int j = 0; j < expectedTokenSequences[i].length; j++) { + expected.append(tokenImage[expectedTokenSequences[i][j]]).append(" "); + } + if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) { + expected.append("..."); + } + expected.append(eol).append(" "); + } + String retval = "Encountered \""; + Token tok = currentToken.next; + for (int i = 0; i < maxSize; i++) { + if (i != 0) retval += " "; + if (tok.kind == 0) { + retval += tokenImage[0]; + break; + } + retval += add_escapes(tok.image); + tok = tok.next; + } + retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn; + retval += "." + eol; + if (expectedTokenSequences.length == 1) { + retval += "Was expecting:" + eol + " "; + } else { + retval += "Was expecting one of:" + eol + " "; + } + retval += expected.toString(); + return retval; + } + + /** + * The end of line string for this machine. + */ + protected String eol = System.getProperty("line.separator", "\n"); + + /** + * Used to convert raw characters to their escaped version + * when these raw version cannot be used as part of an ASCII + * string literal. + */ + protected String add_escapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + +} diff --git a/src/org/apache/james/mime4j/field/language/parser/SimpleCharStream.java b/src/org/apache/james/mime4j/field/language/parser/SimpleCharStream.java new file mode 100644 index 000000000..a5bedb3af --- /dev/null +++ b/src/org/apache/james/mime4j/field/language/parser/SimpleCharStream.java @@ -0,0 +1,489 @@ +/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 5.0 */ +/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.language.parser; + +/** + * An implementation of interface CharStream, where the stream is assumed to + * contain only ASCII characters (without unicode processing). + */ + +public class SimpleCharStream +{ +/** Whether parser is static. */ + public static final boolean staticFlag = false; + int bufsize; + int available; + int tokenBegin; +/** Position in buffer. */ + public int bufpos = -1; + protected int bufline[]; + protected int bufcolumn[]; + + protected int column = 0; + protected int line = 1; + + protected boolean prevCharIsCR = false; + protected boolean prevCharIsLF = false; + + protected java.io.Reader inputStream; + + protected char[] buffer; + protected int maxNextCharInd = 0; + protected int inBuf = 0; + protected int tabSize = 8; + + protected void setTabSize(int i) { tabSize = i; } + protected int getTabSize(int i) { return tabSize; } + + + protected void ExpandBuff(boolean wrapAround) + { + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; + + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } + + + bufsize += 2048; + available = bufsize; + tokenBegin = 0; + } + + protected void FillBuff() throws java.io.IOException + { + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) + { + bufpos = maxNextCharInd = 0; + available = tokenBegin; + } + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; + else + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } + + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } + } + +/** Start. */ + public char BeginToken() throws java.io.IOException + { + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; + + return c; + } + + protected void UpdateLineColumn(char c) + { + column++; + + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else + line += (column = 1); + } + + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (tabSize - (column % tabSize)); + break; + default : + break; + } + + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + +/** Read a character. */ + public char readChar() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + if (++bufpos == bufsize) + bufpos = 0; + + return buffer[bufpos]; + } + + if (++bufpos >= maxNextCharInd) + FillBuff(); + + char c = buffer[bufpos]; + + UpdateLineColumn(c); + return c; + } + + @Deprecated + /** + * @deprecated + * @see #getEndColumn + */ + + public int getColumn() { + return bufcolumn[bufpos]; + } + + @Deprecated + /** + * @deprecated + * @see #getEndLine + */ + + public int getLine() { + return bufline[bufpos]; + } + + /** Get token end column number. */ + public int getEndColumn() { + return bufcolumn[bufpos]; + } + + /** Get token end line number. */ + public int getEndLine() { + return bufline[bufpos]; + } + + /** Get token beginning column number. */ + public int getBeginColumn() { + return bufcolumn[tokenBegin]; + } + + /** Get token beginning line number. */ + public int getBeginLine() { + return bufline[tokenBegin]; + } + +/** Backup a number of characters. */ + public void backup(int amount) { + + inBuf += amount; + if ((bufpos -= amount) < 0) + bufpos += bufsize; + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream) + { + this(dstream, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + if (buffer == null || buffersize != buffer.length) + { + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + prevCharIsLF = prevCharIsCR = false; + tokenBegin = inBuf = maxNextCharInd = 0; + bufpos = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream) + { + ReInit(dstream, 1, 1, 4096); + } + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, 1, 1, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream) + { + this(dstream, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream) + { + ReInit(dstream, 1, 1, 4096); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, startline, startcolumn, 4096); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + /** Get token literal value. */ + public String GetImage() + { + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); + } + + /** Get the suffix. */ + public char[] GetSuffix(int len) + { + char[] ret = new char[len]; + + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } + + return ret; + } + + /** Reset buffer when finished. */ + public void Done() + { + buffer = null; + bufline = null; + bufcolumn = null; + } + + /** + * Method to adjust line and column numbers for the start of a token. + */ + public void adjustBeginLineColumn(int newLine, int newCol) + { + int start = tokenBegin; + int len; + + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } + + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; + + while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } + + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; + + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } + + line = bufline[j]; + column = bufcolumn[j]; + } + +} +/* JavaCC - OriginalChecksum=d7aa8070376344ebe65083648cdac187 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/language/parser/Token.java b/src/org/apache/james/mime4j/field/language/parser/Token.java new file mode 100644 index 000000000..53c3c120f --- /dev/null +++ b/src/org/apache/james/mime4j/field/language/parser/Token.java @@ -0,0 +1,149 @@ +/* Generated By:JavaCC: Do not edit this line. Token.java Version 5.0 */ +/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.language.parser; + +/** + * Describes the input token stream. + */ + +public class Token implements java.io.Serializable { + + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; + + /** + * An integer that describes the kind of this token. This numbering + * system is determined by JavaCCParser, and a table of these numbers is + * stored in the file ...Constants.java. + */ + public int kind; + + /** The line number of the first character of this Token. */ + public int beginLine; + /** The column number of the first character of this Token. */ + public int beginColumn; + /** The line number of the last character of this Token. */ + public int endLine; + /** The column number of the last character of this Token. */ + public int endColumn; + + /** + * The string image of the token. + */ + public String image; + + /** + * A reference to the next regular (non-special) token from the input + * stream. If this is the last token from the input stream, or if the + * token manager has not read tokens beyond this one, this field is + * set to null. This is true only if this token is also a regular + * token. Otherwise, see below for a description of the contents of + * this field. + */ + public Token next; + + /** + * This field is used to access special tokens that occur prior to this + * token, but after the immediately preceding regular (non-special) token. + * If there are no such special tokens, this field is set to null. + * When there are more than one such special token, this field refers + * to the last of these special tokens, which in turn refers to the next + * previous special token through its specialToken field, and so on + * until the first special token (whose specialToken field is null). + * The next fields of special tokens refer to other special tokens that + * immediately follow it (without an intervening regular token). If there + * is no such token, this field is null. + */ + public Token specialToken; + + /** + * An optional attribute value of the Token. + * Tokens which are not used as syntactic sugar will often contain + * meaningful values that will be used later on by the compiler or + * interpreter. This attribute value is often different from the image. + * Any subclass of Token that actually wants to return a non-null value can + * override this method as appropriate. + */ + public Object getValue() { + return null; + } + + /** + * No-argument constructor + */ + public Token() {} + + /** + * Constructs a new token for the specified Image. + */ + public Token(int kind) + { + this(kind, null); + } + + /** + * Constructs a new token for the specified Image and Kind. + */ + public Token(int kind, String image) + { + this.kind = kind; + this.image = image; + } + + /** + * Returns the image. + */ + public String toString() + { + return image; + } + + /** + * Returns a new Token object, by default. However, if you want, you + * can create and return subclass objects based on the value of ofKind. + * Simply add the cases to the switch for all those special cases. + * For example, if you have a subclass of Token called IDToken that + * you want to create if ofKind is ID, simply add something like : + * + * case MyParserConstants.ID : return new IDToken(ofKind, image); + * + * to the following switch statement. Then you can cast matchedToken + * variable to the appropriate type and use sit in your lexical actions. + */ + public static Token newToken(int ofKind, String image) + { + switch(ofKind) + { + default : return new Token(ofKind, image); + } + } + + public static Token newToken(int ofKind) + { + return newToken(ofKind, null); + } + +} +/* JavaCC - OriginalChecksum=fcddf4a8d964b333f6f3396cdbc4bfc9 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/language/parser/TokenMgrError.java b/src/org/apache/james/mime4j/field/language/parser/TokenMgrError.java new file mode 100644 index 000000000..ee9e7f6a0 --- /dev/null +++ b/src/org/apache/james/mime4j/field/language/parser/TokenMgrError.java @@ -0,0 +1,165 @@ +/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */ +/* JavaCCOptions: */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.language.parser; + +/** Token Manager Error. */ +public class TokenMgrError extends Error +{ + + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; + + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ + + /** + * Lexical error occurred. + */ + static final int LEXICAL_ERROR = 0; + + /** + * An attempt was made to create a second instance of a static token manager. + */ + static final int STATIC_LEXER_ERROR = 1; + + /** + * Tried to change to an invalid lexical state. + */ + static final int INVALID_LEXICAL_STATE = 2; + + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + static final int LOOP_DETECTED = 3; + + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + int errorCode; + + /** + * Replaces unprintable characters by their escaped (or unicode escaped) + * equivalents in the given string + */ + protected static final String addEscapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * Parameters : + * EOFSeen : indicates if EOF caused the lexical error + * curLexState : lexical state in which this error occurred + * errorLine : line number when the error occurred + * errorColumn : column number when the error occurred + * errorAfter : prefix that was seen before this error occurred + * curchar : the offending character + * Note: You can customize the lexical error message by modifying this method. + */ + protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { + return("Lexical error at line " + + errorLine + ", column " + + errorColumn + ". Encountered: " + + (EOFSeen ? " " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + + "after : \"" + addEscapes(errorAfter) + "\""); + } + + /** + * You can also modify the body of this method to customize your error messages. + * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not + * of end-users concern, so you can return something like : + * + * "Internal Error : Please file a bug report .... " + * + * from this method for such cases in the release version of your parser. + */ + public String getMessage() { + return super.getMessage(); + } + + /* + * Constructors of various flavors follow. + */ + + /** No arg constructor. */ + public TokenMgrError() { + } + + /** Constructor with message and reason. */ + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } + + /** Full Constructor. */ + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); + } +} +/* JavaCC - OriginalChecksum=eb6019208b078108e75f1d3bfb81f2ca (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/mimeversion/parser/MimeVersionParser.java b/src/org/apache/james/mime4j/field/mimeversion/parser/MimeVersionParser.java new file mode 100644 index 000000000..36e1b1e2e --- /dev/null +++ b/src/org/apache/james/mime4j/field/mimeversion/parser/MimeVersionParser.java @@ -0,0 +1,235 @@ +/* Generated By:JavaCC: Do not edit this line. MimeVersionParser.java */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.mimeversion.parser; + +public class MimeVersionParser implements MimeVersionParserConstants { + public static final int INITIAL_VERSION_VALUE = -1; + private int major=INITIAL_VERSION_VALUE; + private int minor=INITIAL_VERSION_VALUE; + + public int getMinorVersion() { + return minor; + } + + public int getMajorVersion() { + return major; + } + + final public void parseLine() throws ParseException { + parse(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 1: + jj_consume_token(1); + break; + default: + jj_la1[0] = jj_gen; + ; + } + jj_consume_token(2); + } + + final public void parseAll() throws ParseException { + parse(); + jj_consume_token(0); + } + + final public void parse() throws ParseException { + Token major; + Token minor; + major = jj_consume_token(DIGITS); + jj_consume_token(DOT); + minor = jj_consume_token(DIGITS); + try { + this.major = Integer.parseInt(major.image); + this.minor = Integer.parseInt(minor.image); + } catch (NumberFormatException e) { + {if (true) throw new ParseException(e.getMessage());} + } + } + + /** Generated Token Manager. */ + public MimeVersionParserTokenManager token_source; + SimpleCharStream jj_input_stream; + /** Current token. */ + public Token token; + /** Next token. */ + public Token jj_nt; + private int jj_ntk; + private int jj_gen; + final private int[] jj_la1 = new int[1]; + static private int[] jj_la1_0; + static { + jj_la1_init_0(); + } + private static void jj_la1_init_0() { + jj_la1_0 = new int[] {0x2,}; + } + + /** Constructor with InputStream. */ + public MimeVersionParser(java.io.InputStream stream) { + this(stream, null); + } + /** Constructor with InputStream and supplied encoding */ + public MimeVersionParser(java.io.InputStream stream, String encoding) { + try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source = new MimeVersionParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 1; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream stream) { + ReInit(stream, null); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream stream, String encoding) { + try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 1; i++) jj_la1[i] = -1; + } + + /** Constructor. */ + public MimeVersionParser(java.io.Reader stream) { + jj_input_stream = new SimpleCharStream(stream, 1, 1); + token_source = new MimeVersionParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 1; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader stream) { + jj_input_stream.ReInit(stream, 1, 1); + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 1; i++) jj_la1[i] = -1; + } + + /** Constructor with generated Token Manager. */ + public MimeVersionParser(MimeVersionParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 1; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(MimeVersionParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 1; i++) jj_la1[i] = -1; + } + + private Token jj_consume_token(int kind) throws ParseException { + Token oldToken; + if ((oldToken = token).next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + if (token.kind == kind) { + jj_gen++; + return token; + } + token = oldToken; + jj_kind = kind; + throw generateParseException(); + } + + +/** Get the next Token. */ + final public Token getNextToken() { + if (token.next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + jj_gen++; + return token; + } + +/** Get the specific Token. */ + final public Token getToken(int index) { + Token t = token; + for (int i = 0; i < index; i++) { + if (t.next != null) t = t.next; + else t = t.next = token_source.getNextToken(); + } + return t; + } + + private int jj_ntk() { + if ((jj_nt=token.next) == null) + return (jj_ntk = (token.next=token_source.getNextToken()).kind); + else + return (jj_ntk = jj_nt.kind); + } + + private java.util.List jj_expentries = new java.util.ArrayList(); + private int[] jj_expentry; + private int jj_kind = -1; + + /** Generate ParseException. */ + public ParseException generateParseException() { + jj_expentries.clear(); + boolean[] la1tokens = new boolean[21]; + if (jj_kind >= 0) { + la1tokens[jj_kind] = true; + jj_kind = -1; + } + for (int i = 0; i < 1; i++) { + if (jj_la1[i] == jj_gen) { + for (int j = 0; j < 32; j++) { + if ((jj_la1_0[i] & (1<", + "\"\\r\"", + "\"\\n\"", + "", + "\"(\"", + "\")\"", + "", + "\"(\"", + "", + "", + "\"(\"", + "\")\"", + "", + "\"\\\"\"", + "", + "", + "\"\\\"\"", + "", + "\".\"", + "", + "", + }; + +} diff --git a/src/org/apache/james/mime4j/field/mimeversion/parser/MimeVersionParserTokenManager.java b/src/org/apache/james/mime4j/field/mimeversion/parser/MimeVersionParserTokenManager.java new file mode 100644 index 000000000..92bff0b9a --- /dev/null +++ b/src/org/apache/james/mime4j/field/mimeversion/parser/MimeVersionParserTokenManager.java @@ -0,0 +1,824 @@ +/* Generated By:JavaCC: Do not edit this line. MimeVersionParserTokenManager.java */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.mimeversion.parser; + +/** Token Manager. */ +public class MimeVersionParserTokenManager implements MimeVersionParserConstants +{ + // Keeps track of how many levels of comment nesting + // we've encountered. This is only used when the 2nd + // level is reached, for example ((this)), not (this). + // This is because the outermost level must be treated + // specially anyway, because the outermost ")" has a + // different token type than inner ")" instances. + int commentNest; + + /** Debug output. */ + public java.io.PrintStream debugStream = System.out; + /** Set debug output. */ + public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; } +private final int jjStopStringLiteralDfa_0(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_0(int pos, long active0) +{ + return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1); +} +private int jjStopAtPos(int pos, int kind) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + return pos + 1; +} +private int jjMoveStringLiteralDfa0_0() +{ + switch(curChar) + { + case 10: + return jjStartNfaWithStates_0(0, 2, 0); + case 13: + return jjStartNfaWithStates_0(0, 1, 0); + case 34: + return jjStopAtPos(0, 13); + case 40: + return jjStopAtPos(0, 4); + case 46: + return jjStopAtPos(0, 18); + default : + return jjMoveNfa_0(2, 0); + } +} +private int jjStartNfaWithStates_0(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_0(state, pos + 1); +} +private int jjMoveNfa_0(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 2; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 2: + if ((0x3ff000000000000L & l) != 0L) + { + if (kind > 17) + kind = 17; + jjCheckNAdd(1); + } + else if ((0x100002600L & l) != 0L) + { + if (kind > 3) + kind = 3; + jjCheckNAdd(0); + } + break; + case 0: + if ((0x100002600L & l) == 0L) + break; + kind = 3; + jjCheckNAdd(0); + break; + case 1: + if ((0x3ff000000000000L & l) == 0L) + break; + kind = 17; + jjCheckNAdd(1); + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 2 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_1(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_1(int pos, long active0) +{ + return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_1() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 7); + case 41: + return jjStopAtPos(0, 5); + default : + return jjMoveNfa_1(0, 0); + } +} +static final long[] jjbitVec0 = { + 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL +}; +private int jjMoveNfa_1(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 8) + kind = 8; + break; + case 1: + if (kind > 6) + kind = 6; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 8) + kind = 8; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 6) + kind = 6; + break; + case 2: + if (kind > 8) + kind = 8; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 8) + kind = 8; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 6) + kind = 6; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_3(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_3(int pos, long active0) +{ + return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_3() +{ + switch(curChar) + { + case 34: + return jjStopAtPos(0, 16); + default : + return jjMoveNfa_3(0, 0); + } +} +private int jjMoveNfa_3(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + case 2: + if ((0xfffffffbffffffffL & l) == 0L) + break; + if (kind > 15) + kind = 15; + jjCheckNAdd(2); + break; + case 1: + if (kind > 14) + kind = 14; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0xffffffffefffffffL & l) != 0L) + { + if (kind > 15) + kind = 15; + jjCheckNAdd(2); + } + else if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 14) + kind = 14; + break; + case 2: + if ((0xffffffffefffffffL & l) == 0L) + break; + if (kind > 15) + kind = 15; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + case 2: + if ((jjbitVec0[i2] & l2) == 0L) + break; + if (kind > 15) + kind = 15; + jjCheckNAdd(2); + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 14) + kind = 14; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_2(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_2(int pos, long active0) +{ + return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_2() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 10); + case 41: + return jjStopAtPos(0, 11); + default : + return jjMoveNfa_2(0, 0); + } +} +private int jjMoveNfa_2(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 12) + kind = 12; + break; + case 1: + if (kind > 9) + kind = 9; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 12) + kind = 12; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 9) + kind = 9; + break; + case 2: + if (kind > 12) + kind = 12; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 12) + kind = 12; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 9) + kind = 9; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +static final int[] jjnextStates = { +}; + +/** Token literal values. */ +public static final String[] jjstrLiteralImages = { +"", "\15", "\12", null, null, null, null, null, null, null, null, null, null, +null, null, null, null, null, "\56", null, null, }; + +/** Lexer state names. */ +public static final String[] lexStateNames = { + "DEFAULT", + "INCOMMENT", + "NESTED_COMMENT", + "INQUOTEDSTRING", +}; + +/** Lex State array. */ +public static final int[] jjnewLexState = { + -1, -1, -1, -1, 1, 0, -1, 2, -1, -1, -1, -1, -1, 3, -1, -1, 0, -1, -1, -1, -1, +}; +static final long[] jjtoToken = { + 0x70007L, +}; +static final long[] jjtoSkip = { + 0x28L, +}; +static final long[] jjtoSpecial = { + 0x8L, +}; +static final long[] jjtoMore = { + 0xffd0L, +}; +protected SimpleCharStream input_stream; +private final int[] jjrounds = new int[3]; +private final int[] jjstateSet = new int[6]; +private final StringBuilder jjimage = new StringBuilder(); +private StringBuilder image = jjimage; +private int jjimageLen; +private int lengthOfMatch; +protected char curChar; +/** Constructor. */ +public MimeVersionParserTokenManager(SimpleCharStream stream){ + if (SimpleCharStream.staticFlag) + throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer."); + input_stream = stream; +} + +/** Constructor. */ +public MimeVersionParserTokenManager(SimpleCharStream stream, int lexState){ + this(stream); + SwitchTo(lexState); +} + +/** Reinitialise parser. */ +public void ReInit(SimpleCharStream stream) +{ + jjmatchedPos = jjnewStateCnt = 0; + curLexState = defaultLexState; + input_stream = stream; + ReInitRounds(); +} +private void ReInitRounds() +{ + int i; + jjround = 0x80000001; + for (i = 3; i-- > 0;) + jjrounds[i] = 0x80000000; +} + +/** Reinitialise parser. */ +public void ReInit(SimpleCharStream stream, int lexState) +{ + ReInit(stream); + SwitchTo(lexState); +} + +/** Switch to specified lex state. */ +public void SwitchTo(int lexState) +{ + if (lexState >= 4 || lexState < 0) + throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE); + else + curLexState = lexState; +} + +protected Token jjFillToken() +{ + final Token t; + final String curTokenImage; + final int beginLine; + final int endLine; + final int beginColumn; + final int endColumn; + String im = jjstrLiteralImages[jjmatchedKind]; + curTokenImage = (im == null) ? input_stream.GetImage() : im; + beginLine = input_stream.getBeginLine(); + beginColumn = input_stream.getBeginColumn(); + endLine = input_stream.getEndLine(); + endColumn = input_stream.getEndColumn(); + t = Token.newToken(jjmatchedKind, curTokenImage); + + t.beginLine = beginLine; + t.endLine = endLine; + t.beginColumn = beginColumn; + t.endColumn = endColumn; + + return t; +} + +int curLexState = 0; +int defaultLexState = 0; +int jjnewStateCnt; +int jjround; +int jjmatchedPos; +int jjmatchedKind; + +/** Get the next Token. */ +public Token getNextToken() +{ + Token specialToken = null; + Token matchedToken; + int curPos = 0; + + EOFLoop : + for (;;) + { + try + { + curChar = input_stream.BeginToken(); + } + catch(java.io.IOException e) + { + jjmatchedKind = 0; + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + return matchedToken; + } + image = jjimage; + image.setLength(0); + jjimageLen = 0; + + for (;;) + { + switch(curLexState) + { + case 0: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_0(); + break; + case 1: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_1(); + break; + case 2: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_2(); + break; + case 3: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_3(); + break; + } + if (jjmatchedKind != 0x7fffffff) + { + if (jjmatchedPos + 1 < curPos) + input_stream.backup(curPos - jjmatchedPos - 1); + if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + TokenLexicalActions(matchedToken); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + return matchedToken; + } + else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + if ((jjtoSpecial[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + if (specialToken == null) + specialToken = matchedToken; + else + { + matchedToken.specialToken = specialToken; + specialToken = (specialToken.next = matchedToken); + } + } + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + continue EOFLoop; + } + MoreLexicalActions(); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + curPos = 0; + jjmatchedKind = 0x7fffffff; + try { + curChar = input_stream.readChar(); + continue; + } + catch (java.io.IOException e1) { } + } + int error_line = input_stream.getEndLine(); + int error_column = input_stream.getEndColumn(); + String error_after = null; + boolean EOFSeen = false; + try { input_stream.readChar(); input_stream.backup(1); } + catch (java.io.IOException e1) { + EOFSeen = true; + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + if (curChar == '\n' || curChar == '\r') { + error_line++; + error_column = 0; + } + else + error_column++; + } + if (!EOFSeen) { + input_stream.backup(1); + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + } + throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR); + } + } +} + +void MoreLexicalActions() +{ + jjimageLen += (lengthOfMatch = jjmatchedPos + 1); + switch(jjmatchedKind) + { + case 6 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 7 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + commentNest = 1; + break; + case 9 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 10 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + ++commentNest; + break; + case 11 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); + break; + case 13 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 1); + break; + case 14 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + default : + break; + } +} +void TokenLexicalActions(Token matchedToken) +{ + switch(jjmatchedKind) + { + case 16 : + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + matchedToken.image = image.substring(0, image.length() - 1); + break; + default : + break; + } +} +private void jjCheckNAdd(int state) +{ + if (jjrounds[state] != jjround) + { + jjstateSet[jjnewStateCnt++] = state; + jjrounds[state] = jjround; + } +} +private void jjAddStates(int start, int end) +{ + do { + jjstateSet[jjnewStateCnt++] = jjnextStates[start]; + } while (start++ != end); +} +private void jjCheckNAddTwoStates(int state1, int state2) +{ + jjCheckNAdd(state1); + jjCheckNAdd(state2); +} + +} diff --git a/src/org/apache/james/mime4j/field/mimeversion/parser/ParseException.java b/src/org/apache/james/mime4j/field/mimeversion/parser/ParseException.java new file mode 100644 index 000000000..26cd67905 --- /dev/null +++ b/src/org/apache/james/mime4j/field/mimeversion/parser/ParseException.java @@ -0,0 +1,220 @@ +/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.mimeversion.parser; + +/** + * This exception is thrown when parse errors are encountered. + * You can explicitly create objects of this exception type by + * calling the method generateParseException in the generated + * parser. + * + * Changes for Mime4J: + * extends org.apache.james.mime4j.field.ParseException + * added serialVersionUID + * added constructor ParseException(Throwable) + * default detail message is "Cannot parse field" + */ +public class ParseException extends org.apache.james.mime4j.dom.field.ParseException { + + private static final long serialVersionUID = 1L; + + /** + * This constructor is used by the method "generateParseException" + * in the generated parser. Calling this constructor generates + * a new object of this type with the fields "currentToken", + * "expectedTokenSequences", and "tokenImage" set. The boolean + * flag "specialConstructor" is also set to true to indicate that + * this constructor was used to create this object. + * This constructor calls its super class with the empty string + * to force the "toString" method of parent class "Throwable" to + * print the error message in the form: + * ParseException: + */ + public ParseException(Token currentTokenVal, + int[][] expectedTokenSequencesVal, + String[] tokenImageVal + ) + { + super(""); + specialConstructor = true; + currentToken = currentTokenVal; + expectedTokenSequences = expectedTokenSequencesVal; + tokenImage = tokenImageVal; + } + + /** + * The following constructors are for use by you for whatever + * purpose you can think of. Constructing the exception in this + * manner makes the exception behave in the normal way - i.e., as + * documented in the class "Throwable". The fields "errorToken", + * "expectedTokenSequences", and "tokenImage" do not contain + * relevant information. The JavaCC generated code does not use + * these constructors. + */ + + public ParseException() { + super("Cannot parse field"); + specialConstructor = false; + } + + public ParseException(Throwable cause) { + super(cause); + specialConstructor = false; + } + + public ParseException(String message) { + super(message); + specialConstructor = false; + } + + /** + * This variable determines which constructor was used to create + * this object and thereby affects the semantics of the + * "getMessage" method (see below). + */ + protected boolean specialConstructor; + + /** + * This is the last token that has been consumed successfully. If + * this object has been created due to a parse error, the token + * followng this token will (therefore) be the first error token. + */ + public Token currentToken; + + /** + * Each entry in this array is an array of integers. Each array + * of integers represents a sequence of tokens (by their ordinal + * values) that is expected at this point of the parse. + */ + public int[][] expectedTokenSequences; + + /** + * This is a reference to the "tokenImage" array of the generated + * parser within which the parse error occurred. This array is + * defined in the generated ...Constants interface. + */ + public String[] tokenImage; + + /** + * This method has the standard behavior when this object has been + * created using the standard constructors. Otherwise, it uses + * "currentToken" and "expectedTokenSequences" to generate a parse + * error message and returns it. If this object has been created + * due to a parse error, and you do not catch it (it gets thrown + * from the parser), then this method is called during the printing + * of the final stack trace, and hence the correct error message + * gets displayed. + */ + public String getMessage() { + if (!specialConstructor) { + return super.getMessage(); + } + StringBuffer expected = new StringBuffer(); + int maxSize = 0; + for (int i = 0; i < expectedTokenSequences.length; i++) { + if (maxSize < expectedTokenSequences[i].length) { + maxSize = expectedTokenSequences[i].length; + } + for (int j = 0; j < expectedTokenSequences[i].length; j++) { + expected.append(tokenImage[expectedTokenSequences[i][j]]).append(" "); + } + if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) { + expected.append("..."); + } + expected.append(eol).append(" "); + } + String retval = "Encountered \""; + Token tok = currentToken.next; + for (int i = 0; i < maxSize; i++) { + if (i != 0) retval += " "; + if (tok.kind == 0) { + retval += tokenImage[0]; + break; + } + retval += add_escapes(tok.image); + tok = tok.next; + } + retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn; + retval += "." + eol; + if (expectedTokenSequences.length == 1) { + retval += "Was expecting:" + eol + " "; + } else { + retval += "Was expecting one of:" + eol + " "; + } + retval += expected.toString(); + return retval; + } + + /** + * The end of line string for this machine. + */ + protected String eol = System.getProperty("line.separator", "\n"); + + /** + * Used to convert raw characters to their escaped version + * when these raw version cannot be used as part of an ASCII + * string literal. + */ + protected String add_escapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + +} diff --git a/src/org/apache/james/mime4j/field/mimeversion/parser/SimpleCharStream.java b/src/org/apache/james/mime4j/field/mimeversion/parser/SimpleCharStream.java new file mode 100644 index 000000000..df8cbfd92 --- /dev/null +++ b/src/org/apache/james/mime4j/field/mimeversion/parser/SimpleCharStream.java @@ -0,0 +1,489 @@ +/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 5.0 */ +/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.mimeversion.parser; + +/** + * An implementation of interface CharStream, where the stream is assumed to + * contain only ASCII characters (without unicode processing). + */ + +public class SimpleCharStream +{ +/** Whether parser is static. */ + public static final boolean staticFlag = false; + int bufsize; + int available; + int tokenBegin; +/** Position in buffer. */ + public int bufpos = -1; + protected int bufline[]; + protected int bufcolumn[]; + + protected int column = 0; + protected int line = 1; + + protected boolean prevCharIsCR = false; + protected boolean prevCharIsLF = false; + + protected java.io.Reader inputStream; + + protected char[] buffer; + protected int maxNextCharInd = 0; + protected int inBuf = 0; + protected int tabSize = 8; + + protected void setTabSize(int i) { tabSize = i; } + protected int getTabSize(int i) { return tabSize; } + + + protected void ExpandBuff(boolean wrapAround) + { + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; + + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } + + + bufsize += 2048; + available = bufsize; + tokenBegin = 0; + } + + protected void FillBuff() throws java.io.IOException + { + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) + { + bufpos = maxNextCharInd = 0; + available = tokenBegin; + } + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; + else + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } + + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } + } + +/** Start. */ + public char BeginToken() throws java.io.IOException + { + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; + + return c; + } + + protected void UpdateLineColumn(char c) + { + column++; + + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else + line += (column = 1); + } + + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (tabSize - (column % tabSize)); + break; + default : + break; + } + + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + +/** Read a character. */ + public char readChar() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + if (++bufpos == bufsize) + bufpos = 0; + + return buffer[bufpos]; + } + + if (++bufpos >= maxNextCharInd) + FillBuff(); + + char c = buffer[bufpos]; + + UpdateLineColumn(c); + return c; + } + + @Deprecated + /** + * @deprecated + * @see #getEndColumn + */ + + public int getColumn() { + return bufcolumn[bufpos]; + } + + @Deprecated + /** + * @deprecated + * @see #getEndLine + */ + + public int getLine() { + return bufline[bufpos]; + } + + /** Get token end column number. */ + public int getEndColumn() { + return bufcolumn[bufpos]; + } + + /** Get token end line number. */ + public int getEndLine() { + return bufline[bufpos]; + } + + /** Get token beginning column number. */ + public int getBeginColumn() { + return bufcolumn[tokenBegin]; + } + + /** Get token beginning line number. */ + public int getBeginLine() { + return bufline[tokenBegin]; + } + +/** Backup a number of characters. */ + public void backup(int amount) { + + inBuf += amount; + if ((bufpos -= amount) < 0) + bufpos += bufsize; + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream) + { + this(dstream, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + if (buffer == null || buffersize != buffer.length) + { + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + prevCharIsLF = prevCharIsCR = false; + tokenBegin = inBuf = maxNextCharInd = 0; + bufpos = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream) + { + ReInit(dstream, 1, 1, 4096); + } + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, 1, 1, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream) + { + this(dstream, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream) + { + ReInit(dstream, 1, 1, 4096); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, startline, startcolumn, 4096); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + /** Get token literal value. */ + public String GetImage() + { + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); + } + + /** Get the suffix. */ + public char[] GetSuffix(int len) + { + char[] ret = new char[len]; + + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } + + return ret; + } + + /** Reset buffer when finished. */ + public void Done() + { + buffer = null; + bufline = null; + bufcolumn = null; + } + + /** + * Method to adjust line and column numbers for the start of a token. + */ + public void adjustBeginLineColumn(int newLine, int newCol) + { + int start = tokenBegin; + int len; + + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } + + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; + + while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } + + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; + + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } + + line = bufline[j]; + column = bufcolumn[j]; + } + +} +/* JavaCC - OriginalChecksum=2f0f9f97043c831084564dffb8cd0588 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/mimeversion/parser/Token.java b/src/org/apache/james/mime4j/field/mimeversion/parser/Token.java new file mode 100644 index 000000000..c9a6c28ad --- /dev/null +++ b/src/org/apache/james/mime4j/field/mimeversion/parser/Token.java @@ -0,0 +1,149 @@ +/* Generated By:JavaCC: Do not edit this line. Token.java Version 5.0 */ +/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.mimeversion.parser; + +/** + * Describes the input token stream. + */ + +public class Token implements java.io.Serializable { + + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; + + /** + * An integer that describes the kind of this token. This numbering + * system is determined by JavaCCParser, and a table of these numbers is + * stored in the file ...Constants.java. + */ + public int kind; + + /** The line number of the first character of this Token. */ + public int beginLine; + /** The column number of the first character of this Token. */ + public int beginColumn; + /** The line number of the last character of this Token. */ + public int endLine; + /** The column number of the last character of this Token. */ + public int endColumn; + + /** + * The string image of the token. + */ + public String image; + + /** + * A reference to the next regular (non-special) token from the input + * stream. If this is the last token from the input stream, or if the + * token manager has not read tokens beyond this one, this field is + * set to null. This is true only if this token is also a regular + * token. Otherwise, see below for a description of the contents of + * this field. + */ + public Token next; + + /** + * This field is used to access special tokens that occur prior to this + * token, but after the immediately preceding regular (non-special) token. + * If there are no such special tokens, this field is set to null. + * When there are more than one such special token, this field refers + * to the last of these special tokens, which in turn refers to the next + * previous special token through its specialToken field, and so on + * until the first special token (whose specialToken field is null). + * The next fields of special tokens refer to other special tokens that + * immediately follow it (without an intervening regular token). If there + * is no such token, this field is null. + */ + public Token specialToken; + + /** + * An optional attribute value of the Token. + * Tokens which are not used as syntactic sugar will often contain + * meaningful values that will be used later on by the compiler or + * interpreter. This attribute value is often different from the image. + * Any subclass of Token that actually wants to return a non-null value can + * override this method as appropriate. + */ + public Object getValue() { + return null; + } + + /** + * No-argument constructor + */ + public Token() {} + + /** + * Constructs a new token for the specified Image. + */ + public Token(int kind) + { + this(kind, null); + } + + /** + * Constructs a new token for the specified Image and Kind. + */ + public Token(int kind, String image) + { + this.kind = kind; + this.image = image; + } + + /** + * Returns the image. + */ + public String toString() + { + return image; + } + + /** + * Returns a new Token object, by default. However, if you want, you + * can create and return subclass objects based on the value of ofKind. + * Simply add the cases to the switch for all those special cases. + * For example, if you have a subclass of Token called IDToken that + * you want to create if ofKind is ID, simply add something like : + * + * case MyParserConstants.ID : return new IDToken(ofKind, image); + * + * to the following switch statement. Then you can cast matchedToken + * variable to the appropriate type and use sit in your lexical actions. + */ + public static Token newToken(int ofKind, String image) + { + switch(ofKind) + { + default : return new Token(ofKind, image); + } + } + + public static Token newToken(int ofKind) + { + return newToken(ofKind, null); + } + +} +/* JavaCC - OriginalChecksum=b8c5500461d21fb1e0fa5ff0e19aed83 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/mimeversion/parser/TokenMgrError.java b/src/org/apache/james/mime4j/field/mimeversion/parser/TokenMgrError.java new file mode 100644 index 000000000..aeaa40c65 --- /dev/null +++ b/src/org/apache/james/mime4j/field/mimeversion/parser/TokenMgrError.java @@ -0,0 +1,165 @@ +/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */ +/* JavaCCOptions: */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.mimeversion.parser; + +/** Token Manager Error. */ +public class TokenMgrError extends Error +{ + + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; + + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ + + /** + * Lexical error occurred. + */ + static final int LEXICAL_ERROR = 0; + + /** + * An attempt was made to create a second instance of a static token manager. + */ + static final int STATIC_LEXER_ERROR = 1; + + /** + * Tried to change to an invalid lexical state. + */ + static final int INVALID_LEXICAL_STATE = 2; + + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + static final int LOOP_DETECTED = 3; + + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + int errorCode; + + /** + * Replaces unprintable characters by their escaped (or unicode escaped) + * equivalents in the given string + */ + protected static final String addEscapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * Parameters : + * EOFSeen : indicates if EOF caused the lexical error + * curLexState : lexical state in which this error occurred + * errorLine : line number when the error occurred + * errorColumn : column number when the error occurred + * errorAfter : prefix that was seen before this error occurred + * curchar : the offending character + * Note: You can customize the lexical error message by modifying this method. + */ + protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { + return("Lexical error at line " + + errorLine + ", column " + + errorColumn + ". Encountered: " + + (EOFSeen ? " " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + + "after : \"" + addEscapes(errorAfter) + "\""); + } + + /** + * You can also modify the body of this method to customize your error messages. + * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not + * of end-users concern, so you can return something like : + * + * "Internal Error : Please file a bug report .... " + * + * from this method for such cases in the release version of your parser. + */ + public String getMessage() { + return super.getMessage(); + } + + /* + * Constructors of various flavors follow. + */ + + /** No arg constructor. */ + public TokenMgrError() { + } + + /** Constructor with message and reason. */ + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } + + /** Full Constructor. */ + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); + } +} +/* JavaCC - OriginalChecksum=a2a21b7ea9b5401d9f73daabaea160b4 (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/structured/parser/ParseException.java b/src/org/apache/james/mime4j/field/structured/parser/ParseException.java new file mode 100644 index 000000000..bbdb642b8 --- /dev/null +++ b/src/org/apache/james/mime4j/field/structured/parser/ParseException.java @@ -0,0 +1,220 @@ +/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.structured.parser; + +/** + * This exception is thrown when parse errors are encountered. + * You can explicitly create objects of this exception type by + * calling the method generateParseException in the generated + * parser. + * + * Changes for Mime4J: + * extends org.apache.james.mime4j.field.ParseException + * added serialVersionUID + * added constructor ParseException(Throwable) + * default detail message is "Cannot parse field" + */ +public class ParseException extends org.apache.james.mime4j.dom.field.ParseException { + + private static final long serialVersionUID = 1L; + + /** + * This constructor is used by the method "generateParseException" + * in the generated parser. Calling this constructor generates + * a new object of this type with the fields "currentToken", + * "expectedTokenSequences", and "tokenImage" set. The boolean + * flag "specialConstructor" is also set to true to indicate that + * this constructor was used to create this object. + * This constructor calls its super class with the empty string + * to force the "toString" method of parent class "Throwable" to + * print the error message in the form: + * ParseException: + */ + public ParseException(Token currentTokenVal, + int[][] expectedTokenSequencesVal, + String[] tokenImageVal + ) + { + super(""); + specialConstructor = true; + currentToken = currentTokenVal; + expectedTokenSequences = expectedTokenSequencesVal; + tokenImage = tokenImageVal; + } + + /** + * The following constructors are for use by you for whatever + * purpose you can think of. Constructing the exception in this + * manner makes the exception behave in the normal way - i.e., as + * documented in the class "Throwable". The fields "errorToken", + * "expectedTokenSequences", and "tokenImage" do not contain + * relevant information. The JavaCC generated code does not use + * these constructors. + */ + + public ParseException() { + super("Cannot parse field"); + specialConstructor = false; + } + + public ParseException(Throwable cause) { + super(cause); + specialConstructor = false; + } + + public ParseException(String message) { + super(message); + specialConstructor = false; + } + + /** + * This variable determines which constructor was used to create + * this object and thereby affects the semantics of the + * "getMessage" method (see below). + */ + protected boolean specialConstructor; + + /** + * This is the last token that has been consumed successfully. If + * this object has been created due to a parse error, the token + * followng this token will (therefore) be the first error token. + */ + public Token currentToken; + + /** + * Each entry in this array is an array of integers. Each array + * of integers represents a sequence of tokens (by their ordinal + * values) that is expected at this point of the parse. + */ + public int[][] expectedTokenSequences; + + /** + * This is a reference to the "tokenImage" array of the generated + * parser within which the parse error occurred. This array is + * defined in the generated ...Constants interface. + */ + public String[] tokenImage; + + /** + * This method has the standard behavior when this object has been + * created using the standard constructors. Otherwise, it uses + * "currentToken" and "expectedTokenSequences" to generate a parse + * error message and returns it. If this object has been created + * due to a parse error, and you do not catch it (it gets thrown + * from the parser), then this method is called during the printing + * of the final stack trace, and hence the correct error message + * gets displayed. + */ + public String getMessage() { + if (!specialConstructor) { + return super.getMessage(); + } + StringBuffer expected = new StringBuffer(); + int maxSize = 0; + for (int i = 0; i < expectedTokenSequences.length; i++) { + if (maxSize < expectedTokenSequences[i].length) { + maxSize = expectedTokenSequences[i].length; + } + for (int j = 0; j < expectedTokenSequences[i].length; j++) { + expected.append(tokenImage[expectedTokenSequences[i][j]]).append(" "); + } + if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) { + expected.append("..."); + } + expected.append(eol).append(" "); + } + String retval = "Encountered \""; + Token tok = currentToken.next; + for (int i = 0; i < maxSize; i++) { + if (i != 0) retval += " "; + if (tok.kind == 0) { + retval += tokenImage[0]; + break; + } + retval += add_escapes(tok.image); + tok = tok.next; + } + retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn; + retval += "." + eol; + if (expectedTokenSequences.length == 1) { + retval += "Was expecting:" + eol + " "; + } else { + retval += "Was expecting one of:" + eol + " "; + } + retval += expected.toString(); + return retval; + } + + /** + * The end of line string for this machine. + */ + protected String eol = System.getProperty("line.separator", "\n"); + + /** + * Used to convert raw characters to their escaped version + * when these raw version cannot be used as part of an ASCII + * string literal. + */ + protected String add_escapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + +} diff --git a/src/org/apache/james/mime4j/field/structured/parser/SimpleCharStream.java b/src/org/apache/james/mime4j/field/structured/parser/SimpleCharStream.java new file mode 100644 index 000000000..6e340c96b --- /dev/null +++ b/src/org/apache/james/mime4j/field/structured/parser/SimpleCharStream.java @@ -0,0 +1,489 @@ +/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 5.0 */ +/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.structured.parser; + +/** + * An implementation of interface CharStream, where the stream is assumed to + * contain only ASCII characters (without unicode processing). + */ + +public class SimpleCharStream +{ +/** Whether parser is static. */ + public static final boolean staticFlag = false; + int bufsize; + int available; + int tokenBegin; +/** Position in buffer. */ + public int bufpos = -1; + protected int bufline[]; + protected int bufcolumn[]; + + protected int column = 0; + protected int line = 1; + + protected boolean prevCharIsCR = false; + protected boolean prevCharIsLF = false; + + protected java.io.Reader inputStream; + + protected char[] buffer; + protected int maxNextCharInd = 0; + protected int inBuf = 0; + protected int tabSize = 8; + + protected void setTabSize(int i) { tabSize = i; } + protected int getTabSize(int i) { return tabSize; } + + + protected void ExpandBuff(boolean wrapAround) + { + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; + + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } + + + bufsize += 2048; + available = bufsize; + tokenBegin = 0; + } + + protected void FillBuff() throws java.io.IOException + { + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) + { + bufpos = maxNextCharInd = 0; + available = tokenBegin; + } + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; + else + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } + + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } + } + +/** Start. */ + public char BeginToken() throws java.io.IOException + { + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; + + return c; + } + + protected void UpdateLineColumn(char c) + { + column++; + + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else + line += (column = 1); + } + + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (tabSize - (column % tabSize)); + break; + default : + break; + } + + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + +/** Read a character. */ + public char readChar() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + if (++bufpos == bufsize) + bufpos = 0; + + return buffer[bufpos]; + } + + if (++bufpos >= maxNextCharInd) + FillBuff(); + + char c = buffer[bufpos]; + + UpdateLineColumn(c); + return c; + } + + @Deprecated + /** + * @deprecated + * @see #getEndColumn + */ + + public int getColumn() { + return bufcolumn[bufpos]; + } + + @Deprecated + /** + * @deprecated + * @see #getEndLine + */ + + public int getLine() { + return bufline[bufpos]; + } + + /** Get token end column number. */ + public int getEndColumn() { + return bufcolumn[bufpos]; + } + + /** Get token end line number. */ + public int getEndLine() { + return bufline[bufpos]; + } + + /** Get token beginning column number. */ + public int getBeginColumn() { + return bufcolumn[tokenBegin]; + } + + /** Get token beginning line number. */ + public int getBeginLine() { + return bufline[tokenBegin]; + } + +/** Backup a number of characters. */ + public void backup(int amount) { + + inBuf += amount; + if ((bufpos -= amount) < 0) + bufpos += bufsize; + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.Reader dstream) + { + this(dstream, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + if (buffer == null || buffersize != buffer.length) + { + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + prevCharIsLF = prevCharIsCR = false; + tokenBegin = inBuf = maxNextCharInd = 0; + bufpos = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader dstream) + { + ReInit(dstream, 1, 1, 4096); + } + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, 1, 1, 4096); + } + + /** Constructor. */ + public SimpleCharStream(java.io.InputStream dstream) + { + this(dstream, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, 1, 1, 4096); + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream) + { + ReInit(dstream, 1, 1, 4096); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, startline, startcolumn, 4096); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + /** Get token literal value. */ + public String GetImage() + { + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); + } + + /** Get the suffix. */ + public char[] GetSuffix(int len) + { + char[] ret = new char[len]; + + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } + + return ret; + } + + /** Reset buffer when finished. */ + public void Done() + { + buffer = null; + bufline = null; + bufcolumn = null; + } + + /** + * Method to adjust line and column numbers for the start of a token. + */ + public void adjustBeginLineColumn(int newLine, int newCol) + { + int start = tokenBegin; + int len; + + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } + + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; + + while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } + + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; + + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } + + line = bufline[j]; + column = bufcolumn[j]; + } + +} +/* JavaCC - OriginalChecksum=33b0886501084acf0a503d419221290d (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/structured/parser/StructuredFieldParser.java b/src/org/apache/james/mime4j/field/structured/parser/StructuredFieldParser.java new file mode 100644 index 000000000..7d7a166db --- /dev/null +++ b/src/org/apache/james/mime4j/field/structured/parser/StructuredFieldParser.java @@ -0,0 +1,289 @@ +/* Generated By:JavaCC: Do not edit this line. StructuredFieldParser.java */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.structured.parser; + +/** + * Parses generic structure fields. + * Unfolds and removes comments. + */ +public class StructuredFieldParser implements StructuredFieldParserConstants { + + private boolean preserveFolding = false; + + /** + * Should the \r\n folding sequence be preserved? + */ + public boolean isFoldingPreserved() { + return preserveFolding; + } + + /** + * Sets whether the \r\n folding sequence should be preserved. + */ + public void setFoldingPreserved(boolean preserveFolding) { + this.preserveFolding = preserveFolding; + } + + /** + * Unfolds the input and removes comments. + * @return unfolded header value with comments removed + */ + public String parse() throws ParseException { + try { + return doParse(); + } catch (TokenMgrError e) { + // An issue with the TOKENiser + // but it's not polite to throw an Error + // when executing on a server + throw new ParseException(e); + } + } + + final private String doParse() throws ParseException { + Token t; + StringBuffer buffer = new StringBuffer(50); + boolean whitespace = false; + boolean first = true; + label_1: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case STRING_CONTENT: + case FOLD: + case QUOTEDSTRING: + case WS: + case CONTENT: + ; + break; + default: + jj_la1[0] = jj_gen; + break label_1; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CONTENT: + t = jj_consume_token(CONTENT); + if (first) { + first = false; + } else if (whitespace) { + buffer.append(" "); + whitespace = false; + } + buffer.append(t.image); + break; + case STRING_CONTENT: + t = jj_consume_token(STRING_CONTENT); + buffer.append(t.image); + break; + case QUOTEDSTRING: + t = jj_consume_token(QUOTEDSTRING); + if (first) { + first = false; + } else if (whitespace) { + buffer.append(" "); + whitespace = false; + } + buffer.append(t.image); + break; + case FOLD: + t = jj_consume_token(FOLD); + if (preserveFolding) buffer.append("\u005cr\u005cn"); + break; + case WS: + t = jj_consume_token(WS); + whitespace = true; + break; + default: + jj_la1[1] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + {if (true) return buffer.toString();} + throw new Error("Missing return statement in function"); + } + + /** Generated Token Manager. */ + public StructuredFieldParserTokenManager token_source; + SimpleCharStream jj_input_stream; + /** Current token. */ + public Token token; + /** Next token. */ + public Token jj_nt; + private int jj_ntk; + private int jj_gen; + final private int[] jj_la1 = new int[2]; + static private int[] jj_la1_0; + static { + jj_la1_init_0(); + } + private static void jj_la1_init_0() { + jj_la1_0 = new int[] {0xf800,0xf800,}; + } + + /** Constructor with InputStream. */ + public StructuredFieldParser(java.io.InputStream stream) { + this(stream, null); + } + /** Constructor with InputStream and supplied encoding */ + public StructuredFieldParser(java.io.InputStream stream, String encoding) { + try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source = new StructuredFieldParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 2; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.InputStream stream) { + ReInit(stream, null); + } + /** Reinitialise. */ + public void ReInit(java.io.InputStream stream, String encoding) { + try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 2; i++) jj_la1[i] = -1; + } + + /** Constructor. */ + public StructuredFieldParser(java.io.Reader stream) { + jj_input_stream = new SimpleCharStream(stream, 1, 1); + token_source = new StructuredFieldParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 2; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(java.io.Reader stream) { + jj_input_stream.ReInit(stream, 1, 1); + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 2; i++) jj_la1[i] = -1; + } + + /** Constructor with generated Token Manager. */ + public StructuredFieldParser(StructuredFieldParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 2; i++) jj_la1[i] = -1; + } + + /** Reinitialise. */ + public void ReInit(StructuredFieldParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 2; i++) jj_la1[i] = -1; + } + + private Token jj_consume_token(int kind) throws ParseException { + Token oldToken; + if ((oldToken = token).next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + if (token.kind == kind) { + jj_gen++; + return token; + } + token = oldToken; + jj_kind = kind; + throw generateParseException(); + } + + +/** Get the next Token. */ + final public Token getNextToken() { + if (token.next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + jj_gen++; + return token; + } + +/** Get the specific Token. */ + final public Token getToken(int index) { + Token t = token; + for (int i = 0; i < index; i++) { + if (t.next != null) t = t.next; + else t = t.next = token_source.getNextToken(); + } + return t; + } + + private int jj_ntk() { + if ((jj_nt=token.next) == null) + return (jj_ntk = (token.next=token_source.getNextToken()).kind); + else + return (jj_ntk = jj_nt.kind); + } + + private java.util.List jj_expentries = new java.util.ArrayList(); + private int[] jj_expentry; + private int jj_kind = -1; + + /** Generate ParseException. */ + public ParseException generateParseException() { + jj_expentries.clear(); + boolean[] la1tokens = new boolean[18]; + if (jj_kind >= 0) { + la1tokens[jj_kind] = true; + jj_kind = -1; + } + for (int i = 0; i < 2; i++) { + if (jj_la1[i] == jj_gen) { + for (int j = 0; j < 32; j++) { + if ((jj_la1_0[i] & (1<", + "\"(\"", + "\")\"", + "\"(\"", + "", + "\"(\"", + "\")\"", + "", + "", + "\"\\\"\"", + "", + "", + "", + "\"\\\"\"", + "", + "", + "", + "", + }; + +} diff --git a/src/org/apache/james/mime4j/field/structured/parser/StructuredFieldParserTokenManager.java b/src/org/apache/james/mime4j/field/structured/parser/StructuredFieldParserTokenManager.java new file mode 100644 index 000000000..7b3cfa38f --- /dev/null +++ b/src/org/apache/james/mime4j/field/structured/parser/StructuredFieldParserTokenManager.java @@ -0,0 +1,819 @@ +/* Generated By:JavaCC: Do not edit this line. StructuredFieldParserTokenManager.java */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.structured.parser; + +/** Token Manager. */ +public class StructuredFieldParserTokenManager implements StructuredFieldParserConstants +{ + // Keeps track of how many levels of comment nesting + // we've encountered. This is only used when the 2nd + // level is reached, for example ((this)), not (this). + // This is because the outermost level must be treated + // specially anyway, because the outermost ")" has a + // different token type than inner ")" instances. + int commentNest; + + /** Debug output. */ + public java.io.PrintStream debugStream = System.out; + /** Set debug output. */ + public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; } +private final int jjStopStringLiteralDfa_0(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_0(int pos, long active0) +{ + return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1); +} +private int jjStopAtPos(int pos, int kind) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + return pos + 1; +} +private int jjMoveStringLiteralDfa0_0() +{ + switch(curChar) + { + case 34: + return jjStopAtPos(0, 9); + case 40: + return jjStopAtPos(0, 1); + default : + return jjMoveNfa_0(2, 0); + } +} +static final long[] jjbitVec0 = { + 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL +}; +private int jjMoveNfa_0(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 2; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 2: + if ((0xfffffefaffffd9ffL & l) != 0L) + { + if (kind > 15) + kind = 15; + jjCheckNAdd(1); + } + else if ((0x100002600L & l) != 0L) + { + if (kind > 14) + kind = 14; + jjCheckNAdd(0); + } + break; + case 0: + if ((0x100002600L & l) == 0L) + break; + kind = 14; + jjCheckNAdd(0); + break; + case 1: + if ((0xfffffefaffffd9ffL & l) == 0L) + break; + kind = 15; + jjCheckNAdd(1); + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 2: + case 1: + kind = 15; + jjCheckNAdd(1); + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 2: + case 1: + if ((jjbitVec0[i2] & l2) == 0L) + break; + if (kind > 15) + kind = 15; + jjCheckNAdd(1); + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 2 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_1(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_1(int pos, long active0) +{ + return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_1() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 3); + case 41: + return jjStopAtPos(0, 2); + default : + return jjMoveNfa_1(0, 0); + } +} +private int jjMoveNfa_1(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 1; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0xfffffcffffffffffL & l) != 0L) + kind = 4; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + kind = 4; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 4) + kind = 4; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 1 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_3(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_3(int pos, long active0) +{ + return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_3() +{ + switch(curChar) + { + case 34: + return jjStopAtPos(0, 13); + default : + return jjMoveNfa_3(0, 0); + } +} +private int jjMoveNfa_3(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 6; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0xfffffffbffffdfffL & l) != 0L) + { + if (kind > 11) + kind = 11; + jjCheckNAdd(2); + } + else if (curChar == 13) + jjstateSet[jjnewStateCnt++] = 3; + break; + case 1: + if (kind > 10) + kind = 10; + jjstateSet[jjnewStateCnt++] = 1; + break; + case 2: + if ((0xfffffffbffffdfffL & l) == 0L) + break; + if (kind > 11) + kind = 11; + jjCheckNAdd(2); + break; + case 3: + if (curChar != 10) + break; + if (kind > 12) + kind = 12; + jjCheckNAdd(4); + break; + case 4: + if ((0x100000200L & l) == 0L) + break; + if (kind > 12) + kind = 12; + jjCheckNAdd(4); + break; + case 5: + if (curChar == 13) + jjstateSet[jjnewStateCnt++] = 3; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0xffffffffefffffffL & l) != 0L) + { + if (kind > 11) + kind = 11; + jjCheckNAdd(2); + } + else if (curChar == 92) + jjCheckNAdd(1); + break; + case 1: + if (kind > 10) + kind = 10; + jjCheckNAdd(1); + break; + case 2: + if ((0xffffffffefffffffL & l) == 0L) + break; + if (kind > 11) + kind = 11; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + case 2: + if ((jjbitVec0[i2] & l2) == 0L) + break; + if (kind > 11) + kind = 11; + jjCheckNAdd(2); + break; + case 1: + if ((jjbitVec0[i2] & l2) == 0L) + break; + if (kind > 10) + kind = 10; + jjstateSet[jjnewStateCnt++] = 1; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 6 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_2(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_2(int pos, long active0) +{ + return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1); +} +private int jjMoveStringLiteralDfa0_2() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 5); + case 41: + return jjStopAtPos(0, 6); + default : + return jjMoveNfa_2(0, 0); + } +} +private int jjMoveNfa_2(int startState, int curPos) +{ + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0xfffffcffffffffffL & l) != 0L && kind > 8) + kind = 8; + break; + case 1: + if (kind > 7) + kind = 7; + jjstateSet[jjnewStateCnt++] = 1; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 8) + kind = 8; + if (curChar == 92) + jjCheckNAdd(1); + break; + case 1: + if (kind > 7) + kind = 7; + jjCheckNAdd(1); + break; + case 2: + if (kind > 8) + kind = 8; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 8) + kind = 8; + break; + case 1: + if ((jjbitVec0[i2] & l2) == 0L) + break; + if (kind > 7) + kind = 7; + jjstateSet[jjnewStateCnt++] = 1; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +static final int[] jjnextStates = { +}; + +/** Token literal values. */ +public static final String[] jjstrLiteralImages = { +"", null, null, null, null, null, null, null, null, null, null, null, null, +null, null, null, null, null, }; + +/** Lexer state names. */ +public static final String[] lexStateNames = { + "DEFAULT", + "INCOMMENT", + "NESTED_COMMENT", + "INQUOTEDSTRING", +}; + +/** Lex State array. */ +public static final int[] jjnewLexState = { + -1, 1, 0, 2, -1, -1, -1, -1, -1, 3, -1, -1, -1, 0, -1, -1, -1, -1, +}; +static final long[] jjtoToken = { + 0xf801L, +}; +static final long[] jjtoSkip = { + 0x3feL, +}; +static final long[] jjtoMore = { + 0x400L, +}; +protected SimpleCharStream input_stream; +private final int[] jjrounds = new int[6]; +private final int[] jjstateSet = new int[12]; +private final StringBuilder jjimage = new StringBuilder(); +private StringBuilder image = jjimage; +private int jjimageLen; +private int lengthOfMatch; +protected char curChar; +/** Constructor. */ +public StructuredFieldParserTokenManager(SimpleCharStream stream){ + if (SimpleCharStream.staticFlag) + throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer."); + input_stream = stream; +} + +/** Constructor. */ +public StructuredFieldParserTokenManager(SimpleCharStream stream, int lexState){ + this(stream); + SwitchTo(lexState); +} + +/** Reinitialise parser. */ +public void ReInit(SimpleCharStream stream) +{ + jjmatchedPos = jjnewStateCnt = 0; + curLexState = defaultLexState; + input_stream = stream; + ReInitRounds(); +} +private void ReInitRounds() +{ + int i; + jjround = 0x80000001; + for (i = 6; i-- > 0;) + jjrounds[i] = 0x80000000; +} + +/** Reinitialise parser. */ +public void ReInit(SimpleCharStream stream, int lexState) +{ + ReInit(stream); + SwitchTo(lexState); +} + +/** Switch to specified lex state. */ +public void SwitchTo(int lexState) +{ + if (lexState >= 4 || lexState < 0) + throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE); + else + curLexState = lexState; +} + +protected Token jjFillToken() +{ + final Token t; + final String curTokenImage; + final int beginLine; + final int endLine; + final int beginColumn; + final int endColumn; + String im = jjstrLiteralImages[jjmatchedKind]; + curTokenImage = (im == null) ? input_stream.GetImage() : im; + beginLine = input_stream.getBeginLine(); + beginColumn = input_stream.getBeginColumn(); + endLine = input_stream.getEndLine(); + endColumn = input_stream.getEndColumn(); + t = Token.newToken(jjmatchedKind, curTokenImage); + + t.beginLine = beginLine; + t.endLine = endLine; + t.beginColumn = beginColumn; + t.endColumn = endColumn; + + return t; +} + +int curLexState = 0; +int defaultLexState = 0; +int jjnewStateCnt; +int jjround; +int jjmatchedPos; +int jjmatchedKind; + +/** Get the next Token. */ +public Token getNextToken() +{ + Token matchedToken; + int curPos = 0; + + EOFLoop : + for (;;) + { + try + { + curChar = input_stream.BeginToken(); + } + catch(java.io.IOException e) + { + jjmatchedKind = 0; + matchedToken = jjFillToken(); + return matchedToken; + } + image = jjimage; + image.setLength(0); + jjimageLen = 0; + + for (;;) + { + switch(curLexState) + { + case 0: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_0(); + break; + case 1: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_1(); + break; + case 2: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_2(); + break; + case 3: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_3(); + break; + } + if (jjmatchedKind != 0x7fffffff) + { + if (jjmatchedPos + 1 < curPos) + input_stream.backup(curPos - jjmatchedPos - 1); + if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + TokenLexicalActions(matchedToken); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + return matchedToken; + } + else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + SkipLexicalActions(null); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + continue EOFLoop; + } + MoreLexicalActions(); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + curPos = 0; + jjmatchedKind = 0x7fffffff; + try { + curChar = input_stream.readChar(); + continue; + } + catch (java.io.IOException e1) { } + } + int error_line = input_stream.getEndLine(); + int error_column = input_stream.getEndColumn(); + String error_after = null; + boolean EOFSeen = false; + try { input_stream.readChar(); input_stream.backup(1); } + catch (java.io.IOException e1) { + EOFSeen = true; + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + if (curChar == '\n' || curChar == '\r') { + error_line++; + error_column = 0; + } + else + error_column++; + } + if (!EOFSeen) { + input_stream.backup(1); + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + } + throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR); + } + } +} + +void SkipLexicalActions(Token matchedToken) +{ + switch(jjmatchedKind) + { + case 3 : + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + commentNest = 1; + break; + case 5 : + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + ++commentNest; + break; + case 6 : + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); + break; + case 7 : + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + image.deleteCharAt(image.length() - 2); + break; + default : + break; + } +} +void MoreLexicalActions() +{ + jjimageLen += (lengthOfMatch = jjmatchedPos + 1); + switch(jjmatchedKind) + { + case 10 : + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + default : + break; + } +} +void TokenLexicalActions(Token matchedToken) +{ + switch(jjmatchedKind) + { + case 13 : + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + matchedToken.image = image.substring(0, image.length() - 1); + break; + default : + break; + } +} +private void jjCheckNAdd(int state) +{ + if (jjrounds[state] != jjround) + { + jjstateSet[jjnewStateCnt++] = state; + jjrounds[state] = jjround; + } +} +private void jjAddStates(int start, int end) +{ + do { + jjstateSet[jjnewStateCnt++] = jjnextStates[start]; + } while (start++ != end); +} +private void jjCheckNAddTwoStates(int state1, int state2) +{ + jjCheckNAdd(state1); + jjCheckNAdd(state2); +} + +} diff --git a/src/org/apache/james/mime4j/field/structured/parser/Token.java b/src/org/apache/james/mime4j/field/structured/parser/Token.java new file mode 100644 index 000000000..efdb4ea84 --- /dev/null +++ b/src/org/apache/james/mime4j/field/structured/parser/Token.java @@ -0,0 +1,149 @@ +/* Generated By:JavaCC: Do not edit this line. Token.java Version 5.0 */ +/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.structured.parser; + +/** + * Describes the input token stream. + */ + +public class Token implements java.io.Serializable { + + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; + + /** + * An integer that describes the kind of this token. This numbering + * system is determined by JavaCCParser, and a table of these numbers is + * stored in the file ...Constants.java. + */ + public int kind; + + /** The line number of the first character of this Token. */ + public int beginLine; + /** The column number of the first character of this Token. */ + public int beginColumn; + /** The line number of the last character of this Token. */ + public int endLine; + /** The column number of the last character of this Token. */ + public int endColumn; + + /** + * The string image of the token. + */ + public String image; + + /** + * A reference to the next regular (non-special) token from the input + * stream. If this is the last token from the input stream, or if the + * token manager has not read tokens beyond this one, this field is + * set to null. This is true only if this token is also a regular + * token. Otherwise, see below for a description of the contents of + * this field. + */ + public Token next; + + /** + * This field is used to access special tokens that occur prior to this + * token, but after the immediately preceding regular (non-special) token. + * If there are no such special tokens, this field is set to null. + * When there are more than one such special token, this field refers + * to the last of these special tokens, which in turn refers to the next + * previous special token through its specialToken field, and so on + * until the first special token (whose specialToken field is null). + * The next fields of special tokens refer to other special tokens that + * immediately follow it (without an intervening regular token). If there + * is no such token, this field is null. + */ + public Token specialToken; + + /** + * An optional attribute value of the Token. + * Tokens which are not used as syntactic sugar will often contain + * meaningful values that will be used later on by the compiler or + * interpreter. This attribute value is often different from the image. + * Any subclass of Token that actually wants to return a non-null value can + * override this method as appropriate. + */ + public Object getValue() { + return null; + } + + /** + * No-argument constructor + */ + public Token() {} + + /** + * Constructs a new token for the specified Image. + */ + public Token(int kind) + { + this(kind, null); + } + + /** + * Constructs a new token for the specified Image and Kind. + */ + public Token(int kind, String image) + { + this.kind = kind; + this.image = image; + } + + /** + * Returns the image. + */ + public String toString() + { + return image; + } + + /** + * Returns a new Token object, by default. However, if you want, you + * can create and return subclass objects based on the value of ofKind. + * Simply add the cases to the switch for all those special cases. + * For example, if you have a subclass of Token called IDToken that + * you want to create if ofKind is ID, simply add something like : + * + * case MyParserConstants.ID : return new IDToken(ofKind, image); + * + * to the following switch statement. Then you can cast matchedToken + * variable to the appropriate type and use sit in your lexical actions. + */ + public static Token newToken(int ofKind, String image) + { + switch(ofKind) + { + default : return new Token(ofKind, image); + } + } + + public static Token newToken(int ofKind) + { + return newToken(ofKind, null); + } + +} +/* JavaCC - OriginalChecksum=c52fe5a569f9eeba4d882a0b29cb132f (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/field/structured/parser/TokenMgrError.java b/src/org/apache/james/mime4j/field/structured/parser/TokenMgrError.java new file mode 100644 index 000000000..43fbd0b7a --- /dev/null +++ b/src/org/apache/james/mime4j/field/structured/parser/TokenMgrError.java @@ -0,0 +1,165 @@ +/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */ +/* JavaCCOptions: */ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.field.structured.parser; + +/** Token Manager Error. */ +public class TokenMgrError extends Error +{ + + /** + * The version identifier for this Serializable class. + * Increment only if the serialized form of the + * class changes. + */ + private static final long serialVersionUID = 1L; + + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ + + /** + * Lexical error occurred. + */ + static final int LEXICAL_ERROR = 0; + + /** + * An attempt was made to create a second instance of a static token manager. + */ + static final int STATIC_LEXER_ERROR = 1; + + /** + * Tried to change to an invalid lexical state. + */ + static final int INVALID_LEXICAL_STATE = 2; + + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + static final int LOOP_DETECTED = 3; + + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + int errorCode; + + /** + * Replaces unprintable characters by their escaped (or unicode escaped) + * equivalents in the given string + */ + protected static final String addEscapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * Parameters : + * EOFSeen : indicates if EOF caused the lexical error + * curLexState : lexical state in which this error occurred + * errorLine : line number when the error occurred + * errorColumn : column number when the error occurred + * errorAfter : prefix that was seen before this error occurred + * curchar : the offending character + * Note: You can customize the lexical error message by modifying this method. + */ + protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { + return("Lexical error at line " + + errorLine + ", column " + + errorColumn + ". Encountered: " + + (EOFSeen ? " " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + + "after : \"" + addEscapes(errorAfter) + "\""); + } + + /** + * You can also modify the body of this method to customize your error messages. + * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not + * of end-users concern, so you can return something like : + * + * "Internal Error : Please file a bug report .... " + * + * from this method for such cases in the release version of your parser. + */ + public String getMessage() { + return super.getMessage(); + } + + /* + * Constructors of various flavors follow. + */ + + /** No arg constructor. */ + public TokenMgrError() { + } + + /** Constructor with message and reason. */ + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } + + /** Full Constructor. */ + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); + } +} +/* JavaCC - OriginalChecksum=da6dd748e79df5704b2a429e94973ebf (do not edit this line) */ diff --git a/src/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java b/src/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java new file mode 100644 index 000000000..fde48ed6c --- /dev/null +++ b/src/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java @@ -0,0 +1,389 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.io; + +import org.apache.james.mime4j.util.ByteArrayBuffer; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Input buffer that can be used to search for patterns using Quick Search + * algorithm in data read from an {@link InputStream}. + */ +public class BufferedLineReaderInputStream extends LineReaderInputStream { + + private boolean truncated; + + boolean tempBuffer = false; + + private byte[] origBuffer; + private int origBufpos; + private int origBuflen; + + private byte[] buffer; + private int bufpos; + private int buflen; + + private final int maxLineLen; + + public BufferedLineReaderInputStream( + final InputStream instream, + int buffersize, + int maxLineLen) { + super(instream); + if (instream == null) { + throw new IllegalArgumentException("Input stream may not be null"); + } + if (buffersize <= 0) { + throw new IllegalArgumentException("Buffer size may not be negative or zero"); + } + this.buffer = new byte[buffersize]; + this.bufpos = 0; + this.buflen = 0; + this.maxLineLen = maxLineLen; + this.truncated = false; + } + + public BufferedLineReaderInputStream( + final InputStream instream, + int buffersize) { + this(instream, buffersize, -1); + } + + private void expand(int newlen) { + byte newbuffer[] = new byte[newlen]; + int len = bufferLen(); + if (len > 0) { + System.arraycopy(this.buffer, this.bufpos, newbuffer, this.bufpos, len); + } + this.buffer = newbuffer; + } + + public void ensureCapacity(int len) { + if (len > this.buffer.length) { + expand(len); + } + } + + public int fillBuffer() throws IOException { + if (tempBuffer) { + // we was on tempBuffer. + // check that we completed the tempBuffer + if (bufpos != buflen) throw new IllegalStateException("unread only works when a buffer is fully read before the next refill is asked!"); + // restore the original buffer + buffer = origBuffer; + buflen = origBuflen; + bufpos = origBufpos; + tempBuffer = false; + // return that we just read bufferLen data. + return bufferLen(); + } + // compact the buffer if necessary + if (this.bufpos > 0) { // could swtich to (this.buffer.length / 2) but needs a 4*boundary capacity, then (instead of 2). + int len = bufferLen(); + if (len > 0) { + System.arraycopy(this.buffer, this.bufpos, this.buffer, 0, len); + } + this.bufpos = 0; + this.buflen = len; + } + int l; + int off = this.buflen; + int len = this.buffer.length - off; + l = in.read(this.buffer, off, len); + if (l == -1) { + return -1; + } else { + this.buflen = off + l; + return l; + } + } + + private int bufferLen() { + return this.buflen - this.bufpos; + } + + public boolean hasBufferedData() { + return bufferLen() > 0; + } + + public void truncate() { + clear(); + this.truncated = true; + } + + protected boolean readAllowed() { + return !this.truncated; + } + + @Override + public int read() throws IOException { + if (!readAllowed()) return -1; + int noRead = 0; + while (!hasBufferedData()) { + noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + return this.buffer[this.bufpos++] & 0xff; + } + + @Override + public int read(final byte[] b, int off, int len) throws IOException { + if (!readAllowed()) return -1; + if (b == null) { + return 0; + } + int noRead = 0; + while (!hasBufferedData()) { + noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + int chunk = bufferLen(); + if (chunk > len) { + chunk = len; + } + System.arraycopy(this.buffer, this.bufpos, b, off, chunk); + this.bufpos += chunk; + return chunk; + } + + @Override + public int read(final byte[] b) throws IOException { + if (!readAllowed()) return -1; + if (b == null) { + return 0; + } + return read(b, 0, b.length); + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public int readLine(final ByteArrayBuffer dst) + throws MaxLineLimitException, IOException { + if (dst == null) { + throw new IllegalArgumentException("Buffer may not be null"); + } + if (!readAllowed()) return -1; + + int total = 0; + boolean found = false; + int bytesRead = 0; + while (!found) { + if (!hasBufferedData()) { + bytesRead = fillBuffer(); + if (bytesRead == -1) { + break; + } + } + int i = indexOf((byte)'\n'); + int chunk; + if (i != -1) { + found = true; + chunk = i + 1 - pos(); + } else { + chunk = length(); + } + if (chunk > 0) { + dst.append(buf(), pos(), chunk); + skip(chunk); + total += chunk; + } + if (this.maxLineLen > 0 && dst.length() >= this.maxLineLen) { + throw new MaxLineLimitException("Maximum line length limit exceeded"); + } + } + if (total == 0 && bytesRead == -1) { + return -1; + } else { + return total; + } + } + + /** + * Implements quick search algorithm as published by + *

+ * SUNDAY D.M., 1990, + * A very fast substring search algorithm, + * Communications of the ACM . 33(8):132-142. + *

+ */ + public int indexOf(final byte[] pattern, int off, int len) { + if (pattern == null) { + throw new IllegalArgumentException("Pattern may not be null"); + } + if (off < this.bufpos || len < 0 || off + len > this.buflen) { + throw new IndexOutOfBoundsException("looking for "+off+"("+len+")"+" in "+bufpos+"/"+buflen); + } + if (len < pattern.length) { + return -1; + } + + int[] shiftTable = new int[256]; + for (int i = 0; i < shiftTable.length; i++) { + shiftTable[i] = pattern.length + 1; + } + for (int i = 0; i < pattern.length; i++) { + int x = pattern[i] & 0xff; + shiftTable[x] = pattern.length - i; + } + + int j = 0; + while (j <= len - pattern.length) { + int cur = off + j; + boolean match = true; + for (int i = 0; i < pattern.length; i++) { + if (this.buffer[cur + i] != pattern[i]) { + match = false; + break; + } + } + if (match) { + return cur; + } + + int pos = cur + pattern.length; + if (pos >= this.buffer.length) { + break; + } + int x = this.buffer[pos] & 0xff; + j += shiftTable[x]; + } + return -1; + } + + /** + * Implements quick search algorithm as published by + *

+ * SUNDAY D.M., 1990, + * A very fast substring search algorithm, + * Communications of the ACM . 33(8):132-142. + *

+ */ + public int indexOf(final byte[] pattern) { + return indexOf(pattern, this.bufpos, this.buflen - this.bufpos); + } + + public int indexOf(byte b, int off, int len) { + if (off < this.bufpos || len < 0 || off + len > this.buflen) { + throw new IndexOutOfBoundsException(); + } + for (int i = off; i < off + len; i++) { + if (this.buffer[i] == b) { + return i; + } + } + return -1; + } + + public int indexOf(byte b) { + return indexOf(b, this.bufpos, bufferLen()); + } + + public byte charAt(int pos) { + if (pos < this.bufpos || pos > this.buflen) { + throw new IndexOutOfBoundsException("looking for "+pos+" in "+bufpos+"/"+buflen); + } + return this.buffer[pos]; + } + + protected byte[] buf() { + return this.buffer; + } + + protected int pos() { + return this.bufpos; + } + + protected int limit() { + return this.buflen; + } + + protected int length() { + return bufferLen(); + } + + public int capacity() { + return this.buffer.length; + } + + protected int skip(int n) { + int chunk = Math.min(n, bufferLen()); + this.bufpos += chunk; + return chunk; + } + + private void clear() { + this.bufpos = 0; + this.buflen = 0; + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("[pos: "); + buffer.append(this.bufpos); + buffer.append("]"); + buffer.append("[limit: "); + buffer.append(this.buflen); + buffer.append("]"); + buffer.append("["); + for (int i = this.bufpos; i < this.buflen; i++) { + buffer.append((char) this.buffer[i]); + } + buffer.append("]"); + if (tempBuffer) { + buffer.append("-ORIG[pos: "); + buffer.append(this.origBufpos); + buffer.append("]"); + buffer.append("[limit: "); + buffer.append(this.origBuflen); + buffer.append("]"); + buffer.append("["); + for (int i = this.origBufpos; i < this.origBuflen; i++) { + buffer.append((char) this.origBuffer[i]); + } + buffer.append("]"); + } + return buffer.toString(); + } + + @Override + public boolean unread(ByteArrayBuffer buf) { + if (tempBuffer) return false; + origBuffer = buffer; + origBuflen = buflen; + origBufpos = bufpos; + bufpos = 0; + buflen = buf.length(); + buffer = buf.buffer(); + tempBuffer = true; + return true; + } + +} diff --git a/src/org/apache/james/mime4j/EOLConvertingInputStream.java b/src/org/apache/james/mime4j/io/EOLConvertingInputStream.java similarity index 94% rename from src/org/apache/james/mime4j/EOLConvertingInputStream.java rename to src/org/apache/james/mime4j/io/EOLConvertingInputStream.java index 01a7f5db3..b7fedf0c9 100644 --- a/src/org/apache/james/mime4j/EOLConvertingInputStream.java +++ b/src/org/apache/james/mime4j/io/EOLConvertingInputStream.java @@ -17,7 +17,7 @@ * under the License. * ****************************************************************/ -package org.apache.james.mime4j; +package org.apache.james.mime4j.io; import java.io.IOException; import java.io.InputStream; @@ -25,11 +25,8 @@ import java.io.PushbackInputStream; /** * InputStream which converts \r - * bytes not followed by \n and \n not + * bytes not followed by \n and \n not * preceded by \r to \r\n. - * - * - * @version $Id: EOLConvertingInputStream.java,v 1.4 2004/11/29 13:15:42 ntherning Exp $ */ public class EOLConvertingInputStream extends InputStream { /** Converts single '\r' to '\r\n' */ @@ -38,16 +35,16 @@ public class EOLConvertingInputStream extends InputStream { public static final int CONVERT_LF = 2; /** Converts single '\r' and '\n' to '\r\n' */ public static final int CONVERT_BOTH = 3; - + private PushbackInputStream in = null; private int previous = 0; private int flags = CONVERT_BOTH; - + /** * Creates a new EOLConvertingInputStream * instance converting bytes in the given InputStream. * The flag CONVERT_BOTH is the default. - * + * * @param in the InputStream to read from. */ public EOLConvertingInputStream(InputStream in) { @@ -56,37 +53,39 @@ public class EOLConvertingInputStream extends InputStream { /** * Creates a new EOLConvertingInputStream * instance converting bytes in the given InputStream. - * + * * @param in the InputStream to read from. * @param flags one of CONVERT_CR, CONVERT_LF or * CONVERT_BOTH. */ public EOLConvertingInputStream(InputStream in, int flags) { super(); - + this.in = new PushbackInputStream(in, 2); this.flags = flags; } /** * Closes the underlying stream. - * + * * @throws IOException on I/O errors. */ + @Override public void close() throws IOException { in.close(); } - + /** * @see java.io.InputStream#read() */ + @Override public int read() throws IOException { int b = in.read(); - + if (b == -1) { return -1; } - + if ((flags & CONVERT_CR) != 0 && b == '\r') { int c = in.read(); if (c != -1) { @@ -99,9 +98,9 @@ public class EOLConvertingInputStream extends InputStream { b = '\r'; in.unread('\n'); } - + previous = b; - + return b; } diff --git a/src/org/apache/james/mime4j/util/PartialInputStream.java b/src/org/apache/james/mime4j/io/LimitedInputStream.java similarity index 66% rename from src/org/apache/james/mime4j/util/PartialInputStream.java rename to src/org/apache/james/mime4j/io/LimitedInputStream.java index cf3b10761..c32093c02 100644 --- a/src/org/apache/james/mime4j/util/PartialInputStream.java +++ b/src/org/apache/james/mime4j/io/LimitedInputStream.java @@ -17,47 +17,51 @@ * under the License. * ****************************************************************/ - -package org.apache.james.mime4j.util; +package org.apache.james.mime4j.io; import java.io.InputStream; import java.io.IOException; -public class PartialInputStream extends PositionInputStream { +public class LimitedInputStream extends PositionInputStream { + private final long limit; - public PartialInputStream(InputStream inputStream, long offset, long length) throws IOException { - super(inputStream); - inputStream.skip(offset); - this.limit = offset + length; + public LimitedInputStream(InputStream instream, long limit) { + super(instream); + if (limit < 0) { + throw new IllegalArgumentException("Limit may not be negative"); + } + this.limit = limit; } - public int available() throws IOException { - return Math.min(super.available(), getBytesLeft()); + private void enforceLimit() throws IOException { + if (position >= limit) { + throw new IOException("Input stream limit exceeded"); + } } + @Override public int read() throws IOException { - if (limit > position) - return super.read(); - else - return -1; - } - - public int read(byte b[]) throws IOException { - return read(b, 0, b.length); + enforceLimit(); + return super.read(); } + @Override public int read(byte b[], int off, int len) throws IOException { + enforceLimit(); len = Math.min(len, getBytesLeft()); - return super.read(b, off, len); //To change body of overridden methods use File | Settings | File Templates. + return super.read(b, off, len); } + @Override public long skip(long n) throws IOException { + enforceLimit(); n = Math.min(n, getBytesLeft()); - return super.skip(n); //To change body of overridden methods use File | Settings | File Templates. + return super.skip(n); } private int getBytesLeft() { return (int)Math.min(Integer.MAX_VALUE, limit - position); } + } diff --git a/src/org/apache/james/mime4j/io/LineNumberInputStream.java b/src/org/apache/james/mime4j/io/LineNumberInputStream.java new file mode 100644 index 000000000..98afb2b43 --- /dev/null +++ b/src/org/apache/james/mime4j/io/LineNumberInputStream.java @@ -0,0 +1,67 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * InputStream used by the parser to wrap the original user + * supplied stream. This stream keeps track of the current line number. + */ +public class LineNumberInputStream extends FilterInputStream implements + LineNumberSource { + private int lineNumber = 1; + + /** + * Creates a new LineNumberInputStream. + * + * @param is + * the stream to read from. + */ + public LineNumberInputStream(InputStream is) { + super(is); + } + + public int getLineNumber() { + return lineNumber; + } + + @Override + public int read() throws IOException { + int b = in.read(); + if (b == '\n') { + lineNumber++; + } + return b; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int n = in.read(b, off, len); + for (int i = off; i < off + n; i++) { + if (b[i] == '\n') { + lineNumber++; + } + } + return n; + } +} diff --git a/src/org/apache/james/mime4j/io/LineNumberSource.java b/src/org/apache/james/mime4j/io/LineNumberSource.java new file mode 100644 index 000000000..15b6fa63a --- /dev/null +++ b/src/org/apache/james/mime4j/io/LineNumberSource.java @@ -0,0 +1,30 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.io; + +public interface LineNumberSource { + /** + * Gets the current line number starting at 1 (the number of + * \r\n read so far plus 1). + * + * @return the current line number. + */ + int getLineNumber(); +} diff --git a/src/org/apache/james/mime4j/io/LineReaderInputStream.java b/src/org/apache/james/mime4j/io/LineReaderInputStream.java new file mode 100644 index 000000000..22d00ee08 --- /dev/null +++ b/src/org/apache/james/mime4j/io/LineReaderInputStream.java @@ -0,0 +1,64 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.io; + +import org.apache.james.mime4j.util.ByteArrayBuffer; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Input stream capable of reading lines of text. + */ +public abstract class LineReaderInputStream extends FilterInputStream { + + protected LineReaderInputStream(InputStream in) { + super(in); + } + + /** + * Reads one line of text into the given {@link ByteArrayBuffer}. + * + * @param dst Destination + * @return number of bytes copied or -1 if the end of + * the stream has been reached. + * + * @throws MaxLineLimitException if the line exceeds a limit on + * the line length imposed by a subclass. + * @throws IOException in case of an I/O error. + */ + public abstract int readLine(final ByteArrayBuffer dst) + throws MaxLineLimitException, IOException; + + /** + * Tries to unread the last read line. + * + * Implementation may refuse to unread a new buffer until the previous + * unread one has been competely consumed. + * + * Implementations will directly use the byte array backed by buf, so + * make sure to not alter it anymore once this method has been called. + * + * @return true if the unread has been succesfull. + */ + public abstract boolean unread(ByteArrayBuffer buf); + +} diff --git a/src/org/apache/james/mime4j/io/LineReaderInputStreamAdaptor.java b/src/org/apache/james/mime4j/io/LineReaderInputStreamAdaptor.java new file mode 100644 index 000000000..c327b18df --- /dev/null +++ b/src/org/apache/james/mime4j/io/LineReaderInputStreamAdaptor.java @@ -0,0 +1,148 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.io; + +import org.apache.james.mime4j.util.ByteArrayBuffer; + +import java.io.IOException; +import java.io.InputStream; + +/** + * InputStream used by the MIME parser to detect whether the + * underlying data stream was used (read from) and whether the end of the + * stream was reached. + */ +public class LineReaderInputStreamAdaptor extends LineReaderInputStream { + + private final LineReaderInputStream bis; + private final int maxLineLen; + + private boolean used = false; + private boolean eof = false; + + public LineReaderInputStreamAdaptor( + final InputStream is, + int maxLineLen) { + super(is); + if (is instanceof LineReaderInputStream) { + this.bis = (LineReaderInputStream) is; + } else { + this.bis = null; + } + this.maxLineLen = maxLineLen; + } + + public LineReaderInputStreamAdaptor( + final InputStream is) { + this(is, -1); + } + + @Override + public int read() throws IOException { + int i = in.read(); + this.eof = i == -1; + this.used = true; + return i; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int i = in.read(b, off, len); + this.eof = i == -1; + this.used = true; + return i; + } + + @Override + public int readLine(final ByteArrayBuffer dst) + throws MaxLineLimitException, IOException { + int i; + if (this.bis != null) { + i = this.bis.readLine(dst); + } else { + i = doReadLine(dst); + } + this.eof = i == -1; + this.used = true; + return i; + } + + private int doReadLine(final ByteArrayBuffer dst) + throws MaxLineLimitException, IOException { + int total = 0; + int ch; + while ((ch = in.read()) != -1) { + dst.append(ch); + total++; + if (this.maxLineLen > 0 && dst.length() >= this.maxLineLen) { + throw new MaxLineLimitException("Maximum line length limit exceeded"); + } + if (ch == '\n') { + break; + } + } + if (total == 0 && ch == -1) { + return -1; + } else { + return total; + } + } + + public boolean eof() { + return this.eof; + } + + public boolean isUsed() { + return this.used; + } + + @Override + public String toString() { + return "[LineReaderInputStreamAdaptor: " + bis + "]"; + } + + @Override + public boolean unread(ByteArrayBuffer buf) { + if (bis != null) { + return bis.unread(buf); + } else { + return false; + } + } + + @Override + public long skip(long count) throws IOException { + if (count <= 0) { + return 0; // So specified by InputStream.skip(long). + } + final int bufferSize = count > 8192 ? 8192 : (int) count; + final byte[] buffer = new byte[bufferSize]; + long result = 0; + while (count > 0) { + int res = read(buffer); + if (res == -1) { + break; + } + result += res; + count -= res; + } + return result; + } +} diff --git a/src/org/apache/james/mime4j/io/MaxHeaderLengthLimitException.java b/src/org/apache/james/mime4j/io/MaxHeaderLengthLimitException.java new file mode 100644 index 000000000..1e81436f4 --- /dev/null +++ b/src/org/apache/james/mime4j/io/MaxHeaderLengthLimitException.java @@ -0,0 +1,35 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.io; + +import org.apache.james.mime4j.MimeException; + +/** + * Signals a I/O error due to the total header length exceeding the maximum limit. + */ +public class MaxHeaderLengthLimitException extends MimeException { + + private static final long serialVersionUID = 8924290744274769913L; + + public MaxHeaderLengthLimitException(final String message) { + super(message); + } + +} diff --git a/src/org/apache/james/mime4j/io/MaxHeaderLimitException.java b/src/org/apache/james/mime4j/io/MaxHeaderLimitException.java new file mode 100644 index 000000000..74b766687 --- /dev/null +++ b/src/org/apache/james/mime4j/io/MaxHeaderLimitException.java @@ -0,0 +1,35 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.io; + +import org.apache.james.mime4j.MimeException; + +/** + * Signals a I/O error due to the header count exceeding the maximum limit. + */ +public class MaxHeaderLimitException extends MimeException { + + private static final long serialVersionUID = 2154269045186186769L; + + public MaxHeaderLimitException(final String message) { + super(message); + } + +} diff --git a/src/org/apache/james/mime4j/io/MaxLineLimitException.java b/src/org/apache/james/mime4j/io/MaxLineLimitException.java new file mode 100644 index 000000000..ae0b7cd34 --- /dev/null +++ b/src/org/apache/james/mime4j/io/MaxLineLimitException.java @@ -0,0 +1,36 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.io; + +import java.io.IOException; + +/** + * Signals an I/O error due to a line exceeding the limit on the maximum line + * length. + */ +public class MaxLineLimitException extends IOException { + + private static final long serialVersionUID = 1855987166990764426L; + + public MaxLineLimitException(final String message) { + super(message); + } + +} diff --git a/src/org/apache/james/mime4j/io/MimeBoundaryInputStream.java b/src/org/apache/james/mime4j/io/MimeBoundaryInputStream.java new file mode 100644 index 000000000..b83575b01 --- /dev/null +++ b/src/org/apache/james/mime4j/io/MimeBoundaryInputStream.java @@ -0,0 +1,314 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.io; + +import org.apache.james.mime4j.util.ByteArrayBuffer; + +import java.io.IOException; + +/** + * Stream that constrains itself to a single MIME body part. + * After the stream ends (i.e. read() returns -1) {@link #isLastPart()} + * can be used to determine if a final boundary has been seen or not. + */ +public class MimeBoundaryInputStream extends LineReaderInputStream { + + private final byte[] boundary; + + private boolean eof; + private int limit; + private boolean atBoundary; + private int boundaryLen; + private boolean lastPart; + private boolean completed; + + private BufferedLineReaderInputStream buffer; + + /** + * Store the first buffer length. + * Used to distinguish between an empty preamble and + * no preamble. + */ + private int initialLength; + + /** + * Creates a new MimeBoundaryInputStream. + * + * @param inbuffer The underlying stream. + * @param boundary Boundary string (not including leading hyphens). + * @throws IllegalArgumentException when boundary is too long + */ + public MimeBoundaryInputStream(BufferedLineReaderInputStream inbuffer, String boundary) + throws IOException { + super(inbuffer); + + if (inbuffer.capacity() < boundary.length() * 2) { + throw new IllegalArgumentException("Boundary is too long"); + } + this.buffer = inbuffer; + this.eof = false; + this.limit = -1; + this.atBoundary = false; + this.boundaryLen = 0; + this.lastPart = false; + this.initialLength = -1; + this.completed = false; + + this.boundary = new byte[boundary.length() + 2]; + this.boundary[0] = (byte) '-'; + this.boundary[1] = (byte) '-'; + for (int i = 0; i < boundary.length(); i++) { + byte ch = (byte) boundary.charAt(i); + if (ch == '\r' || ch == '\n') { + throw new IllegalArgumentException("Boundary may not contain CR or LF"); + } + this.boundary[i + 2] = ch; + } + + fillBuffer(); + } + + /** + * Closes the underlying stream. + * + * @throws IOException on I/O errors. + */ + @Override + public void close() throws IOException { + } + + /** + * @see java.io.InputStream#markSupported() + */ + @Override + public boolean markSupported() { + return false; + } + + public boolean readAllowed() throws IOException { + if (completed) { + return false; + } + // System.out.println("rA!"); + if (endOfStream() && !hasData()) { + skipBoundary(); + return false; + } + return true; + } + + /** + * @see java.io.InputStream#read() + */ + @Override + public int read() throws IOException { + if (!readAllowed()) return -1; + for (;;) { + if (hasData()) { + return buffer.read(); + } else if (endOfStream()) { + skipBoundary(); + return -1; + } + fillBuffer(); + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (!readAllowed()) return -1; + fillBuffer(); + if (!hasData()) { + return read(b, off, len); + } + int chunk = Math.min(len, limit - buffer.pos()); + return buffer.read(b, off, chunk); + } + + @Override + public int readLine(final ByteArrayBuffer dst) throws IOException { + if (dst == null) { + throw new IllegalArgumentException("Destination buffer may not be null"); + } + if (!readAllowed()) return -1; + + int total = 0; + boolean found = false; + int bytesRead = 0; + while (!found) { + if (!hasData()) { + bytesRead = fillBuffer(); + if (endOfStream() && !hasData()) { + skipBoundary(); + bytesRead = -1; + break; + } + } + int len = this.limit - this.buffer.pos(); + int i = this.buffer.indexOf((byte)'\n', this.buffer.pos(), len); + int chunk; + if (i != -1) { + found = true; + chunk = i + 1 - this.buffer.pos(); + } else { + chunk = len; + } + if (chunk > 0) { + dst.append(this.buffer.buf(), this.buffer.pos(), chunk); + this.buffer.skip(chunk); + total += chunk; + } + } + if (total == 0 && bytesRead == -1) { + return -1; + } else { + return total; + } + } + + private boolean endOfStream() { + return eof || atBoundary; + } + + private boolean hasData() { + return limit > buffer.pos() && limit <= buffer.limit(); + } + + private int fillBuffer() throws IOException { + if (eof) { + return -1; + } + int bytesRead; + if (!hasData()) { + bytesRead = buffer.fillBuffer(); + if (bytesRead == -1) { + eof = true; + } + } else { + bytesRead = 0; + } + + + int i = buffer.indexOf(boundary); + // NOTE this currently check only for LF. It doesn't check for canonical CRLF + // and neither for isolated CR. This will require updates according to MIME4J-60 + while (i > buffer.pos() && buffer.charAt(i-1) != '\n') { + // skip the "fake" boundary (it does not contain LF or CR so we cannot have + // another boundary starting before this is complete. + i = i + boundary.length; + i = buffer.indexOf(boundary, i, buffer.limit() - i); + } + if (i != -1) { + limit = i; + atBoundary = true; + calculateBoundaryLen(); + } else { + if (eof) { + limit = buffer.limit(); + } else { + limit = buffer.limit() - (boundary.length + 1); + // \r\n + (boundary - one char) + } + } + return bytesRead; + } + + public boolean isEmptyStream() { + return initialLength == 0; + } + + private void calculateBoundaryLen() throws IOException { + boundaryLen = boundary.length; + int len = limit - buffer.pos(); + if (len >= 0 && initialLength == -1) initialLength = len; + if (len > 0) { + if (buffer.charAt(limit - 1) == '\n') { + boundaryLen++; + limit--; + } + } + if (len > 1) { + if (buffer.charAt(limit - 1) == '\r') { + boundaryLen++; + limit--; + } + } + } + + private void skipBoundary() throws IOException { + if (!completed) { + completed = true; + buffer.skip(boundaryLen); + boolean checkForLastPart = true; + for (;;) { + if (buffer.length() > 1) { + int ch1 = buffer.charAt(buffer.pos()); + int ch2 = buffer.charAt(buffer.pos() + 1); + + if (checkForLastPart) if (ch1 == '-' && ch2 == '-') { + this.lastPart = true; + buffer.skip(2); + checkForLastPart = false; + continue; + } + + if (ch1 == '\r' && ch2 == '\n') { + buffer.skip(2); + break; + } else if (ch1 == '\n') { + buffer.skip(1); + break; + } else { + // ignoring everything in a line starting with a boundary. + buffer.skip(1); + } + + } else { + if (eof) { + break; + } + fillBuffer(); + } + } + } + } + + public boolean isLastPart() { + return lastPart; + } + + public boolean eof() { + return eof && !buffer.hasBufferedData(); + } + + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder("MimeBoundaryInputStream, boundary "); + for (byte b : boundary) { + buffer.append((char) b); + } + return buffer.toString(); + } + + @Override + public boolean unread(ByteArrayBuffer buf) { + return false; + } +} diff --git a/src/org/apache/james/mime4j/util/PositionInputStream.java b/src/org/apache/james/mime4j/io/PositionInputStream.java similarity index 75% rename from src/org/apache/james/mime4j/util/PositionInputStream.java rename to src/org/apache/james/mime4j/io/PositionInputStream.java index 7865b6e8a..b78c659a3 100644 --- a/src/org/apache/james/mime4j/util/PositionInputStream.java +++ b/src/org/apache/james/mime4j/io/PositionInputStream.java @@ -18,69 +18,73 @@ ****************************************************************/ -package org.apache.james.mime4j.util; +package org.apache.james.mime4j.io; +import java.io.FilterInputStream; import java.io.InputStream; import java.io.IOException; -public class PositionInputStream extends InputStream { +public class PositionInputStream extends FilterInputStream { - private final InputStream inputStream; protected long position = 0; private long markedPosition = 0; public PositionInputStream(InputStream inputStream) { - this.inputStream = inputStream; + super(inputStream); } public long getPosition() { return position; } + @Override public int available() throws IOException { - return inputStream.available(); + return in.available(); } + @Override public int read() throws IOException { - int b = inputStream.read(); + int b = in.read(); if (b != -1) position++; return b; } + @Override public void close() throws IOException { - inputStream.close(); + in.close(); } + @Override public void reset() throws IOException { - inputStream.reset(); + in.reset(); position = markedPosition; } + @Override public boolean markSupported() { - return inputStream.markSupported(); + return in.markSupported(); } + @Override public void mark(int readlimit) { - inputStream.mark(readlimit); + in.mark(readlimit); markedPosition = position; } + @Override public long skip(long n) throws IOException { - final long c = inputStream.skip(n); - position += c; - return c; - } - - public int read(byte b[]) throws IOException { - final int c = inputStream.read(b); - position += c; + final long c = in.skip(n); + if (c > 0) + position += c; return c; } + @Override public int read(byte b[], int off, int len) throws IOException { - final int c = inputStream.read(b, off, len); - position += c; + final int c = in.read(b, off, len); + if (c > 0) + position += c; return c; } diff --git a/src/org/apache/james/mime4j/message/BodyCopier.java b/src/org/apache/james/mime4j/message/BodyCopier.java new file mode 100644 index 000000000..8c17b3c02 --- /dev/null +++ b/src/org/apache/james/mime4j/message/BodyCopier.java @@ -0,0 +1,74 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import org.apache.james.mime4j.dom.Body; +import org.apache.james.mime4j.dom.Disposable; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.Multipart; +import org.apache.james.mime4j.dom.SingleBody; + +/** + * Utility class for copying message bodies. + */ +public class BodyCopier { + + private BodyCopier() { + } + + /** + * Returns a copy of the given {@link Body} that can be used (and modified) + * independently of the original. The copy should be + * {@link Disposable#dispose() disposed of} when it is no longer needed. + *

+ * The {@link Body#getParent() parent} of the returned copy is + * null, that is, the copy is detached from the parent + * entity of the original. + * + * @param body + * body to copy. + * @return a copy of the given body. + * @throws UnsupportedOperationException + * if body is an instance of {@link SingleBody} + * that does not support the {@link SingleBody#copy() copy()} + * operation (or contains such a SingleBody). + * @throws IllegalArgumentException + * if body is null or + * body is a Body that is neither + * a {@link MessageImpl}, {@link Multipart} or {@link SingleBody} + * (or contains such a Body). + */ + public static Body copy(Body body) { + if (body == null) + throw new IllegalArgumentException("Body is null"); + + if (body instanceof Message) + return new MessageImpl((Message) body); + + if (body instanceof Multipart) + return new MultipartImpl((Multipart) body); + + if (body instanceof SingleBody) + return ((SingleBody) body).copy(); + + throw new IllegalArgumentException("Unsupported body class"); + } + +} diff --git a/src/org/apache/james/mime4j/message/BodyFactory.java b/src/org/apache/james/mime4j/message/BodyFactory.java new file mode 100644 index 000000000..d277829d6 --- /dev/null +++ b/src/org/apache/james/mime4j/message/BodyFactory.java @@ -0,0 +1,323 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.BinaryBody; +import org.apache.james.mime4j.dom.Disposable; +import org.apache.james.mime4j.dom.SingleBody; +import org.apache.james.mime4j.dom.TextBody; +import org.apache.james.mime4j.storage.DefaultStorageProvider; +import org.apache.james.mime4j.storage.MultiReferenceStorage; +import org.apache.james.mime4j.storage.Storage; +import org.apache.james.mime4j.storage.StorageProvider; +import org.apache.james.mime4j.util.CharsetUtil; + +/** + * Factory for creating message bodies. + */ +public class BodyFactory { + + private static final Charset FALLBACK_CHARSET = CharsetUtil.DEFAULT_CHARSET; + + private final StorageProvider storageProvider; + private final DecodeMonitor monitor; + + /** + * Creates a new BodyFactory instance that uses the default + * storage provider for creating message bodies from input streams. + */ + public BodyFactory() { + this(null, null); + } + + /** + * Creates a new BodyFactory instance that uses the given + * storage provider for creating message bodies from input streams. + * + * @param storageProvider + * a storage provider or null to use the default + * one. + */ + public BodyFactory( + final StorageProvider storageProvider, + final DecodeMonitor monitor) { + this.storageProvider = + storageProvider != null ? storageProvider : DefaultStorageProvider.getInstance(); + this.monitor = + monitor != null ? monitor : DecodeMonitor.SILENT; + } + + /** + * Returns the StorageProvider this BodyFactory + * uses to create message bodies from input streams. + * + * @return a StorageProvider. + */ + public StorageProvider getStorageProvider() { + return storageProvider; + } + + /** + * Creates a {@link BinaryBody} that holds the content of the given input + * stream. + * + * @param is + * input stream to create a message body from. + * @return a binary body. + * @throws IOException + * if an I/O error occurs. + */ + public BinaryBody binaryBody(InputStream is) throws IOException { + if (is == null) + throw new IllegalArgumentException(); + + Storage storage = storageProvider.store(is); + return new StorageBinaryBody(new MultiReferenceStorage(storage)); + } + + /** + * Creates a {@link BinaryBody} that holds the content of the given + * {@link Storage}. + *

+ * Note that the caller must not invoke {@link Storage#delete() delete()} on + * the given Storage object after it has been passed to this + * method. Instead the message body created by this method takes care of + * deleting the storage when it gets disposed of (see + * {@link Disposable#dispose()}). + * + * @param storage + * storage to create a message body from. + * @return a binary body. + * @throws IOException + * if an I/O error occurs. + */ + public BinaryBody binaryBody(Storage storage) throws IOException { + if (storage == null) + throw new IllegalArgumentException(); + + return new StorageBinaryBody(new MultiReferenceStorage(storage)); + } + + /** + * Creates a {@link TextBody} that holds the content of the given input + * stream. + *

+ * "us-ascii" is used to decode the byte content of the + * Storage into a character stream when calling + * {@link TextBody#getReader() getReader()} on the returned object. + * + * @param is + * input stream to create a message body from. + * @return a text body. + * @throws IOException + * if an I/O error occurs. + */ + public TextBody textBody(InputStream is) throws IOException { + if (is == null) + throw new IllegalArgumentException(); + + Storage storage = storageProvider.store(is); + return new StorageTextBody(new MultiReferenceStorage(storage), + CharsetUtil.DEFAULT_CHARSET); + } + + /** + * Creates a {@link TextBody} that holds the content of the given input + * stream. + *

+ * The charset corresponding to the given MIME charset name is used to + * decode the byte content of the input stream into a character stream when + * calling {@link TextBody#getReader() getReader()} on the returned object. + * If the MIME charset has no corresponding Java charset or the Java charset + * cannot be used for decoding then "us-ascii" is used instead. + * + * @param is + * input stream to create a message body from. + * @param mimeCharset + * name of a MIME charset. + * @return a text body. + * @throws IOException + * if an I/O error occurs. + */ + public TextBody textBody(InputStream is, String mimeCharset) + throws IOException { + if (is == null) + throw new IllegalArgumentException(); + if (mimeCharset == null) + throw new IllegalArgumentException(); + + Storage storage = storageProvider.store(is); + Charset charset = toJavaCharset(mimeCharset, false, monitor); + return new StorageTextBody(new MultiReferenceStorage(storage), charset); + } + + /** + * Creates a {@link TextBody} that holds the content of the given + * {@link Storage}. + *

+ * "us-ascii" is used to decode the byte content of the + * Storage into a character stream when calling + * {@link TextBody#getReader() getReader()} on the returned object. + *

+ * Note that the caller must not invoke {@link Storage#delete() delete()} on + * the given Storage object after it has been passed to this + * method. Instead the message body created by this method takes care of + * deleting the storage when it gets disposed of (see + * {@link Disposable#dispose()}). + * + * @param storage + * storage to create a message body from. + * @return a text body. + * @throws IOException + * if an I/O error occurs. + */ + public TextBody textBody(Storage storage) throws IOException { + if (storage == null) + throw new IllegalArgumentException(); + + return new StorageTextBody(new MultiReferenceStorage(storage), + CharsetUtil.DEFAULT_CHARSET); + } + + /** + * Creates a {@link TextBody} that holds the content of the given + * {@link Storage}. + *

+ * The charset corresponding to the given MIME charset name is used to + * decode the byte content of the Storage into a character + * stream when calling {@link TextBody#getReader() getReader()} on the + * returned object. If the MIME charset has no corresponding Java charset or + * the Java charset cannot be used for decoding then "us-ascii" is + * used instead. + *

+ * Note that the caller must not invoke {@link Storage#delete() delete()} on + * the given Storage object after it has been passed to this + * method. Instead the message body created by this method takes care of + * deleting the storage when it gets disposed of (see + * {@link Disposable#dispose()}). + * + * @param storage + * storage to create a message body from. + * @param mimeCharset + * name of a MIME charset. + * @return a text body. + * @throws IOException + * if an I/O error occurs. + */ + public TextBody textBody(Storage storage, String mimeCharset) + throws IOException { + if (storage == null) + throw new IllegalArgumentException(); + if (mimeCharset == null) + throw new IllegalArgumentException(); + + Charset charset = toJavaCharset(mimeCharset, false, monitor); + return new StorageTextBody(new MultiReferenceStorage(storage), charset); + } + + /** + * Creates a {@link TextBody} that holds the content of the given string. + *

+ * "us-ascii" is used to encode the characters of the string into + * a byte stream when calling + * {@link SingleBody#writeTo(java.io.OutputStream) writeTo(OutputStream)} on + * the returned object. + * + * @param text + * text to create a message body from. + * @return a text body. + */ + public TextBody textBody(String text) { + if (text == null) + throw new IllegalArgumentException(); + + return new StringTextBody(text, CharsetUtil.DEFAULT_CHARSET); + } + + /** + * Creates a {@link TextBody} that holds the content of the given string. + *

+ * The charset corresponding to the given MIME charset name is used to + * encode the characters of the string into a byte stream when calling + * {@link SingleBody#writeTo(java.io.OutputStream) writeTo(OutputStream)} on + * the returned object. If the MIME charset has no corresponding Java + * charset or the Java charset cannot be used for encoding then + * "us-ascii" is used instead. + * + * @param text + * text to create a message body from. + * @param mimeCharset + * name of a MIME charset. + * @return a text body. + */ + public TextBody textBody(String text, String mimeCharset) { + if (text == null) + throw new IllegalArgumentException(); + if (mimeCharset == null) + throw new IllegalArgumentException(); + + Charset charset = toJavaCharset(mimeCharset, true, monitor); + return new StringTextBody(text, charset); + } + + private static Charset toJavaCharset( + final String mimeCharset, + boolean forEncoding, + final DecodeMonitor monitor) { + String charset = CharsetUtil.toJavaCharset(mimeCharset); + if (charset == null) { + if (monitor.isListening()) { + monitor.warn( + "MIME charset '" + mimeCharset + "' has no " + + "corresponding Java charset", "Using " + + FALLBACK_CHARSET + " instead."); + } + return FALLBACK_CHARSET; + } + + if (forEncoding && !CharsetUtil.isEncodingSupported(charset)) { + if (monitor.isListening()) { + monitor.warn( + "MIME charset '" + mimeCharset + + "' does not support encoding", "Using " + + FALLBACK_CHARSET + " instead."); + } + return FALLBACK_CHARSET; + } + + if (!forEncoding && !CharsetUtil.isDecodingSupported(charset)) { + if (monitor.isListening()) { + monitor.warn( + "MIME charset '" + mimeCharset + + "' does not support decoding", "Using " + + FALLBACK_CHARSET + " instead."); + } + return FALLBACK_CHARSET; + } + + return Charset.forName(charset); + } + +} diff --git a/src/org/apache/james/mime4j/message/BodyPart.java b/src/org/apache/james/mime4j/message/BodyPart.java index 764018336..eabb7ce02 100644 --- a/src/org/apache/james/mime4j/message/BodyPart.java +++ b/src/org/apache/james/mime4j/message/BodyPart.java @@ -19,24 +19,99 @@ package org.apache.james.mime4j.message; -import java.io.IOException; -import java.io.OutputStream; +import java.util.Date; +import java.util.Map; +import org.apache.james.mime4j.dom.Body; +import org.apache.james.mime4j.dom.Entity; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.Multipart; +import org.apache.james.mime4j.dom.SingleBody; +import org.apache.james.mime4j.dom.field.ContentDispositionField; +import org.apache.james.mime4j.dom.field.ContentTransferEncodingField; +import org.apache.james.mime4j.dom.field.ContentTypeField; +import org.apache.james.mime4j.field.ContentTransferEncodingFieldImpl; +import org.apache.james.mime4j.field.ContentTypeFieldImpl; +import org.apache.james.mime4j.field.Fields; +import org.apache.james.mime4j.util.MimeUtil; /** * Represents a MIME body part (see RFC 2045). - * - * - * @version $Id: BodyPart.java,v 1.3 2004/10/02 12:41:11 ntherning Exp $ */ public class BodyPart extends Entity { /** - * - * @see org.apache.james.mime4j.message.Entity#writeTo(java.io.OutputStream) + * Creates a new empty BodyPart. */ - public void writeTo(OutputStream out) throws IOException { - getHeader().writeTo(out); - getBody().writeTo(out); + public BodyPart() { } + + /** + * Creates a new BodyPart from the specified + * BodyPart. The BodyPart instance is initialized + * with copies of header and body of the specified BodyPart. + * The parent entity of the new body part is null. + * + * @param other + * body part to copy. + * @throws UnsupportedOperationException + * if other contains a {@link SingleBody} that + * does not support the {@link SingleBody#copy() copy()} + * operation. + * @throws IllegalArgumentException + * if other contains a Body that + * is neither a {@link Message}, {@link Multipart} or + * {@link SingleBody}. + */ + public BodyPart(Entity other) { + if (other.getHeader() != null) { + setHeader(new Header(other.getHeader())); + } + + if (other.getBody() != null) { + Body bodyCopy = BodyCopier.copy(other.getBody()); + setBody(bodyCopy); + } + } + + @Override + protected String newUniqueBoundary() { + return MimeUtil.createUniqueBoundary(); + } + + protected ContentDispositionField newContentDisposition( + String dispositionType, String filename, long size, + Date creationDate, Date modificationDate, Date readDate) { + return Fields.contentDisposition(dispositionType, filename, size, + creationDate, modificationDate, readDate); + } + + protected ContentDispositionField newContentDisposition( + String dispositionType, Map parameters) { + return Fields.contentDisposition(dispositionType, parameters); + } + + protected ContentTypeField newContentType(String mimeType, + Map parameters) { + return Fields.contentType(mimeType, parameters); + } + + protected ContentTransferEncodingField newContentTransferEncoding( + String contentTransferEncoding) { + return Fields.contentTransferEncoding(contentTransferEncoding); + } + + protected String calcTransferEncoding(ContentTransferEncodingField f) { + return ContentTransferEncodingFieldImpl.getEncoding(f); + } + + protected String calcMimeType(ContentTypeField child, ContentTypeField parent) { + return ContentTypeFieldImpl.getMimeType(child, parent); + } + + protected String calcCharset(ContentTypeField contentType) { + return ContentTypeFieldImpl.getCharset(contentType); + } + } diff --git a/src/org/apache/james/mime4j/message/Entity.java b/src/org/apache/james/mime4j/message/Entity.java deleted file mode 100644 index 06039f3d7..000000000 --- a/src/org/apache/james/mime4j/message/Entity.java +++ /dev/null @@ -1,170 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.message; - -import java.io.IOException; -import java.io.OutputStream; - -import org.apache.james.mime4j.field.ContentTransferEncodingField; -import org.apache.james.mime4j.field.ContentTypeField; -import org.apache.james.mime4j.field.Field; - -/** - * MIME entity. An entity has a header and a body (see RFC 2045). - * - * - * @version $Id: Entity.java,v 1.3 2004/10/02 12:41:11 ntherning Exp $ - */ -public abstract class Entity { - private Header header = null; - private Body body = null; - private Entity parent = null; - - /** - * Gets the parent entity of this entity. - * Returns null if this is the root entity. - * - * @return the parent or null. - */ - public Entity getParent() { - return parent; - } - - /** - * Sets the parent entity of this entity. - * - * @param parent the parent entity or null if - * this will be the root entity. - */ - public void setParent(Entity parent) { - this.parent = parent; - } - - /** - * Gets the entity header. - * - * @return the header. - */ - public Header getHeader() { - return header; - } - - /** - * Sets the entity header. - * - * @param header the header. - */ - public void setHeader(Header header) { - this.header = header; - } - - /** - * Gets the body of this entity. - * - * @return the body, - */ - public Body getBody() { - return body; - } - - /** - * Sets the body of this entity. - * - * @param body the body. - */ - public void setBody(Body body) { - this.body = body; - body.setParent(this); - } - - /** - * Determines the MIME type of this Entity. The MIME type - * is derived by looking at the parent's Content-Type field if no - * Content-Type field is set for this Entity. - * - * @return the MIME type. - */ - public String getMimeType() { - ContentTypeField child = - (ContentTypeField) getHeader().getField(Field.CONTENT_TYPE); - ContentTypeField parent = getParent() != null - ? (ContentTypeField) getParent().getHeader(). - getField(Field.CONTENT_TYPE) - : null; - - return ContentTypeField.getMimeType(child, parent); - } - - /** - * Determines the MIME character set encoding of this Entity. - * - * @return the MIME character set encoding. - */ - public String getCharset() { - return ContentTypeField.getCharset( - (ContentTypeField) getHeader().getField(Field.CONTENT_TYPE)); - } - - /** - * Determines the transfer encoding of this Entity. - * - * @return the transfer encoding. - */ - public String getContentTransferEncoding() { - ContentTransferEncodingField f = (ContentTransferEncodingField) - getHeader().getField(Field.CONTENT_TRANSFER_ENCODING); - - return ContentTransferEncodingField.getEncoding(f); - } - - /** - * Determines if the MIME type of this Entity matches the - * given one. MIME types are case-insensitive. - * - * @param type the MIME type to match against. - * @return true on match, false otherwise. - */ - public boolean isMimeType(String type) { - return getMimeType().equalsIgnoreCase(type); - } - - /** - * Determines if the MIME type of this Entity is - * multipart/*. Since multipart-entities must have - * a boundary parameter in the Content-Type field this - * method returns false if no boundary exists. - * - * @return true on match, false otherwise. - */ - public boolean isMultipart() { - ContentTypeField f = - (ContentTypeField) getHeader().getField(Field.CONTENT_TYPE); - return f != null && f.getBoundary() != null - && getMimeType().startsWith(ContentTypeField.TYPE_MULTIPART_PREFIX); - } - - /** - * Write the content to the given outputstream - * - * @param out the outputstream to write to - * @throws IOException - */ - public abstract void writeTo(OutputStream out) throws IOException; -} diff --git a/src/org/apache/james/mime4j/message/EntityBuilder.java b/src/org/apache/james/mime4j/message/EntityBuilder.java new file mode 100644 index 000000000..76e54debd --- /dev/null +++ b/src/org/apache/james/mime4j/message/EntityBuilder.java @@ -0,0 +1,234 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Stack; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.Body; +import org.apache.james.mime4j.dom.Entity; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.Multipart; +import org.apache.james.mime4j.dom.field.Field; +import org.apache.james.mime4j.field.DefaultFieldParser; +import org.apache.james.mime4j.parser.ContentHandler; +import org.apache.james.mime4j.storage.StorageProvider; +import org.apache.james.mime4j.stream.BodyDescriptor; +import org.apache.james.mime4j.stream.RawField; +import org.apache.james.mime4j.util.ByteArrayBuffer; +import org.apache.james.mime4j.util.ByteSequence; + +/** + * A ContentHandler for building an Entity to be + * used in conjunction with a {@link org.apache.james.mime4j.parser.MimeStreamParser}. + */ +public class EntityBuilder implements ContentHandler { + + private final Entity entity; + private final BodyFactory bodyFactory; + private final Stack stack; + private final DecodeMonitor monitor; + + public EntityBuilder(Entity entity) { + this(entity, null, null); + } + + public EntityBuilder( + final Entity entity, + final StorageProvider storageProvider, + final DecodeMonitor monitor) { + this.entity = entity; + this.stack = new Stack(); + this.monitor = monitor != null ? monitor : DecodeMonitor.SILENT; + this.bodyFactory = new BodyFactory(storageProvider, this.monitor); + } + + private void expect(Class c) { + if (!c.isInstance(stack.peek())) { + throw new IllegalStateException("Internal stack error: " + + "Expected '" + c.getName() + "' found '" + + stack.peek().getClass().getName() + "'"); + } + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#startMessage() + */ + public void startMessage() throws MimeException { + if (stack.isEmpty()) { + stack.push(this.entity); + } else { + expect(Entity.class); + Message m = new MessageImpl(); + ((Entity) stack.peek()).setBody(m); + stack.push(m); + } + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#endMessage() + */ + public void endMessage() throws MimeException { + expect(Message.class); + stack.pop(); + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#startHeader() + */ + public void startHeader() throws MimeException { + stack.push(new Header()); + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#field(RawField) + */ + public void field(RawField field) throws MimeException { + expect(Header.class); + Field parsedField = DefaultFieldParser.parse(field.getRaw(), monitor); + ((Header) stack.peek()).addField(parsedField); + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#endHeader() + */ + public void endHeader() throws MimeException { + expect(Header.class); + Header h = (Header) stack.pop(); + expect(Entity.class); + ((Entity) stack.peek()).setHeader(h); + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#startMultipart(org.apache.james.mime4j.stream.BodyDescriptor) + */ + public void startMultipart(final BodyDescriptor bd) throws MimeException { + expect(Entity.class); + + final Entity e = (Entity) stack.peek(); + final String subType = bd.getSubType(); + final Multipart multiPart = new MultipartImpl(subType); + e.setBody(multiPart); + stack.push(multiPart); + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#body(org.apache.james.mime4j.stream.BodyDescriptor, java.io.InputStream) + */ + public void body(BodyDescriptor bd, final InputStream is) throws MimeException, IOException { + expect(Entity.class); + + // NO NEED TO MANUALLY RUN DECODING. + // The parser has a "setContentDecoding" method. We should + // simply instantiate the MimeStreamParser with that method. + + // final String enc = bd.getTransferEncoding(); + + final Body body; + + /* + final InputStream decodedStream; + if (MimeUtil.ENC_BASE64.equals(enc)) { + decodedStream = new Base64InputStream(is); + } else if (MimeUtil.ENC_QUOTED_PRINTABLE.equals(enc)) { + decodedStream = new QuotedPrintableInputStream(is); + } else { + decodedStream = is; + } + */ + + if (bd.getMimeType().startsWith("text/")) { + body = bodyFactory.textBody(is, bd.getCharset()); + } else { + body = bodyFactory.binaryBody(is); + } + + Entity entity = ((Entity) stack.peek()); + entity.setBody(body); + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#endMultipart() + */ + public void endMultipart() throws MimeException { + stack.pop(); + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#startBodyPart() + */ + public void startBodyPart() throws MimeException { + expect(Multipart.class); + + BodyPart bodyPart = new BodyPart(); + ((Multipart) stack.peek()).addBodyPart(bodyPart); + stack.push(bodyPart); + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#endBodyPart() + */ + public void endBodyPart() throws MimeException { + expect(BodyPart.class); + stack.pop(); + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#epilogue(java.io.InputStream) + */ + public void epilogue(InputStream is) throws MimeException, IOException { + expect(MultipartImpl.class); + ByteSequence bytes = loadStream(is); + ((MultipartImpl) stack.peek()).setEpilogueRaw(bytes); + } + + /** + * @see org.apache.james.mime4j.parser.ContentHandler#preamble(java.io.InputStream) + */ + public void preamble(InputStream is) throws MimeException, IOException { + expect(MultipartImpl.class); + ByteSequence bytes = loadStream(is); + ((MultipartImpl) stack.peek()).setPreambleRaw(bytes); + } + + /** + * Unsupported. + * @see org.apache.james.mime4j.parser.ContentHandler#raw(java.io.InputStream) + */ + public void raw(InputStream is) throws MimeException, IOException { + throw new UnsupportedOperationException("Not supported"); + } + + private static ByteSequence loadStream(InputStream in) throws IOException { + ByteArrayBuffer bab = new ByteArrayBuffer(64); + + int b; + while ((b = in.read()) != -1) { + bab.append(b); + } + + return bab; + } + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/message/Header.java b/src/org/apache/james/mime4j/message/Header.java deleted file mode 100644 index 3e9746479..000000000 --- a/src/org/apache/james/mime4j/message/Header.java +++ /dev/null @@ -1,155 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.message; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import org.apache.james.mime4j.AbstractContentHandler; -import org.apache.james.mime4j.MimeStreamParser; -import org.apache.james.mime4j.field.ContentTypeField; -import org.apache.james.mime4j.field.Field; -import org.apache.james.mime4j.util.CharsetUtil; - - -/** - * The header of an entity (see RFC 2045). - * - * - * @version $Id: Header.java,v 1.3 2004/10/04 15:36:44 ntherning Exp $ - */ -public class Header { - private List fields = new LinkedList(); - private HashMap fieldMap = new HashMap(); - - /** - * Creates a new empty Header. - */ - public Header() { - } - - /** - * Creates a new Header from the specified stream. - * - * @param is the stream to read the header from. - */ - public Header(InputStream is) throws IOException { - final MimeStreamParser parser = new MimeStreamParser(); - parser.setContentHandler(new AbstractContentHandler() { - public void endHeader() { - parser.stop(); - } - public void field(String fieldData) { - addField(Field.parse(fieldData)); - } - }); - parser.parse(is); - } - - /** - * Adds a field to the end of the list of fields. - * - * @param field the field to add. - */ - public void addField(Field field) { - List values = (List) fieldMap.get(field.getName().toLowerCase()); - if (values == null) { - values = new LinkedList(); - fieldMap.put(field.getName().toLowerCase(), values); - } - values.add(field); - fields.add(field); - } - - /** - * Gets the fields of this header. The returned list will not be - * modifiable. - * - * @return the list of Field objects. - */ - public List getFields() { - return Collections.unmodifiableList(fields); - } - - /** - * Gets a Field given a field name. If there are multiple - * such fields defined in this header the first one will be returned. - * - * @param name the field name (e.g. From, Subject). - * @return the field or null if none found. - */ - public Field getField(String name) { - List l = (List) fieldMap.get(name.toLowerCase()); - if (l != null && !l.isEmpty()) { - return (Field) l.get(0); - } - return null; - } - - /** - * Gets all Fields having the specified field name. - * - * @param name the field name (e.g. From, Subject). - * @return the list of fields. - */ - public List getFields(String name) { - List l = (List) fieldMap.get(name.toLowerCase()); - return Collections.unmodifiableList(l); - } - - /** - * Return Header Object as String representation. Each headerline is - * seperated by "\r\n" - * - * @return headers - */ - public String toString() { - StringBuffer str = new StringBuffer(); - for (Iterator it = fields.iterator(); it.hasNext();) { - str.append(it.next().toString()); - str.append("\r\n"); - } - return str.toString(); - } - - - /** - * Write the Header to the given OutputStream - * - * @param out the OutputStream to write to - * @throws IOException - */ - public void writeTo(OutputStream out) throws IOException { - String charString = ((ContentTypeField) getField(Field.CONTENT_TYPE)).getCharset(); - - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, CharsetUtil.getCharset(charString)),8192); - writer.write(toString()+ "\r\n"); - writer.flush(); - } - -} diff --git a/src/org/apache/james/mime4j/message/HeaderImpl.java b/src/org/apache/james/mime4j/message/HeaderImpl.java new file mode 100644 index 000000000..8402acbe5 --- /dev/null +++ b/src/org/apache/james/mime4j/message/HeaderImpl.java @@ -0,0 +1,92 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.MimeIOException; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.field.Field; +import org.apache.james.mime4j.field.DefaultFieldParser; +import org.apache.james.mime4j.parser.AbstractContentHandler; +import org.apache.james.mime4j.parser.MimeStreamParser; +import org.apache.james.mime4j.stream.RawField; + +/** + * The header of an entity (see RFC 2045). + */ +public class HeaderImpl extends Header { + + /** + * Creates a new empty Header. + */ + public HeaderImpl() { + } + + /** + * Creates a new Header from the specified + * Header. The Header instance is initialized + * with a copy of the list of {@link Field}s of the specified + * Header. The Field objects are not copied + * because they are immutable and can safely be shared between headers. + * + * @param other + * header to copy. + */ + public HeaderImpl(Header other) { + for (Field otherField : other.getFields()) { + addField(otherField); + } + } + + /** + * Creates a new Header from the specified stream. + * + * @param is the stream to read the header from. + * + * @throws IOException on I/O errors. + * @throws MimeIOException on MIME protocol violations. + */ + public HeaderImpl( + final InputStream is, + final DecodeMonitor monitor) throws IOException, MimeIOException { + final MimeStreamParser parser = new MimeStreamParser(); + parser.setContentHandler(new AbstractContentHandler() { + @Override + public void endHeader() { + parser.stop(); + } + @Override + public void field(RawField field) throws MimeException { + Field parsedField = DefaultFieldParser.parse(field.getRaw(), monitor); + addField(parsedField); + } + }); + try { + parser.parse(is); + } catch (MimeException ex) { + throw new MimeIOException(ex); + } + } + +} diff --git a/src/org/apache/james/mime4j/message/MaximalBodyDescriptor.java b/src/org/apache/james/mime4j/message/MaximalBodyDescriptor.java new file mode 100644 index 000000000..bf6e7b676 --- /dev/null +++ b/src/org/apache/james/mime4j/message/MaximalBodyDescriptor.java @@ -0,0 +1,481 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import java.io.StringReader; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.datetime.DateTime; +import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; +import org.apache.james.mime4j.field.datetime.parser.ParseException; +import org.apache.james.mime4j.field.language.parser.ContentLanguageParser; +import org.apache.james.mime4j.field.mimeversion.parser.MimeVersionParser; +import org.apache.james.mime4j.field.structured.parser.StructuredFieldParser; +import org.apache.james.mime4j.stream.BodyDescriptor; +import org.apache.james.mime4j.stream.DefaultBodyDescriptor; +import org.apache.james.mime4j.stream.MutableBodyDescriptor; +import org.apache.james.mime4j.stream.RawField; +import org.apache.james.mime4j.util.MimeUtil; + +/** + * Parses and stores values for standard MIME header values. + * + */ +public class MaximalBodyDescriptor extends DefaultBodyDescriptor { + + private static final int DEFAULT_MINOR_VERSION = 0; + private static final int DEFAULT_MAJOR_VERSION = 1; + private boolean isMimeVersionSet; + private int mimeMinorVersion; + private int mimeMajorVersion; + private MimeException mimeVersionException; + private String contentId; + private boolean isContentIdSet; + private String contentDescription; + private boolean isContentDescriptionSet; + private String contentDispositionType; + private Map contentDispositionParameters; + private DateTime contentDispositionModificationDate; + private MimeException contentDispositionModificationDateParseException; + private DateTime contentDispositionCreationDate; + private MimeException contentDispositionCreationDateParseException; + private DateTime contentDispositionReadDate; + private MimeException contentDispositionReadDateParseException; + private long contentDispositionSize; + private MimeException contentDispositionSizeParseException; + private boolean isContentDispositionSet; + private List contentLanguage; + private MimeException contentLanguageParseException; + private boolean isContentLanguageSet; + private MimeException contentLocationParseException; + private String contentLocation; + private boolean isContentLocationSet; + private String contentMD5Raw; + private boolean isContentMD5Set; + + protected MaximalBodyDescriptor() { + this(null, null); + } + + public MaximalBodyDescriptor(final BodyDescriptor parent, final DecodeMonitor monitor) { + super(parent, monitor); + isMimeVersionSet = false; + mimeMajorVersion = DEFAULT_MAJOR_VERSION; + mimeMinorVersion = DEFAULT_MINOR_VERSION; + this.contentId = null; + this.isContentIdSet = false; + this.contentDescription = null; + this.isContentDescriptionSet = false; + this.contentDispositionType = null; + this.contentDispositionParameters = Collections.emptyMap(); + this.contentDispositionModificationDate = null; + this.contentDispositionModificationDateParseException = null; + this.contentDispositionCreationDate = null; + this.contentDispositionCreationDateParseException = null; + this.contentDispositionReadDate = null; + this.contentDispositionReadDateParseException = null; + this.contentDispositionSize = -1; + this.contentDispositionSizeParseException = null; + this.isContentDispositionSet = false; + this.contentLanguage = null; + this.contentLanguageParseException = null; + this.isContentIdSet = false; + this.contentLocation = null; + this.contentLocationParseException = null; + this.isContentLocationSet = false; + this.contentMD5Raw = null; + this.isContentMD5Set = false; + } + + public MutableBodyDescriptor newChild() { + return new MaximalBodyDescriptor(this, getDecodeMonitor()); + } + + @Override + public void addField(RawField field) throws MimeException { + String name = field.getName(); + String value = field.getBody(); + name = name.trim().toLowerCase(); + if (MimeUtil.MIME_HEADER_MIME_VERSION.equals(name) && !isMimeVersionSet) { + parseMimeVersion(value); + } else if (MimeUtil.MIME_HEADER_CONTENT_ID.equals(name) && !isContentIdSet) { + parseContentId(value); + } else if (MimeUtil.MIME_HEADER_CONTENT_DESCRIPTION.equals(name) && !isContentDescriptionSet) { + parseContentDescription(value); + } else if (MimeUtil.MIME_HEADER_CONTENT_DISPOSITION.equals(name) && !isContentDispositionSet) { + parseContentDisposition(value); + } else if (MimeUtil.MIME_HEADER_LANGAUGE.equals(name) && !isContentLanguageSet) { + parseLanguage(value); + } else if (MimeUtil.MIME_HEADER_LOCATION.equals(name) && !isContentLocationSet) { + parseLocation(value); + } else if (MimeUtil.MIME_HEADER_MD5.equals(name) && !isContentMD5Set) { + parseMD5(value); + } else { + super.addField(field); + } + } + + private void parseMD5(String value) { + isContentMD5Set = true; + if (value != null) { + contentMD5Raw = value.trim(); + } + } + + private void parseLocation(final String value) { + isContentLocationSet = true; + if (value != null) { + final StringReader stringReader = new StringReader(value); + final StructuredFieldParser parser = new StructuredFieldParser(stringReader); + try { + // From RFC2017 3.1 + /* + * Extraction of the URL string from the URL-parameter is even simpler: + * The enclosing quotes and any linear whitespace are removed and the + * remaining material is the URL string. + * Read more: http://www.faqs.org/rfcs/rfc2017.html#ixzz0aufO9nRL + */ + contentLocation = parser.parse().replaceAll("\\s", ""); + } catch (MimeException e) { + contentLocationParseException = e; + } + } + } + + private void parseLanguage(final String value) { + isContentLanguageSet = true; + if (value != null) { + try { + final ContentLanguageParser parser = new ContentLanguageParser(new StringReader(value)); + contentLanguage = parser.parse(); + } catch (MimeException e) { + contentLanguageParseException = e; + } + } + } + + private void parseContentDisposition(final String value) throws MimeException { + isContentDispositionSet = true; + contentDispositionParameters = DefaultBodyDescriptor.getHeaderParams(value, getDecodeMonitor()); + contentDispositionType = contentDispositionParameters.get(""); + + final String contentDispositionModificationDate + = contentDispositionParameters.get(MimeUtil.PARAM_MODIFICATION_DATE); + if (contentDispositionModificationDate != null) { + try { + this.contentDispositionModificationDate = parseDate(contentDispositionModificationDate); + } catch (ParseException e) { + this.contentDispositionModificationDateParseException = e; + } + } + + final String contentDispositionCreationDate + = contentDispositionParameters.get(MimeUtil.PARAM_CREATION_DATE); + if (contentDispositionCreationDate != null) { + try { + this.contentDispositionCreationDate = parseDate(contentDispositionCreationDate); + } catch (ParseException e) { + this.contentDispositionCreationDateParseException = e; + } + } + + final String contentDispositionReadDate + = contentDispositionParameters.get(MimeUtil.PARAM_READ_DATE); + if (contentDispositionReadDate != null) { + try { + this.contentDispositionReadDate = parseDate(contentDispositionReadDate); + } catch (ParseException e) { + this.contentDispositionReadDateParseException = e; + } + } + + final String size = contentDispositionParameters.get(MimeUtil.PARAM_SIZE); + if (size != null) { + try { + contentDispositionSize = Long.parseLong(size); + } catch (NumberFormatException e) { + this.contentDispositionSizeParseException = (MimeException) new MimeException(e.getMessage(), e).fillInStackTrace(); + } + } + contentDispositionParameters.remove(""); + } + + private DateTime parseDate(final String date) throws ParseException { + final StringReader stringReader = new StringReader(date); + final DateTimeParser parser = new DateTimeParser(stringReader); + DateTime result = parser.date_time(); + return result; + } + + private void parseContentDescription(String value) { + if (value == null) { + contentDescription = ""; + } else { + contentDescription = value.trim(); + } + isContentDescriptionSet = true; + } + + private void parseContentId(final String value) { + if (value == null) { + contentId = ""; + } else { + contentId = value.trim(); + } + isContentIdSet = true; + } + + private void parseMimeVersion(String value) { + final StringReader reader = new StringReader(value); + final MimeVersionParser parser = new MimeVersionParser(reader); + try { + parser.parse(); + final int major = parser.getMajorVersion(); + if (major != MimeVersionParser.INITIAL_VERSION_VALUE) { + mimeMajorVersion = major; + } + final int minor = parser.getMinorVersion(); + if (minor != MimeVersionParser.INITIAL_VERSION_VALUE) { + mimeMinorVersion = minor; + } + } catch (MimeException e) { + this.mimeVersionException = e; + } + isMimeVersionSet = true; + } + + /** + * Gets the MIME major version + * as specified by the MIME-Version + * header. + * Defaults to one. + * @return positive integer + */ + public int getMimeMajorVersion() { + return mimeMajorVersion; + } + + /** + * Gets the MIME minor version + * as specified by the MIME-Version + * header. + * Defaults to zero. + * @return positive integer + */ + public int getMimeMinorVersion() { + return mimeMinorVersion; + } + + + /** + * When the MIME version header exists but cannot be parsed + * this field will be contain the exception. + * @return MimeException if the mime header cannot + * be parsed, null otherwise + */ + public MimeException getMimeVersionParseException() { + return mimeVersionException; + } + + /** + * Gets the value of the RFC + * Content-Description header. + * @return value of the Content-Description when present, + * null otherwise + */ + public String getContentDescription() { + return contentDescription; + } + + /** + * Gets the value of the RFC + * Content-ID header. + * @return value of the Content-ID when present, + * null otherwise + */ + public String getContentId() { + return contentId; + } + + /** + * Gets the disposition type of the content-disposition field. + * The value is case insensitive and will be converted to lower case. + * See RFC2183. + * @return content disposition type, + * or null when this has not been set + */ + public String getContentDispositionType() { + return contentDispositionType; + } + + /** + * Gets the parameters of the content-disposition field. + * See RFC2183. + * @return parameter value strings indexed by parameter name strings, + * not null + */ + public Map getContentDispositionParameters() { + return contentDispositionParameters; + } + + /** + * Gets the filename parameter value of the content-disposition field. + * See RFC2183. + * @return filename parameter value, + * or null when it is not present + */ + public String getContentDispositionFilename() { + return contentDispositionParameters.get(MimeUtil.PARAM_FILENAME); + } + + /** + * Gets the modification-date parameter value of the content-disposition field. + * See RFC2183. + * @return modification-date parameter value, + * or null when this is not present + */ + public DateTime getContentDispositionModificationDate() { + return contentDispositionModificationDate; + } + + /** + * Gets any exception thrown during the parsing of {@link #getContentDispositionModificationDate()} + * @return ParseException when the modification-date parse fails, + * null otherwise + */ + public MimeException getContentDispositionModificationDateParseException() { + return contentDispositionModificationDateParseException; + } + + /** + * Gets the creation-date parameter value of the content-disposition field. + * See RFC2183. + * @return creation-date parameter value, + * or null when this is not present + */ + public DateTime getContentDispositionCreationDate() { + return contentDispositionCreationDate; + } + + /** + * Gets any exception thrown during the parsing of {@link #getContentDispositionCreationDate()} + * @return ParseException when the creation-date parse fails, + * null otherwise + */ + public MimeException getContentDispositionCreationDateParseException() { + return contentDispositionCreationDateParseException; + } + + /** + * Gets the read-date parameter value of the content-disposition field. + * See RFC2183. + * @return read-date parameter value, + * or null when this is not present + */ + public DateTime getContentDispositionReadDate() { + return contentDispositionReadDate; + } + + /** + * Gets any exception thrown during the parsing of {@link #getContentDispositionReadDate()} + * @return ParseException when the read-date parse fails, + * null otherwise + */ + public MimeException getContentDispositionReadDateParseException() { + return contentDispositionReadDateParseException; + } + + /** + * Gets the size parameter value of the content-disposition field. + * See RFC2183. + * @return size parameter value, + * or -1 if this size has not been set + */ + public long getContentDispositionSize() { + return contentDispositionSize; + } + + /** + * Gets any exception thrown during the parsing of {@link #getContentDispositionSize()} + * @return ParseException when the read-date parse fails, + * null otherwise + */ + public MimeException getContentDispositionSizeParseException() { + return contentDispositionSizeParseException; + } + + /** + * Get the content-language header values. + * Each applicable language tag will be returned in order. + * See RFC4646 + * http://tools.ietf.org/html/rfc4646. + * @return list of language tag Strings, + * or null if no header exists + */ + public List getContentLanguage() { + return contentLanguage; + } + + /** + * Gets any exception thrown during the parsing of {@link #getContentLanguage()} + * @return ParseException when the content-language parse fails, + * null otherwise + */ + public MimeException getContentLanguageParseException() { + return contentLanguageParseException; + } + + + /** + * Get the content-location header value. + * See RFC2557 + * @return the URL content-location + * or null if no header exists + */ + public String getContentLocation() { + return contentLocation; + } + + /** + * Gets any exception thrown during the parsing of {@link #getContentLocation()} + * @return ParseException when the content-language parse fails, + * null otherwise + */ + public MimeException getContentLocationParseException() { + return contentLocationParseException; + } + + /** + * Gets the raw, Base64 encoded value of the + * Content-MD5 field. + * See RFC1864. + * @return raw encoded content-md5 + * or null if no header exists + */ + public String getContentMD5Raw() { + return contentMD5Raw; + } + + +} diff --git a/src/org/apache/james/mime4j/message/BinaryBody.java b/src/org/apache/james/mime4j/message/MaximalBodyDescriptorFactory.java similarity index 72% rename from src/org/apache/james/mime4j/message/BinaryBody.java rename to src/org/apache/james/mime4j/message/MaximalBodyDescriptorFactory.java index fc05a7d8a..7dc3b78c0 100644 --- a/src/org/apache/james/mime4j/message/BinaryBody.java +++ b/src/org/apache/james/mime4j/message/MaximalBodyDescriptorFactory.java @@ -19,24 +19,14 @@ package org.apache.james.mime4j.message; -import java.io.IOException; -import java.io.InputStream; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.stream.MutableBodyDescriptor; +import org.apache.james.mime4j.stream.MutableBodyDescriptorFactory; +public class MaximalBodyDescriptorFactory implements MutableBodyDescriptorFactory { + + public MutableBodyDescriptor newInstance(DecodeMonitor monitor) { + return new MaximalBodyDescriptor(null, monitor); + } -/** - * Interface implemented by bodies containing binary data. - * - * - * @version $Id: BinaryBody.java,v 1.3 2004/10/02 12:41:11 ntherning Exp $ - */ -public interface BinaryBody extends Body { - - /** - * Gets a InputStream which reads the bytes of the - * body. - * - * @return the stream. - * @throws IOException on I/O errors. - */ - InputStream getInputStream() throws IOException; } diff --git a/src/org/apache/james/mime4j/message/MemoryBinaryBody.java b/src/org/apache/james/mime4j/message/MemoryBinaryBody.java deleted file mode 100644 index fb78ff080..000000000 --- a/src/org/apache/james/mime4j/message/MemoryBinaryBody.java +++ /dev/null @@ -1,90 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.message; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.james.mime4j.util.TempPath; -import org.apache.james.mime4j.util.TempStorage; - - -/** - * Binary body backed by a {@link org.apache.james.mime4j.util.TempFile}. - * - * - * @version $Id: TempFileBinaryBody.java,v 1.2 2004/10/02 12:41:11 ntherning Exp $ - */ -class MemoryBinaryBody extends AbstractBody implements BinaryBody { - private static Log log = LogFactory.getLog(MemoryBinaryBody.class); - - private Entity parent = null; - private byte[] tempFile = null; - - /** - * Use the given InputStream to build the TemporyFileBinaryBody - * - * @param is the InputStream to use as source - * @throws IOException - */ - public MemoryBinaryBody(InputStream is) throws IOException { - - TempPath tempPath = TempStorage.getInstance().getRootTempPath(); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - IOUtils.copy(is, out); - out.close(); - tempFile = out.toByteArray(); - } - - /** - * @see org.apache.james.mime4j.message.AbstractBody#getParent() - */ - public Entity getParent() { - return parent; - } - - /** - * @see org.apache.james.mime4j.message.AbstractBody#setParent(org.apache.james.mime4j.message.Entity) - */ - public void setParent(Entity parent) { - this.parent = parent; - } - - /** - * @see org.apache.james.mime4j.message.BinaryBody#getInputStream() - */ - public InputStream getInputStream() throws IOException { - return new ByteArrayInputStream(tempFile); - } - - /** - * @see org.apache.james.mime4j.message.Body#writeTo(java.io.OutputStream) - */ - public void writeTo(OutputStream out) throws IOException { - IOUtils.copy(getInputStream(),out); - } -} diff --git a/src/org/apache/james/mime4j/message/MemoryTextBody.java b/src/org/apache/james/mime4j/message/MemoryTextBody.java deleted file mode 100644 index ce4318a4d..000000000 --- a/src/org/apache/james/mime4j/message/MemoryTextBody.java +++ /dev/null @@ -1,116 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.message; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.Reader; -import java.io.UnsupportedEncodingException; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.james.mime4j.util.CharsetUtil; -import org.apache.james.mime4j.util.TempPath; -import org.apache.james.mime4j.util.TempStorage; - - -/** - * Text body backed by a {@link org.apache.james.mime4j.util.TempFile}. - * - * - * @version $Id: TempFileTextBody.java,v 1.3 2004/10/25 07:26:46 ntherning Exp $ - */ -class MemoryTextBody extends AbstractBody implements TextBody { - private static Log log = LogFactory.getLog(MemoryTextBody.class); - - private String mimeCharset = null; - private byte[] tempFile = null; - - public MemoryTextBody(InputStream is) throws IOException { - this(is, null); - } - - public MemoryTextBody(InputStream is, String mimeCharset) - throws IOException { - - this.mimeCharset = mimeCharset; - - TempPath tempPath = TempStorage.getInstance().getRootTempPath(); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - IOUtils.copy(is, out); - out.close(); - tempFile = out.toByteArray(); - } - - /** - * @see org.apache.james.mime4j.message.TextBody#getReader() - */ - public Reader getReader() throws UnsupportedEncodingException, IOException { - String javaCharset = null; - if (mimeCharset != null) { - javaCharset = CharsetUtil.toJavaCharset(mimeCharset); - } - - if (javaCharset == null) { - javaCharset = "ISO-8859-1"; - - if (log.isWarnEnabled()) { - if (mimeCharset == null) { - log.warn("No MIME charset specified. Using " + javaCharset - + " instead."); - } else { - log.warn("MIME charset '" + mimeCharset + "' has no " - + "corresponding Java charset. Using " + javaCharset - + " instead."); - } - } - } - /* - if (log.isWarnEnabled()) { - if (mimeCharset == null) { - log.warn("No MIME charset specified. Using the " - + "platform's default charset."); - } else { - log.warn("MIME charset '" + mimeCharset + "' has no " - + "corresponding Java charset. Using the " - + "platform's default charset."); - } - } - - return new InputStreamReader(tempFile.getInputStream()); - }*/ - - return new InputStreamReader(new ByteArrayInputStream(tempFile), javaCharset); - } - - - /** - * @see org.apache.james.mime4j.message.Body#writeTo(java.io.OutputStream) - */ - public void writeTo(OutputStream out) throws IOException { - IOUtils.copy(new ByteArrayInputStream(tempFile), out); - } -} diff --git a/src/org/apache/james/mime4j/message/Message.java b/src/org/apache/james/mime4j/message/Message.java deleted file mode 100644 index 14aacfc2b..000000000 --- a/src/org/apache/james/mime4j/message/Message.java +++ /dev/null @@ -1,256 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.message; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Stack; - -import org.apache.james.mime4j.BodyDescriptor; -import org.apache.james.mime4j.ContentHandler; -import org.apache.james.mime4j.MimeStreamParser; -import org.apache.james.mime4j.decoder.Base64InputStream; -import org.apache.james.mime4j.decoder.QuotedPrintableInputStream; -import org.apache.james.mime4j.field.Field; -import org.apache.james.mime4j.field.UnstructuredField; - - -/** - * Represents a MIME message. The following code parses a stream into a - * Message object. - * - *
- *      Message msg = new Message(new BufferedInputStream(
- *                                      new FileInputStream("mime.msg")));
- * 
- * - * - * - * @version $Id: Message.java,v 1.3 2004/10/02 12:41:11 ntherning Exp $ - */ -public class Message extends Entity implements Body { - - /** - * Creates a new empty Message. - */ - public Message() { - } - - /** - * Parses the specified MIME message stream into a Message - * instance. - * - * @param is the stream to parse. - * @throws IOException on I/O errors. - */ - public Message(InputStream is) throws IOException { - MimeStreamParser parser = new MimeStreamParser(); - parser.setContentHandler(new MessageBuilder()); - parser.parse(is); - } - - - /** - * Gets the Subject field. - * - * @return the Subject field or null if it - * doesn't exist. - */ - public UnstructuredField getSubject() { - return (UnstructuredField) getHeader().getField(Field.SUBJECT); - } - - /** - * - * @see org.apache.james.mime4j.message.Entity#writeTo(java.io.OutputStream) - */ - public void writeTo(OutputStream out) throws IOException { - getHeader().writeTo(out); - - Body body = getBody(); - if (body instanceof Multipart) { - Multipart mp = (Multipart) body; - mp.writeTo(out); - } else { - body.writeTo(out); - } - } - - - private class MessageBuilder implements ContentHandler { - private Stack stack = new Stack(); - - public MessageBuilder() { - } - - private void expect(Class c) { - if (!c.isInstance(stack.peek())) { - throw new IllegalStateException("Internal stack error: " - + "Expected '" + c.getName() + "' found '" - + stack.peek().getClass().getName() + "'"); - } - } - - /** - * @see org.apache.james.mime4j.ContentHandler#startMessage() - */ - public void startMessage() { - if (stack.isEmpty()) { - stack.push(Message.this); - } else { - expect(Entity.class); - Message m = new Message(); - ((Entity) stack.peek()).setBody(m); - stack.push(m); - } - } - - /** - * @see org.apache.james.mime4j.ContentHandler#endMessage() - */ - public void endMessage() { - expect(Message.class); - stack.pop(); - } - - /** - * @see org.apache.james.mime4j.ContentHandler#startHeader() - */ - public void startHeader() { - stack.push(new Header()); - } - - /** - * @see org.apache.james.mime4j.ContentHandler#field(java.lang.String) - */ - public void field(String fieldData) { - expect(Header.class); - ((Header) stack.peek()).addField(Field.parse(fieldData)); - } - - /** - * @see org.apache.james.mime4j.ContentHandler#endHeader() - */ - public void endHeader() { - expect(Header.class); - Header h = (Header) stack.pop(); - expect(Entity.class); - ((Entity) stack.peek()).setHeader(h); - } - - /** - * @see org.apache.james.mime4j.ContentHandler#startMultipart(org.apache.james.mime4j.BodyDescriptor) - */ - public void startMultipart(BodyDescriptor bd) { - expect(Entity.class); - - Entity e = (Entity) stack.peek(); - Multipart multiPart = new Multipart(); - e.setBody(multiPart); - stack.push(multiPart); - } - - /** - * @see org.apache.james.mime4j.ContentHandler#body(org.apache.james.mime4j.BodyDescriptor, java.io.InputStream) - */ - public void body(BodyDescriptor bd, InputStream is) throws IOException { - expect(Entity.class); - - String enc = bd.getTransferEncoding(); - if ("base64".equals(enc)) { - is = new Base64InputStream(is); - } else if ("quoted-printable".equals(enc)) { - is = new QuotedPrintableInputStream(is); - } - - Body body = null; - if (bd.getMimeType().startsWith("text/")) { - body = new MemoryTextBody(is, bd.getCharset()); - } else { - body = new MemoryBinaryBody(is); - } - - ((Entity) stack.peek()).setBody(body); - } - - /** - * @see org.apache.james.mime4j.ContentHandler#endMultipart() - */ - public void endMultipart() { - stack.pop(); - } - - /** - * @see org.apache.james.mime4j.ContentHandler#startBodyPart() - */ - public void startBodyPart() { - expect(Multipart.class); - - BodyPart bodyPart = new BodyPart(); - ((Multipart) stack.peek()).addBodyPart(bodyPart); - stack.push(bodyPart); - } - - /** - * @see org.apache.james.mime4j.ContentHandler#endBodyPart() - */ - public void endBodyPart() { - expect(BodyPart.class); - stack.pop(); - } - - /** - * @see org.apache.james.mime4j.ContentHandler#epilogue(java.io.InputStream) - */ - public void epilogue(InputStream is) throws IOException { - expect(Multipart.class); - StringBuffer sb = new StringBuffer(); - int b; - while ((b = is.read()) != -1) { - sb.append((char) b); - } - ((Multipart) stack.peek()).setEpilogue(sb.toString()); - } - - /** - * @see org.apache.james.mime4j.ContentHandler#preamble(java.io.InputStream) - */ - public void preamble(InputStream is) throws IOException { - expect(Multipart.class); - StringBuffer sb = new StringBuffer(); - int b; - while ((b = is.read()) != -1) { - sb.append((char) b); - } - ((Multipart) stack.peek()).setPreamble(sb.toString()); - } - - /** - * TODO: Implement me - * - * @see org.apache.james.mime4j.ContentHandler#raw(java.io.InputStream) - */ - public void raw(InputStream is) throws IOException { - throw new UnsupportedOperationException("Not supported"); - } - - } -} diff --git a/src/org/apache/james/mime4j/message/MessageBuilderFactoryImpl.java b/src/org/apache/james/mime4j/message/MessageBuilderFactoryImpl.java new file mode 100644 index 000000000..4add9d1f6 --- /dev/null +++ b/src/org/apache/james/mime4j/message/MessageBuilderFactoryImpl.java @@ -0,0 +1,73 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mime4j.message; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.dom.MessageBuilder; +import org.apache.james.mime4j.dom.MessageBuilderFactory; +import org.apache.james.mime4j.storage.StorageProvider; +import org.apache.james.mime4j.stream.MimeEntityConfig; +import org.apache.james.mime4j.stream.MutableBodyDescriptorFactory; + +/** + * The default MessageBuilderFactory bundled with Mime4j. + * + * Supports the "StorageProvider", "MimeEntityConfig" and "MutableBodyDescriptorFactory" + * attributes. + */ +public class MessageBuilderFactoryImpl extends MessageBuilderFactory { + + private StorageProvider storageProvider = null; + private MimeEntityConfig mimeEntityConfig = null; + private MutableBodyDescriptorFactory mutableBodyDescriptorFactory = null; + + @Override + public MessageBuilder newMessageBuilder() throws MimeException { + MessageBuilderImpl m = new MessageBuilderImpl(); + if (storageProvider != null) m.setStorageProvider(storageProvider); + if (mimeEntityConfig != null) m.setMimeEntityConfig(mimeEntityConfig); + if (mutableBodyDescriptorFactory != null) m.setMutableBodyDescriptorFactory(mutableBodyDescriptorFactory); + return m; + } + + @Override + public void setAttribute(String name, Object value) + throws IllegalArgumentException { + if ("StorageProvider".equals(name)) { + if (value instanceof StorageProvider) { + this.storageProvider = (StorageProvider) value; + return; + } else throw new IllegalArgumentException("Unsupported attribute value type for "+name+", expected a StorageProvider"); + } else if ("MimeEntityConfig".equals(name)) { + if (value instanceof MimeEntityConfig) { + this.mimeEntityConfig = (MimeEntityConfig) value; + return; + } else throw new IllegalArgumentException("Unsupported attribute value type for "+name+", expected a MimeEntityConfig"); + } else if ("MutableBodyDescriptorFactory".equals(name)) { + if (value instanceof MutableBodyDescriptorFactory) { + this.mutableBodyDescriptorFactory = (MutableBodyDescriptorFactory) value; + return; + } else throw new IllegalArgumentException("Unsupported attribute value type for "+name+", expected a MutableBodyDescriptorFactory"); + } + + throw new IllegalArgumentException("Unsupported attribute: "+name); + + } + +} diff --git a/src/org/apache/james/mime4j/message/MessageBuilderImpl.java b/src/org/apache/james/mime4j/message/MessageBuilderImpl.java new file mode 100644 index 000000000..109a4890d --- /dev/null +++ b/src/org/apache/james/mime4j/message/MessageBuilderImpl.java @@ -0,0 +1,73 @@ +package org.apache.james.mime4j.message; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.MessageBuilder; +import org.apache.james.mime4j.storage.StorageProvider; +import org.apache.james.mime4j.stream.MimeEntityConfig; +import org.apache.james.mime4j.stream.MutableBodyDescriptorFactory; + +/** + * Default MessageBuilder implementation delegating Message parsing to the "legacy" + * MessageImpl object. + */ +public class MessageBuilderImpl extends MessageBuilder { + + private StorageProvider storageProvider = null; + private DecodeMonitor decodeMonitor = null; + private MimeEntityConfig mimeEntityConfig = null; + private MutableBodyDescriptorFactory mutableBodyDescriptorFactory = null; + private boolean flatMode = false; + private boolean contentDecoding = true; + + public MessageBuilderImpl() { + } + + @Override + public Message newMessage() { + return new MessageImpl(); + } + + @Override + public Message newMessage(Message source) { + return new MessageImpl(source); + } + + @Override + public Message parse(InputStream source) throws MimeException, IOException { + return new MessageImpl(source, mimeEntityConfig, storageProvider, mutableBodyDescriptorFactory, decodeMonitor, contentDecoding, flatMode); + } + + @Override + public void setDecodeMonitor(DecodeMonitor decodeMonitor) { + this.decodeMonitor = decodeMonitor; + } + + public void setStorageProvider(StorageProvider storageProvider) { + this.storageProvider = storageProvider; + } + + public void setMimeEntityConfig(MimeEntityConfig mimeEntityConfig) { + this.mimeEntityConfig = mimeEntityConfig; + } + + public void setMutableBodyDescriptorFactory( + MutableBodyDescriptorFactory mutableBodyDescriptorFactory) { + this.mutableBodyDescriptorFactory = mutableBodyDescriptorFactory; + } + + @Override + public void setContentDecoding(boolean contentDecoding) { + this.contentDecoding = contentDecoding; + } + + @Override + public void setFlatMode() { + this.flatMode = true; + } + +} diff --git a/src/org/apache/james/mime4j/message/MessageImpl.java b/src/org/apache/james/mime4j/message/MessageImpl.java new file mode 100644 index 000000000..bf7a99d87 --- /dev/null +++ b/src/org/apache/james/mime4j/message/MessageImpl.java @@ -0,0 +1,287 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.MimeIOException; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.Body; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.Multipart; +import org.apache.james.mime4j.dom.SingleBody; +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.field.AddressListField; +import org.apache.james.mime4j.dom.field.ContentDispositionField; +import org.apache.james.mime4j.dom.field.ContentTransferEncodingField; +import org.apache.james.mime4j.dom.field.ContentTypeField; +import org.apache.james.mime4j.dom.field.DateTimeField; +import org.apache.james.mime4j.dom.field.FieldName; +import org.apache.james.mime4j.dom.field.MailboxField; +import org.apache.james.mime4j.dom.field.MailboxListField; +import org.apache.james.mime4j.dom.field.UnstructuredField; +import org.apache.james.mime4j.field.ContentTransferEncodingFieldImpl; +import org.apache.james.mime4j.field.ContentTypeFieldImpl; +import org.apache.james.mime4j.field.Fields; +import org.apache.james.mime4j.parser.MimeStreamParser; +import org.apache.james.mime4j.storage.DefaultStorageProvider; +import org.apache.james.mime4j.storage.StorageProvider; +import org.apache.james.mime4j.stream.MimeEntityConfig; +import org.apache.james.mime4j.stream.MutableBodyDescriptorFactory; +import org.apache.james.mime4j.util.MimeUtil; + +/** + * Represents a MIME message. The following code parses a stream into a + * Message object. + * + *
+ * Message msg = new Message(new FileInputStream("mime.msg"));
+ * 
+ */ +public class MessageImpl extends Message { + + /** + * Creates a new empty Message. + */ + public MessageImpl() { + } + + /** + * Creates a new Message from the specified + * Message. The Message instance is + * initialized with copies of header and body of the specified + * Message. The parent entity of the new message is + * null. + * + * @param other + * message to copy. + * @throws UnsupportedOperationException + * if other contains a {@link SingleBody} that + * does not support the {@link SingleBody#copy() copy()} + * operation. + * @throws IllegalArgumentException + * if other contains a Body that + * is neither a {@link MessageImpl}, {@link Multipart} or + * {@link SingleBody}. + */ + public MessageImpl(Message other) { + if (other.getHeader() != null) { + setHeader(new Header(other.getHeader())); + } + + if (other.getBody() != null) { + Body bodyCopy = BodyCopier.copy(other.getBody()); + setBody(bodyCopy); + } + } + + /** + * Parses the specified MIME message stream into a Message + * instance. + * + * @param is + * the stream to parse. + * @throws IOException + * on I/O errors. + * @throws MimeIOException + * on MIME protocol violations. + */ + public MessageImpl(InputStream is) throws IOException, MimeIOException { + this(is, null, DefaultStorageProvider.getInstance()); + } + + /** + * Parses the specified MIME message stream into a Message + * instance using given {@link MimeEntityConfig}. + * + * @param is + * the stream to parse. + * @throws IOException + * on I/O errors. + * @throws MimeIOException + * on MIME protocol violations. + */ + public MessageImpl(InputStream is, MimeEntityConfig config) throws IOException, + MimeIOException { + this(is, config, DefaultStorageProvider.getInstance()); + } + + /** + * Parses the specified MIME message stream into a Message + * instance using given {@link MimeEntityConfig} and {@link StorageProvider}. + * + * @param is + * the stream to parse. + * @param config + * {@link MimeEntityConfig} to use. + * @param storageProvider + * {@link StorageProvider} to use for storing text and binary + * message bodies. + * @param bodyDescFactory + * {@link MutableBodyDescriptorFactory} to use for creating body descriptors. + * @throws IOException + * on I/O errors. + * @throws MimeIOException + * on MIME protocol violations. + */ + public MessageImpl( + final InputStream is, + final MimeEntityConfig config, + final StorageProvider storageProvider, + final MutableBodyDescriptorFactory bodyDescFactory, + final DecodeMonitor monitor) throws IOException, MimeIOException { + this(is, config, storageProvider, bodyDescFactory, monitor, true, false); + } + + /** + * Parses the specified MIME message stream into a Message + * instance using given {@link MimeEntityConfig} and {@link StorageProvider}. + * + * @param is + * the stream to parse. + * @param config + * {@link MimeEntityConfig} to use. + * @param storageProvider + * {@link StorageProvider} to use for storing text and binary + * message bodies. + * @param bodyDescFactory + * {@link MutableBodyDescriptorFactory} to use for creating body descriptors. + * @throws IOException + * on I/O errors. + * @throws MimeIOException + * on MIME protocol violations. + */ + public MessageImpl( + final InputStream is, + final MimeEntityConfig config, + final StorageProvider storageProvider, + final MutableBodyDescriptorFactory bodyDescFactory, + final DecodeMonitor monitor, + boolean contentDecoding, + boolean flatMode) throws IOException, MimeIOException { + try { + DecodeMonitor mon = monitor != null ? monitor : DecodeMonitor.SILENT; + MimeStreamParser parser = new MimeStreamParser(config, bodyDescFactory, mon); + parser.setContentHandler(new EntityBuilder(this, storageProvider, mon)); + parser.setContentDecoding(contentDecoding); + if (flatMode) parser.setFlat(true); + parser.parse(is); + } catch (MimeException e) { + throw new MimeIOException(e); + } + } + + public MessageImpl( + final InputStream is, + final MimeEntityConfig config, + final StorageProvider storageProvider, + final MutableBodyDescriptorFactory bodyDescFactory) throws IOException, MimeIOException { + this(is, config, storageProvider, bodyDescFactory, null); + } + + public MessageImpl( + final InputStream is, + final MimeEntityConfig config, + final StorageProvider storageProvider) throws IOException, MimeIOException { + this(is, config, storageProvider, null, null); + } + + /** + * @see org.apache.james.mime4j.dom.Message#writeTo(java.io.OutputStream) + */ + public void writeTo(OutputStream out) throws IOException { + MessageWriter.DEFAULT.writeEntity(this, out); + } + + @Override + protected String newUniqueBoundary() { + return MimeUtil.createUniqueBoundary(); + } + + protected UnstructuredField newMessageId(String hostname) { + return Fields.messageId(hostname); + } + + protected DateTimeField newDate(Date date, TimeZone zone) { + return Fields.date(FieldName.DATE, date, zone); + } + + protected MailboxField newMailbox(String fieldName, Mailbox mailbox) { + return Fields.mailbox(fieldName, mailbox); + } + + protected MailboxListField newMailboxList(String fieldName, + Collection mailboxes) { + return Fields.mailboxList(fieldName, mailboxes); + } + + protected AddressListField newAddressList(String fieldName, + Collection
addresses) { + return Fields.addressList(fieldName, addresses); + } + + protected UnstructuredField newSubject(String subject) { + return Fields.subject(subject); + } + + protected ContentDispositionField newContentDisposition( + String dispositionType, String filename, long size, + Date creationDate, Date modificationDate, Date readDate) { + return Fields.contentDisposition(dispositionType, filename, size, + creationDate, modificationDate, readDate); + } + + protected ContentDispositionField newContentDisposition( + String dispositionType, Map parameters) { + return Fields.contentDisposition(dispositionType, parameters); + } + + protected ContentTypeField newContentType(String mimeType, + Map parameters) { + return Fields.contentType(mimeType, parameters); + } + + protected ContentTransferEncodingField newContentTransferEncoding( + String contentTransferEncoding) { + return Fields.contentTransferEncoding(contentTransferEncoding); + } + + protected String calcTransferEncoding(ContentTransferEncodingField f) { + return ContentTransferEncodingFieldImpl.getEncoding(f); + } + + protected String calcMimeType(ContentTypeField child, ContentTypeField parent) { + return ContentTypeFieldImpl.getMimeType(child, parent); + } + + protected String calcCharset(ContentTypeField contentType) { + return ContentTypeFieldImpl.getCharset(contentType); + } + +} diff --git a/src/org/apache/james/mime4j/message/MessageWriter.java b/src/org/apache/james/mime4j/message/MessageWriter.java new file mode 100644 index 000000000..93c8c018b --- /dev/null +++ b/src/org/apache/james/mime4j/message/MessageWriter.java @@ -0,0 +1,240 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.james.mime4j.codec.CodecUtil; +import org.apache.james.mime4j.dom.BinaryBody; +import org.apache.james.mime4j.dom.Body; +import org.apache.james.mime4j.dom.Entity; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.Multipart; +import org.apache.james.mime4j.dom.SingleBody; +import org.apache.james.mime4j.dom.field.ContentTypeField; +import org.apache.james.mime4j.dom.field.Field; +import org.apache.james.mime4j.dom.field.FieldName; +import org.apache.james.mime4j.util.ByteArrayBuffer; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; +import org.apache.james.mime4j.util.MimeUtil; + +/** + * Writes a message (or a part of a message) to an output stream. + *

+ * This class cannot be instantiated; instead the static instance + * {@link #DEFAULT} implements the default strategy for writing a message. + *

+ * This class may be subclassed to implement custom strategies for writing + * messages. + */ +public class MessageWriter { + + private static final byte[] CRLF = { '\r', '\n' }; + private static final byte[] DASHES = { '-', '-' }; + + /** + * The default message writer. + */ + public static final MessageWriter DEFAULT = new MessageWriter(); + + /** + * Protected constructor prevents direct instantiation. + */ + protected MessageWriter() { + } + + /** + * Write the specified Body to the specified + * OutputStream. + * + * @param body + * the Body to write. + * @param out + * the OutputStream to write to. + * @throws IOException + * if an I/O error occurs. + */ + public void writeBody(Body body, OutputStream out) throws IOException { + if (body instanceof Message) { + writeEntity((Message) body, out); + } else if (body instanceof Multipart) { + writeMultipart((Multipart) body, out); + } else if (body instanceof SingleBody) { + ((SingleBody) body).writeTo(out); + } else + throw new IllegalArgumentException("Unsupported body class"); + } + + /** + * Write the specified Entity to the specified + * OutputStream. + * + * @param entity + * the Entity to write. + * @param out + * the OutputStream to write to. + * @throws IOException + * if an I/O error occurs. + */ + public void writeEntity(Entity entity, OutputStream out) throws IOException { + final Header header = entity.getHeader(); + if (header == null) + throw new IllegalArgumentException("Missing header"); + + writeHeader(header, out); + + final Body body = entity.getBody(); + if (body == null) + throw new IllegalArgumentException("Missing body"); + + boolean binaryBody = body instanceof BinaryBody; + OutputStream encOut = encodeStream(out, entity + .getContentTransferEncoding(), binaryBody); + + writeBody(body, encOut); + + // close if wrapped (base64 or quoted-printable) + if (encOut != out) + encOut.close(); + } + + /** + * Write the specified Multipart to the specified + * OutputStream. + * + * @param multipart + * the Multipart to write. + * @param out + * the OutputStream to write to. + * @throws IOException + * if an I/O error occurs. + */ + public void writeMultipart(Multipart multipart, OutputStream out) + throws IOException { + ContentTypeField contentType = getContentType(multipart); + + ByteSequence boundary = getBoundary(contentType); + + ByteSequence preamble; + ByteSequence epilogue; + if (multipart instanceof MultipartImpl) { + preamble = ((MultipartImpl) multipart).getPreambleRaw(); + epilogue = ((MultipartImpl) multipart).getEpilogueRaw(); + } else { + preamble = multipart.getPreamble() != null ? ContentUtil.encode(multipart.getPreamble()) : null; + epilogue = multipart.getEpilogue() != null ? ContentUtil.encode(multipart.getEpilogue()) : null; + } + if (preamble != null) { + writeBytes(preamble, out); + out.write(CRLF); + } + + for (Entity bodyPart : multipart.getBodyParts()) { + out.write(DASHES); + writeBytes(boundary, out); + out.write(CRLF); + + writeEntity(bodyPart, out); + out.write(CRLF); + } + + out.write(DASHES); + writeBytes(boundary, out); + out.write(DASHES); + if (epilogue != null) { + out.write(CRLF); + writeBytes(epilogue, out); + } + } + + /** + * Write the specified Header to the specified + * OutputStream. + * + * @param header + * the Header to write. + * @param out + * the OutputStream to write to. + * @throws IOException + * if an I/O error occurs. + */ + public void writeHeader(Header header, OutputStream out) throws IOException { + for (Field field : header) { + field.writeTo(out); + out.write(CRLF); + } + + out.write(CRLF); + } + + protected OutputStream encodeStream(OutputStream out, String encoding, + boolean binaryBody) throws IOException { + if (MimeUtil.isBase64Encoding(encoding)) { + return CodecUtil.wrapBase64(out); + } else if (MimeUtil.isQuotedPrintableEncoded(encoding)) { + return CodecUtil.wrapQuotedPrintable(out, binaryBody); + } else { + return out; + } + } + + private ContentTypeField getContentType(Multipart multipart) { + Entity parent = multipart.getParent(); + if (parent == null) + throw new IllegalArgumentException( + "Missing parent entity in multipart"); + + Header header = parent.getHeader(); + if (header == null) + throw new IllegalArgumentException( + "Missing header in parent entity"); + + ContentTypeField contentType = (ContentTypeField) header + .getField(FieldName.CONTENT_TYPE); + if (contentType == null) + throw new IllegalArgumentException( + "Content-Type field not specified"); + + return contentType; + } + + private ByteSequence getBoundary(ContentTypeField contentType) { + String boundary = contentType.getBoundary(); + if (boundary == null) + throw new IllegalArgumentException( + "Multipart boundary not specified"); + + return ContentUtil.encode(boundary); + } + + private void writeBytes(ByteSequence byteSequence, OutputStream out) + throws IOException { + if (byteSequence instanceof ByteArrayBuffer) { + ByteArrayBuffer bab = (ByteArrayBuffer) byteSequence; + out.write(bab.buffer(), 0, bab.length()); + } else { + out.write(byteSequence.toByteArray()); + } + } + +} diff --git a/src/org/apache/james/mime4j/message/Multipart.java b/src/org/apache/james/mime4j/message/Multipart.java deleted file mode 100644 index a3a255b71..000000000 --- a/src/org/apache/james/mime4j/message/Multipart.java +++ /dev/null @@ -1,203 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.message; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import org.apache.james.mime4j.field.ContentTypeField; -import org.apache.james.mime4j.field.Field; -import org.apache.james.mime4j.util.CharsetUtil; - -/** - * Represents a MIME multipart body (see RFC 2045).A multipart body has a - * ordered list of body parts. The multipart body also has a preamble and - * epilogue. The preamble consists of whatever characters appear before the - * first body part while the epilogue consists of whatever characters come - * after the last body part. - * - * - * @version $Id: Multipart.java,v 1.3 2004/10/02 12:41:11 ntherning Exp $ - */ -public class Multipart implements Body { - private String preamble = ""; - private String epilogue = ""; - private List bodyParts = new LinkedList(); - private Entity parent = null; - private String subType = "alternative"; - - /** - * Creates a new empty Multipart instance. - */ - public Multipart() { - } - - /** - * Gets the multipart sub-type. E.g. alternative (the default) - * or parallel. See RFC 2045 for common sub-types and their - * meaning. - * - * @return the multipart sub-type. - */ - public String getSubType() { - return subType; - } - - /** - * Sets the multipart sub-type. E.g. alternative - * or parallel. See RFC 2045 for common sub-types and their - * meaning. - * - * @param subType the sub-type. - */ - public void setSubType(String subType) { - this.subType = subType; - } - - /** - * @see org.apache.james.mime4j.message.Body#getParent() - */ - public Entity getParent() { - return parent; - } - - /** - * @see org.apache.james.mime4j.message.Body#setParent(org.apache.james.mime4j.message.Entity) - */ - public void setParent(Entity parent) { - this.parent = parent; - for (Iterator it = bodyParts.iterator(); it.hasNext();) { - ((BodyPart) it.next()).setParent(parent); - } - } - - /** - * Gets the epilogue. - * - * @return the epilogue. - */ - public String getEpilogue() { - return epilogue; - } - - /** - * Sets the epilogue. - * - * @param epilogue the epilogue. - */ - public void setEpilogue(String epilogue) { - this.epilogue = epilogue; - } - - /** - * Gets the list of body parts. The list is immutable. - * - * @return the list of BodyPart objects. - */ - public List getBodyParts() { - return Collections.unmodifiableList(bodyParts); - } - - /** - * Sets the list of body parts. - * - * @param bodyParts the new list of BodyPart objects. - */ - public void setBodyParts(List bodyParts) { - this.bodyParts = bodyParts; - for (Iterator it = bodyParts.iterator(); it.hasNext();) { - ((BodyPart) it.next()).setParent(parent); - } - } - - /** - * Adds a body part to the end of the list of body parts. - * - * @param bodyPart the body part. - */ - public void addBodyPart(BodyPart bodyPart) { - bodyParts.add(bodyPart); - bodyPart.setParent(parent); - } - - /** - * Gets the preamble. - * - * @return the preamble. - */ - public String getPreamble() { - return preamble; - } - - /** - * Sets the preamble. - * - * @param preamble the preamble. - */ - public void setPreamble(String preamble) { - this.preamble = preamble; - } - - /** - * - * @see org.apache.james.mime4j.message.Body#writeTo(java.io.OutputStream) - */ - public void writeTo(OutputStream out) throws IOException { - String boundary = getBoundary(); - List bodyParts = getBodyParts(); - - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, CharsetUtil.getCharset(getCharset())),8192); - - writer.write(getPreamble() + "\r\n"); - - for (int i = 0; i < bodyParts.size(); i++) { - writer.write(boundary + "\r\n"); - ((BodyPart) bodyParts.get(i)).writeTo(out); - } - - writer.write(getEpilogue() + "\r\n"); - writer.write(boundary + "--" + "\r\n"); - - } - - /** - * Return the boundory of the parent Entity - * - * @return boundery - */ - private String getBoundary() { - Entity e = getParent(); - ContentTypeField cField = (ContentTypeField) e.getHeader().getField( - Field.CONTENT_TYPE); - return cField.getBoundary(); - } - - private String getCharset() { - Entity e = getParent(); - String charString = ((ContentTypeField) e.getHeader().getField(Field.CONTENT_TYPE)).getCharset(); - return charString; - } -} diff --git a/src/org/apache/james/mime4j/message/MultipartImpl.java b/src/org/apache/james/mime4j/message/MultipartImpl.java new file mode 100644 index 000000000..14f0b5829 --- /dev/null +++ b/src/org/apache/james/mime4j/message/MultipartImpl.java @@ -0,0 +1,169 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import org.apache.james.mime4j.dom.Entity; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.Multipart; +import org.apache.james.mime4j.dom.SingleBody; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; + +/** + * Represents a MIME multipart body (see RFC 2045).A multipart body has a + * ordered list of body parts. The multipart body also has a preamble and + * epilogue. The preamble consists of whatever characters appear before the + * first body part while the epilogue consists of whatever characters come after + * the last body part. + */ +public class MultipartImpl extends Multipart { + + private ByteSequence preamble; + private transient String preambleStrCache; + private transient boolean preambleComputed = false; + private ByteSequence epilogue; + private transient String epilogueStrCache; + private transient boolean epilogueComputed = false; + + /** + * Creates a new empty Multipart instance. + */ + public MultipartImpl(String subType) { + super(subType); + preamble = null; + preambleStrCache = null; + preambleComputed = true; + epilogue = null; + epilogueStrCache = null; + epilogueComputed = true; + } + + /** + * Creates a new Multipart from the specified + * Multipart. The Multipart instance is + * initialized with copies of preamble, epilogue, sub type and the list of + * body parts of the specified Multipart. The parent entity + * of the new multipart is null. + * + * @param other + * multipart to copy. + * @throws UnsupportedOperationException + * if other contains a {@link SingleBody} that + * does not support the {@link SingleBody#copy() copy()} + * operation. + * @throws IllegalArgumentException + * if other contains a Body that + * is neither a {@link Message}, {@link Multipart} or + * {@link SingleBody}. + */ + public MultipartImpl(Multipart other) { + super(other.getSubType()); + + for (Entity otherBodyPart : other.getBodyParts()) { + Entity bodyPartCopy = new BodyPart(otherBodyPart); + addBodyPart(bodyPartCopy); + } + + if (other instanceof MultipartImpl) { + preamble = ((MultipartImpl) other).preamble; + epilogue = ((MultipartImpl) other).epilogue; + preambleStrCache = ((MultipartImpl) other).preambleStrCache; + epilogueStrCache = ((MultipartImpl) other).epilogueStrCache; + preambleComputed = ((MultipartImpl) other).preambleComputed; + epilogueComputed = ((MultipartImpl) other).epilogueComputed; + } else { + setPreamble(other.getPreamble()); + setEpilogue(other.getEpilogue()); + } + } + + // package private for now; might become public someday + public ByteSequence getPreambleRaw() { + return preamble; + } + + public void setPreambleRaw(ByteSequence preamble) { + this.preamble = preamble; + this.preambleStrCache = null; + this.preambleComputed = false; + } + + /** + * Gets the preamble. + * + * @return the preamble. + */ + public String getPreamble() { + if (!preambleComputed) { + preambleStrCache = preamble != null ? ContentUtil.decode(preamble) : null; + preambleComputed = true; + } + return preambleStrCache; + } + + /** + * Sets the preamble. + * + * @param preamble + * the preamble. + */ + public void setPreamble(String preamble) { + this.preamble = preamble != null ? ContentUtil.encode(preamble) : null; + this.preambleStrCache = preamble; + this.preambleComputed = true; + } + + // package private for now; might become public someday + public ByteSequence getEpilogueRaw() { + return epilogue; + } + + public void setEpilogueRaw(ByteSequence epilogue) { + this.epilogue = epilogue; + this.epilogueStrCache = null; + this.epilogueComputed = false; + } + + /** + * Gets the epilogue. + * + * @return the epilogue. + */ + public String getEpilogue() { + if (!epilogueComputed) { + epilogueStrCache = epilogue != null ? ContentUtil.decode(epilogue) : null; + epilogueComputed = true; + } + return epilogueStrCache; + } + + /** + * Sets the epilogue. + * + * @param epilogue + * the epilogue. + */ + public void setEpilogue(String epilogue) { + this.epilogue = epilogue != null ? ContentUtil.encode(epilogue) : null; + this.epilogueStrCache = epilogue; + this.epilogueComputed = true; + } + +} diff --git a/src/org/apache/james/mime4j/message/SimpleContentHandler.java b/src/org/apache/james/mime4j/message/SimpleContentHandler.java new file mode 100644 index 000000000..e44bc46be --- /dev/null +++ b/src/org/apache/james/mime4j/message/SimpleContentHandler.java @@ -0,0 +1,86 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.field.Field; +import org.apache.james.mime4j.field.DefaultFieldParser; +import org.apache.james.mime4j.parser.AbstractContentHandler; +import org.apache.james.mime4j.stream.RawField; + +/** + * Abstract implementation of ContentHandler that automates common + * tasks. Currently performs header parsing. + * + * Older versions of this class performed decoding of content streams. + * This can be now easily achieved by calling setContentDecoding(true) on the MimeStreamParser. + */ +public abstract class SimpleContentHandler extends AbstractContentHandler { + + private final DecodeMonitor monitor; + + public SimpleContentHandler(final DecodeMonitor monitor) { + super(); + this.monitor = monitor; + } + + public SimpleContentHandler() { + this(null); + } + + /** + * Called after headers are parsed. + */ + public abstract void headers(Header header); + + /* Implement introduced callbacks. */ + + private Header currHeader; + + /** + * @see org.apache.james.mime4j.parser.AbstractContentHandler#startHeader() + */ + @Override + public final void startHeader() { + currHeader = new Header(); + } + + /** + * @see org.apache.james.mime4j.parser.AbstractContentHandler#field(RawField) + */ + @Override + public final void field(RawField field) throws MimeException { + Field parsedField = DefaultFieldParser.parse(field.getRaw(), monitor); + currHeader.addField(parsedField); + } + + /** + * @see org.apache.james.mime4j.parser.AbstractContentHandler#endHeader() + */ + @Override + public final void endHeader() { + Header tmp = currHeader; + currHeader = null; + headers(tmp); + } + +} diff --git a/src/org/apache/james/mime4j/message/StorageBinaryBody.java b/src/org/apache/james/mime4j/message/StorageBinaryBody.java new file mode 100644 index 000000000..0854e2e44 --- /dev/null +++ b/src/org/apache/james/mime4j/message/StorageBinaryBody.java @@ -0,0 +1,76 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.james.mime4j.codec.CodecUtil; +import org.apache.james.mime4j.dom.BinaryBody; +import org.apache.james.mime4j.storage.MultiReferenceStorage; + +/** + * Binary body backed by a + * {@link org.apache.james.mime4j.storage.Storage} + */ +class StorageBinaryBody extends BinaryBody { + + private MultiReferenceStorage storage; + + public StorageBinaryBody(final MultiReferenceStorage storage) { + this.storage = storage; + } + + @Override + public InputStream getInputStream() throws IOException { + return storage.getInputStream(); + } + + @Override + public void writeTo(OutputStream out) throws IOException { + if (out == null) + throw new IllegalArgumentException(); + + InputStream in = storage.getInputStream(); + CodecUtil.copy(in, out); + in.close(); + } + + @Override + public StorageBinaryBody copy() { + storage.addReference(); + return new StorageBinaryBody(storage); + } + + /** + * Deletes the Storage that holds the content of this binary body. + * + * @see org.apache.james.mime4j.dom.Disposable#dispose() + */ + @Override + public void dispose() { + if (storage != null) { + storage.delete(); + storage = null; + } + } + +} diff --git a/src/org/apache/james/mime4j/message/StorageTextBody.java b/src/org/apache/james/mime4j/message/StorageTextBody.java new file mode 100644 index 000000000..8338d11ed --- /dev/null +++ b/src/org/apache/james/mime4j/message/StorageTextBody.java @@ -0,0 +1,79 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; + +import org.apache.james.mime4j.dom.TextBody; +import org.apache.james.mime4j.storage.MultiReferenceStorage; +import org.apache.james.mime4j.util.CharsetUtil; + +/** + * Text body backed by a {@link org.apache.james.mime4j.storage.Storage}. + */ +class StorageTextBody extends TextBody { + + private MultiReferenceStorage storage; + private Charset charset; + + public StorageTextBody(MultiReferenceStorage storage, Charset charset) { + this.storage = storage; + this.charset = charset; + } + + @Override + public String getMimeCharset() { + return CharsetUtil.toMimeCharset(charset.name()); + } + + @Override + public Reader getReader() throws IOException { + return new InputStreamReader(storage.getInputStream(), charset); + } + + @Override + public InputStream getInputStream() throws IOException { + return storage.getInputStream(); + } + + @Override + public StorageTextBody copy() { + storage.addReference(); + return new StorageTextBody(storage, charset); + } + + /** + * Deletes the Storage that holds the content of this text body. + * + * @see org.apache.james.mime4j.dom.Disposable#dispose() + */ + @Override + public void dispose() { + if (storage != null) { + storage.delete(); + storage = null; + } + } + +} diff --git a/src/org/apache/james/mime4j/message/StringTextBody.java b/src/org/apache/james/mime4j/message/StringTextBody.java new file mode 100644 index 000000000..6d46a920d --- /dev/null +++ b/src/org/apache/james/mime4j/message/StringTextBody.java @@ -0,0 +1,89 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.message; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.nio.charset.Charset; + +import org.apache.james.mime4j.dom.TextBody; +import org.apache.james.mime4j.util.CharsetUtil; + +/** + * Text body backed by a String. + */ +class StringTextBody extends TextBody { + + private final String text; + private final Charset charset; + + public StringTextBody(final String text, Charset charset) { + this.text = text; + this.charset = charset; + } + + @Override + public String getMimeCharset() { + return CharsetUtil.toMimeCharset(charset.name()); + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(text.getBytes(charset.name())); + } + + @Override + public Reader getReader() throws IOException { + return new StringReader(text); + } + + @Override + public void writeTo(OutputStream out) throws IOException { + if (out == null) + throw new IllegalArgumentException(); + + Reader reader = new StringReader(text); + Writer writer = new OutputStreamWriter(out, charset); + + char buffer[] = new char[1024]; + while (true) { + int nChars = reader.read(buffer); + if (nChars == -1) + break; + + writer.write(buffer, 0, nChars); + } + + reader.close(); + writer.flush(); + } + + @Override + public StringTextBody copy() { + return new StringTextBody(text, charset); + } + +} diff --git a/src/org/apache/james/mime4j/message/TempFileBinaryBody.java b/src/org/apache/james/mime4j/message/TempFileBinaryBody.java deleted file mode 100644 index 2151a53cd..000000000 --- a/src/org/apache/james/mime4j/message/TempFileBinaryBody.java +++ /dev/null @@ -1,89 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.message; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.james.mime4j.util.TempFile; -import org.apache.james.mime4j.util.TempPath; -import org.apache.james.mime4j.util.TempStorage; - - -/** - * Binary body backed by a {@link org.apache.james.mime4j.util.TempFile}. - * - * - * @version $Id: TempFileBinaryBody.java,v 1.2 2004/10/02 12:41:11 ntherning Exp $ - */ -class TempFileBinaryBody extends AbstractBody implements BinaryBody { - private static Log log = LogFactory.getLog(TempFileBinaryBody.class); - - private Entity parent = null; - private TempFile tempFile = null; - - /** - * Use the given InputStream to build the TemporyFileBinaryBody - * - * @param is the InputStream to use as source - * @throws IOException - */ - public TempFileBinaryBody(InputStream is) throws IOException { - - TempPath tempPath = TempStorage.getInstance().getRootTempPath(); - tempFile = tempPath.createTempFile("attachment", ".bin"); - - OutputStream out = tempFile.getOutputStream(); - IOUtils.copy(is, out); - out.close(); - } - - /** - * @see org.apache.james.mime4j.message.AbstractBody#getParent() - */ - public Entity getParent() { - return parent; - } - - /** - * @see org.apache.james.mime4j.message.AbstractBody#setParent(org.apache.james.mime4j.message.Entity) - */ - public void setParent(Entity parent) { - this.parent = parent; - } - - /** - * @see org.apache.james.mime4j.message.BinaryBody#getInputStream() - */ - public InputStream getInputStream() throws IOException { - return tempFile.getInputStream(); - } - - /** - * @see org.apache.james.mime4j.message.Body#writeTo(java.io.OutputStream) - */ - public void writeTo(OutputStream out) throws IOException { - IOUtils.copy(getInputStream(),out); - } -} diff --git a/src/org/apache/james/mime4j/message/TempFileTextBody.java b/src/org/apache/james/mime4j/message/TempFileTextBody.java deleted file mode 100644 index 25a3b2958..000000000 --- a/src/org/apache/james/mime4j/message/TempFileTextBody.java +++ /dev/null @@ -1,115 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.message; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.Reader; -import java.io.UnsupportedEncodingException; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.james.mime4j.util.CharsetUtil; -import org.apache.james.mime4j.util.TempFile; -import org.apache.james.mime4j.util.TempPath; -import org.apache.james.mime4j.util.TempStorage; - - -/** - * Text body backed by a {@link org.apache.james.mime4j.util.TempFile}. - * - * - * @version $Id: TempFileTextBody.java,v 1.3 2004/10/25 07:26:46 ntherning Exp $ - */ -class TempFileTextBody extends AbstractBody implements TextBody { - private static Log log = LogFactory.getLog(TempFileTextBody.class); - - private String mimeCharset = null; - private TempFile tempFile = null; - - public TempFileTextBody(InputStream is) throws IOException { - this(is, null); - } - - public TempFileTextBody(InputStream is, String mimeCharset) - throws IOException { - - this.mimeCharset = mimeCharset; - - TempPath tempPath = TempStorage.getInstance().getRootTempPath(); - tempFile = tempPath.createTempFile("attachment", ".txt"); - - OutputStream out = tempFile.getOutputStream(); - IOUtils.copy(is, out); - out.close(); - } - - /** - * @see org.apache.james.mime4j.message.TextBody#getReader() - */ - public Reader getReader() throws UnsupportedEncodingException, IOException { - String javaCharset = null; - if (mimeCharset != null) { - javaCharset = CharsetUtil.toJavaCharset(mimeCharset); - } - - if (javaCharset == null) { - javaCharset = "ISO-8859-1"; - - if (log.isWarnEnabled()) { - if (mimeCharset == null) { - log.warn("No MIME charset specified. Using " + javaCharset - + " instead."); - } else { - log.warn("MIME charset '" + mimeCharset + "' has no " - + "corresponding Java charset. Using " + javaCharset - + " instead."); - } - } - } - /* - if (log.isWarnEnabled()) { - if (mimeCharset == null) { - log.warn("No MIME charset specified. Using the " - + "platform's default charset."); - } else { - log.warn("MIME charset '" + mimeCharset + "' has no " - + "corresponding Java charset. Using the " - + "platform's default charset."); - } - } - - return new InputStreamReader(tempFile.getInputStream()); - }*/ - - return new InputStreamReader(tempFile.getInputStream(), javaCharset); - } - - - /** - * @see org.apache.james.mime4j.message.Body#writeTo(java.io.OutputStream) - */ - public void writeTo(OutputStream out) throws IOException { - IOUtils.copy(tempFile.getInputStream(), out); - } -} diff --git a/src/org/apache/james/mime4j/parser/AbstractContentHandler.java b/src/org/apache/james/mime4j/parser/AbstractContentHandler.java new file mode 100644 index 000000000..0a48a7da7 --- /dev/null +++ b/src/org/apache/james/mime4j/parser/AbstractContentHandler.java @@ -0,0 +1,77 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.parser; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.stream.BodyDescriptor; +import org.apache.james.mime4j.stream.RawField; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Abstract ContentHandler with default implementations of all + * the methods of the ContentHandler interface. + * + * The default is to do nothing. + */ +public abstract class AbstractContentHandler implements ContentHandler { + + public void endMultipart() throws MimeException { + } + + public void startMultipart(BodyDescriptor bd) throws MimeException { + } + + public void body(BodyDescriptor bd, InputStream is) + throws MimeException, IOException { + } + + public void endBodyPart() throws MimeException { + } + + public void endHeader() throws MimeException { + } + + public void endMessage() throws MimeException { + } + + public void epilogue(InputStream is) throws MimeException, IOException { + } + + public void field(RawField field) throws MimeException { + } + + public void preamble(InputStream is) throws MimeException, IOException { + } + + public void startBodyPart() throws MimeException { + } + + public void startHeader() throws MimeException { + } + + public void startMessage() throws MimeException { + } + + public void raw(InputStream is) throws MimeException, IOException { + } + +} diff --git a/src/org/apache/james/mime4j/ContentHandler.java b/src/org/apache/james/mime4j/parser/ContentHandler.java similarity index 68% rename from src/org/apache/james/mime4j/ContentHandler.java rename to src/org/apache/james/mime4j/parser/ContentHandler.java index f8213a0d3..8199fa266 100644 --- a/src/org/apache/james/mime4j/ContentHandler.java +++ b/src/org/apache/james/mime4j/parser/ContentHandler.java @@ -17,7 +17,11 @@ * under the License. * ****************************************************************/ -package org.apache.james.mime4j; +package org.apache.james.mime4j.parser; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.stream.BodyDescriptor; +import org.apache.james.mime4j.stream.RawField; import java.io.IOException; import java.io.InputStream; @@ -26,13 +30,16 @@ import java.io.InputStream; *

* Receives notifications of the content of a plain RFC822 or MIME message. * Implement this interface and register an instance of that implementation - * with a MimeStreamParser instance using its - * {@link org.apache.james.mime4j.MimeStreamParser#setContentHandler(ContentHandler)} + * with a MimeStreamParser instance using its + * {@link org.apache.james.mime4j.stream.MimeStreamParser#setContentHandler(ContentHandler)} * method. The parser uses the ContentHandler instance to report * basic message-related events like the start and end of the body of a * part in a multipart MIME entity. *

*

+ * Throwing an exception from an event method will terminate the message + * processing, i.e. no new events will be generated for that message. + *

* Events will be generated in the order the corresponding elements occur in * the message stream parsed by the parser. E.g.: *

@@ -68,110 +75,128 @@ import java.io.InputStream;
  * body containing two body parts.
  * 

*

- * See MIME RFCs 2045-2049 for more information on the structure of MIME + * See MIME RFCs 2045-2049 for more information on the structure of MIME * messages and RFC 822 and 2822 for the general structure of Internet mail * messages. *

- * - * - * @version $Id: ContentHandler.java,v 1.3 2004/10/02 12:41:10 ntherning Exp $ */ public interface ContentHandler { + /** - * Called when a new message starts (a top level message or an embedded + * Called when a new message starts (a top level message or an embedded * rfc822 message). + * + * @throws MimeException on processing errors */ - void startMessage(); - + void startMessage() throws MimeException; + /** * Called when a message ends. + * + * @throws MimeException on processing errors */ - void endMessage(); - + void endMessage() throws MimeException; + /** * Called when a new body part starts inside a * multipart/* entity. + * + * @throws MimeException on processing errors */ - void startBodyPart(); - + void startBodyPart() throws MimeException; + /** * Called when a body part ends. + * + * @throws MimeException on processing errors */ - void endBodyPart(); - + void endBodyPart() throws MimeException; + /** * Called when a header (of a message or body part) is about to be parsed. + * + * @throws MimeException on processing errors */ - void startHeader(); - + void startHeader() throws MimeException; + /** * Called for each field of a header. - * - * @param fieldData the raw contents of the field - * (Field-Name: field value). The value will not be - * unfolded. + * + * @param rawField the MIME field. + * @throws MimeException on processing errors */ - void field(String fieldData); - + void field(RawField rawField) throws MimeException; + /** * Called when there are no more header fields in a message or body part. + * + * @throws MimeException on processing errors */ - void endHeader(); - + void endHeader() throws MimeException; + /** * Called for the preamble (whatever comes before the first body part) * of a multipart/* entity. - * + * * @param is used to get the contents of the preamble. + * @throws MimeException on processing errors * @throws IOException should be thrown on I/O errors. */ - void preamble(InputStream is) throws IOException; - + void preamble(InputStream is) throws MimeException, IOException; + /** - * Called for the epilogue (whatever comes after the final body part) + * Called for the epilogue (whatever comes after the final body part) * of a multipart/* entity. - * + * * @param is used to get the contents of the epilogue. + * @throws MimeException on processing errors * @throws IOException should be thrown on I/O errors. */ - void epilogue(InputStream is) throws IOException; - + void epilogue(InputStream is) throws MimeException, IOException; + /** * Called when the body of a multipart entity is about to be parsed. - * - * @param bd encapsulates the values (either read from the - * message stream or, if not present, determined implictly - * as described in the - * MIME rfc:s) of the Content-Type and + * + * @param bd encapsulates the values (either read from the + * message stream or, if not present, determined implictly + * as described in the + * MIME rfc:s) of the Content-Type and * Content-Transfer-Encoding header fields. + * @throws MimeException on processing errors */ - void startMultipart(BodyDescriptor bd); - + void startMultipart(BodyDescriptor bd) throws MimeException; + /** * Called when the body of an entity has been parsed. + * + * @throws MimeException on processing errors */ - void endMultipart(); - + void endMultipart() throws MimeException; + /** - * Called when the body of a discrete (non-multipart) entity is about to + * Called when the body of a discrete (non-multipart) entity is about to * be parsed. - * + * * @param bd see {@link #startMultipart(BodyDescriptor)} - * @param is the contents of the body. NOTE: this is the raw body contents + * @param is the contents of the body. NOTE: this is the raw body contents * - it will not be decoded if encoded. The bd * parameter should be used to determine how the stream data * should be decoded. + * @throws MimeException on processing errors * @throws IOException should be thrown on I/O errors. */ - void body(BodyDescriptor bd, InputStream is) throws IOException; - + void body(BodyDescriptor bd, InputStream is) + throws MimeException, IOException; + /** - * Called when a new entity (message or body part) starts and the + * Called when a new entity (message or body part) starts and the * parser is in raw mode. - * + * * @param is the raw contents of the entity. + * @throws MimeException on processing errors * @throws IOException should be thrown on I/O errors. - * @see MimeStreamParser#setRaw(boolean) + * @see org.apache.james.mime4j.stream.MimeStreamParser#setRaw(boolean) */ - void raw(InputStream is) throws IOException; + void raw(InputStream is) throws MimeException, IOException; + } diff --git a/src/org/apache/james/mime4j/parser/MimeStreamParser.java b/src/org/apache/james/mime4j/parser/MimeStreamParser.java new file mode 100644 index 000000000..1e40bfabc --- /dev/null +++ b/src/org/apache/james/mime4j/parser/MimeStreamParser.java @@ -0,0 +1,246 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.parser; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.stream.BodyDescriptor; +import org.apache.james.mime4j.stream.MimeEntityConfig; +import org.apache.james.mime4j.stream.MimeTokenStream; +import org.apache.james.mime4j.stream.MutableBodyDescriptorFactory; +import org.apache.james.mime4j.stream.RawField; + +/** + *

+ * Parses MIME (or RFC822) message streams of bytes or characters and reports + * parsing events to a ContentHandler instance. + *

+ *

+ * Typical usage:
+ *

+ *      ContentHandler handler = new MyHandler();
+ *      MimeStreamParser parser = new MimeStreamParser();
+ *      parser.setContentHandler(handler);
+ *      parser.parse(new FileInputStream("mime.msg"));
+ * 
+ */ +public class MimeStreamParser { + + private ContentHandler handler = null; + private boolean contentDecoding; + private final MimeEntityConfig mimeEntityConfig; + + private final MimeTokenStream mimeTokenStream; + + public MimeStreamParser(MimeTokenStream tokenStream) { + super(); + this.mimeTokenStream = tokenStream; + this.mimeEntityConfig = tokenStream.getConfig(); + this.contentDecoding = false; + } + + public MimeStreamParser( + final MimeEntityConfig config, + boolean clone, + final MutableBodyDescriptorFactory bodyDescFactory, + final DecodeMonitor monitor) { + this(new MimeTokenStream(clone ? config.clone() : config, bodyDescFactory, monitor)); + } + + public MimeStreamParser(final MimeEntityConfig config, boolean clone) { + this(new MimeTokenStream(clone ? config.clone() : config, null, null)); + } + + public MimeStreamParser( + final MimeEntityConfig config, + final MutableBodyDescriptorFactory bodyDescFactory, + final DecodeMonitor monitor) { + this(config != null ? config : new MimeEntityConfig(), config != null, + bodyDescFactory, monitor); + } + + public MimeStreamParser(final MimeEntityConfig config) { + this(config, null, null); + } + + public MimeStreamParser() { + this(new MimeEntityConfig(), false, null, null); + } + + /** + * Determines whether this parser automatically decodes body content + * based on the on the MIME fields with the standard defaults. + */ + public boolean isContentDecoding() { + return contentDecoding; + } + + /** + * Defines whether parser should automatically decode body content + * based on the on the MIME fields with the standard defaults. + */ + public void setContentDecoding(boolean b) { + this.contentDecoding = b; + } + + /** + * Parses a stream of bytes containing a MIME message. If the mime config of this + * object contains a not null defaultContentType + * ({@link MimeEntityConfig#getDefaultContentType()}) a headless parsing is performed. + * + * @param is the stream to parse. + * @throws MimeException if the message can not be processed + * @throws IOException on I/O errors. + */ + public void parse(InputStream inputStream) throws MimeException, IOException { + if (mimeEntityConfig.getHeadlessParsing() != null) { + mimeTokenStream.parseHeadless(inputStream, mimeEntityConfig.getHeadlessParsing()); + handler.startMessage(); + handler.startHeader(); + handler.field(new RawField("Content-Type", mimeEntityConfig.getHeadlessParsing())); + handler.endHeader(); + } else { + mimeTokenStream.parse(inputStream); + } + OUTER: for (;;) { + int state = mimeTokenStream.getState(); + switch (state) { + case MimeTokenStream.T_BODY: + BodyDescriptor desc = mimeTokenStream.getBodyDescriptor(); + InputStream bodyContent; + if (contentDecoding) { + bodyContent = mimeTokenStream.getDecodedInputStream(); + } else { + bodyContent = mimeTokenStream.getInputStream(); + } + handler.body(desc, bodyContent); + break; + case MimeTokenStream.T_END_BODYPART: + handler.endBodyPart(); + break; + case MimeTokenStream.T_END_HEADER: + handler.endHeader(); + break; + case MimeTokenStream.T_END_MESSAGE: + handler.endMessage(); + break; + case MimeTokenStream.T_END_MULTIPART: + handler.endMultipart(); + break; + case MimeTokenStream.T_END_OF_STREAM: + break OUTER; + case MimeTokenStream.T_EPILOGUE: + handler.epilogue(mimeTokenStream.getInputStream()); + break; + case MimeTokenStream.T_FIELD: + handler.field(mimeTokenStream.getField()); + break; + case MimeTokenStream.T_PREAMBLE: + handler.preamble(mimeTokenStream.getInputStream()); + break; + case MimeTokenStream.T_RAW_ENTITY: + handler.raw(mimeTokenStream.getInputStream()); + break; + case MimeTokenStream.T_START_BODYPART: + handler.startBodyPart(); + break; + case MimeTokenStream.T_START_HEADER: + handler.startHeader(); + break; + case MimeTokenStream.T_START_MESSAGE: + handler.startMessage(); + break; + case MimeTokenStream.T_START_MULTIPART: + handler.startMultipart(mimeTokenStream.getBodyDescriptor()); + break; + default: + throw new IllegalStateException("Invalid state: " + state); + } + state = mimeTokenStream.next(); + } + } + + /** + * Determines if this parser is currently in raw mode. + * + * @return true if in raw mode, false + * otherwise. + * @see #setRaw(boolean) + */ + public boolean isRaw() { + return mimeTokenStream.isRaw(); + } + + /** + * Enables or disables raw mode. In raw mode all future entities + * (messages or body parts) in the stream will be reported to the + * {@link ContentHandler#raw(InputStream)} handler method only. + * The stream will contain the entire unparsed entity contents + * including header fields and whatever is in the body. + * + * @param raw true enables raw mode, false + * disables it. + */ + public void setRaw(boolean raw) { + mimeTokenStream.setRecursionMode(MimeTokenStream.M_RAW); + } + + /** + * Enables or disables flat mode. In flat mode rfc822 parts are not + * recursively parsed and multipart content is handled as a single + * "simple" stream. + * + * @param raw true enables raw mode, false + * disables it. + */ + public void setFlat(boolean flat) { + mimeTokenStream.setRecursionMode(MimeTokenStream.M_FLAT); + } + + /** + * Finishes the parsing and stops reading lines. + * NOTE: No more lines will be parsed but the parser + * will still call + * {@link ContentHandler#endMultipart()}, + * {@link ContentHandler#endBodyPart()}, + * {@link ContentHandler#endMessage()}, etc to match previous calls + * to + * {@link ContentHandler#startMultipart(BodyDescriptor)}, + * {@link ContentHandler#startBodyPart()}, + * {@link ContentHandler#startMessage()}, etc. + */ + public void stop() { + mimeTokenStream.stop(); + } + + /** + * Sets the ContentHandler to use when reporting + * parsing events. + * + * @param h the ContentHandler. + */ + public void setContentHandler(ContentHandler h) { + this.handler = h; + } + +} diff --git a/src/org/apache/james/mime4j/storage/AbstractStorageProvider.java b/src/org/apache/james/mime4j/storage/AbstractStorageProvider.java new file mode 100644 index 000000000..c8046c480 --- /dev/null +++ b/src/org/apache/james/mime4j/storage/AbstractStorageProvider.java @@ -0,0 +1,61 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.storage; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mime4j.codec.CodecUtil; + +/** + * Abstract implementation of {@link StorageProvider} that implements + * {@link StorageProvider#store(InputStream) store(InputStream)} by copying the + * input stream to a {@link StorageOutputStream} obtained from + * {@link StorageProvider#createStorageOutputStream() createStorageOutputStream()}. + */ +public abstract class AbstractStorageProvider implements StorageProvider { + + /** + * Sole constructor. + */ + protected AbstractStorageProvider() { + } + + /** + * This implementation creates a {@link StorageOutputStream} by calling + * {@link StorageProvider#createStorageOutputStream() createStorageOutputStream()} + * and copies the content of the given input stream to that output stream. + * It then calls {@link StorageOutputStream#toStorage()} on the output + * stream and returns this object. + * + * @param in + * stream containing the data to store. + * @return a {@link Storage} instance that can be used to retrieve the + * stored content. + * @throws IOException + * if an I/O error occurs. + */ + public final Storage store(InputStream in) throws IOException { + StorageOutputStream out = createStorageOutputStream(); + CodecUtil.copy(in, out); + return out.toStorage(); + } + +} diff --git a/src/org/apache/james/mime4j/storage/CipherStorageProvider.java b/src/org/apache/james/mime4j/storage/CipherStorageProvider.java new file mode 100644 index 000000000..24216c7b6 --- /dev/null +++ b/src/org/apache/james/mime4j/storage/CipherStorageProvider.java @@ -0,0 +1,177 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.storage; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.SecretKeySpec; + +/** + * A {@link StorageProvider} that transparently scrambles and unscrambles the + * data stored by another StorageProvider. + * + *

+ * Example usage: + * + *

+ * StorageProvider mistrusted = new TempFileStorageProvider();
+ * StorageProvider enciphered = new CipherStorageProvider(mistrusted);
+ * StorageProvider provider = new ThresholdStorageProvider(enciphered);
+ * DefaultStorageProvider.setInstance(provider);
+ * 
+ */ +public class CipherStorageProvider extends AbstractStorageProvider { + + private final StorageProvider backend; + private final String algorithm; + private final KeyGenerator keygen; + + /** + * Creates a new CipherStorageProvider for the given back-end + * using the Blowfish cipher algorithm. + * + * @param backend + * back-end storage strategy to encrypt. + */ + public CipherStorageProvider(StorageProvider backend) { + this(backend, "Blowfish"); + } + + /** + * Creates a new CipherStorageProvider for the given back-end + * and cipher algorithm. + * + * @param backend + * back-end storage strategy to encrypt. + * @param algorithm + * the name of the symmetric block cipher algorithm such as + * "Blowfish", "AES" or "RC2". + */ + public CipherStorageProvider(StorageProvider backend, String algorithm) { + if (backend == null) + throw new IllegalArgumentException(); + + try { + this.backend = backend; + this.algorithm = algorithm; + this.keygen = KeyGenerator.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + public StorageOutputStream createStorageOutputStream() throws IOException { + SecretKeySpec skeySpec = getSecretKeySpec(); + + return new CipherStorageOutputStream(backend + .createStorageOutputStream(), algorithm, skeySpec); + } + + private SecretKeySpec getSecretKeySpec() { + byte[] raw = keygen.generateKey().getEncoded(); + return new SecretKeySpec(raw, algorithm); + } + + private static final class CipherStorageOutputStream extends + StorageOutputStream { + private final StorageOutputStream storageOut; + private final String algorithm; + private final SecretKeySpec skeySpec; + private final CipherOutputStream cipherOut; + + public CipherStorageOutputStream(StorageOutputStream out, + String algorithm, SecretKeySpec skeySpec) throws IOException { + try { + this.storageOut = out; + this.algorithm = algorithm; + this.skeySpec = skeySpec; + + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec); + + this.cipherOut = new CipherOutputStream(out, cipher); + } catch (GeneralSecurityException e) { + throw (IOException) new IOException().initCause(e); + } + } + + @Override + public void close() throws IOException { + super.close(); + cipherOut.close(); + } + + @Override + protected void write0(byte[] buffer, int offset, int length) + throws IOException { + cipherOut.write(buffer, offset, length); + } + + @Override + protected Storage toStorage0() throws IOException { + // cipherOut has already been closed because toStorage calls close + Storage encrypted = storageOut.toStorage(); + return new CipherStorage(encrypted, algorithm, skeySpec); + } + } + + private static final class CipherStorage implements Storage { + private Storage encrypted; + private final String algorithm; + private final SecretKeySpec skeySpec; + + public CipherStorage(Storage encrypted, String algorithm, + SecretKeySpec skeySpec) { + this.encrypted = encrypted; + this.algorithm = algorithm; + this.skeySpec = skeySpec; + } + + public void delete() { + if (encrypted != null) { + encrypted.delete(); + encrypted = null; + } + } + + public InputStream getInputStream() throws IOException { + if (encrypted == null) + throw new IllegalStateException("storage has been deleted"); + + try { + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.DECRYPT_MODE, skeySpec); + + InputStream in = encrypted.getInputStream(); + return new CipherInputStream(in, cipher); + } catch (GeneralSecurityException e) { + throw (IOException) new IOException().initCause(e); + } + } + } + +} diff --git a/src/org/apache/james/mime4j/storage/DefaultStorageProvider.java b/src/org/apache/james/mime4j/storage/DefaultStorageProvider.java new file mode 100644 index 000000000..3a046d8b3 --- /dev/null +++ b/src/org/apache/james/mime4j/storage/DefaultStorageProvider.java @@ -0,0 +1,93 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.storage; + +/** + * Allows for a default {@link StorageProvider} instance to be configured on an + * application level. + *

+ * The default instance can be set by either calling + * {@link #setInstance(StorageProvider)} when the application starts up or by + * setting the system property + * org.apache.james.mime4j.defaultStorageProvider to the class + * name of a StorageProvider implementation. + *

+ * If neither option is used or if the class instantiation fails this class + * provides a pre-configured default instance. + */ +public class DefaultStorageProvider { + + /** Value is org.apache.james.mime4j.defaultStorageProvider */ + public static final String DEFAULT_STORAGE_PROVIDER_PROPERTY = + "org.apache.james.mime4j.defaultStorageProvider"; + + private static volatile StorageProvider instance = null; + + static { + initialize(); + } + + private DefaultStorageProvider() { + } + + /** + * Returns the default {@link StorageProvider} instance. + * + * @return the default {@link StorageProvider} instance. + */ + public static StorageProvider getInstance() { + return instance; + } + + /** + * Sets the default {@link StorageProvider} instance. + * + * @param instance + * the default {@link StorageProvider} instance. + */ + public static void setInstance(StorageProvider instance) { + if (instance == null) { + throw new IllegalArgumentException(); + } + + DefaultStorageProvider.instance = instance; + } + + private static void initialize() { + String clazz = System.getProperty(DEFAULT_STORAGE_PROVIDER_PROPERTY); + try { + if (clazz != null) { + instance = (StorageProvider) Class.forName(clazz).newInstance(); + } + } catch (Exception e) { + } + if (instance == null) { + StorageProvider backend = new TempFileStorageProvider(); + instance = new ThresholdStorageProvider(backend, 1024); + } + } + + // for unit tests only + static void reset() { + instance = null; + initialize(); + } + +} diff --git a/src/org/apache/james/mime4j/storage/MemoryStorageProvider.java b/src/org/apache/james/mime4j/storage/MemoryStorageProvider.java new file mode 100644 index 000000000..d53e6c5f6 --- /dev/null +++ b/src/org/apache/james/mime4j/storage/MemoryStorageProvider.java @@ -0,0 +1,87 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.storage; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mime4j.util.ByteArrayBuffer; + +/** + * A {@link StorageProvider} that stores the data entirely in memory. + *

+ * Example usage: + * + *

+ * StorageProvider provider = new MemoryStorageProvider();
+ * DefaultStorageProvider.setInstance(provider);
+ * 
+ */ +public class MemoryStorageProvider extends AbstractStorageProvider { + + /** + * Creates a new MemoryStorageProvider. + */ + public MemoryStorageProvider() { + } + + public StorageOutputStream createStorageOutputStream() { + return new MemoryStorageOutputStream(); + } + + private static final class MemoryStorageOutputStream extends + StorageOutputStream { + ByteArrayBuffer bab = new ByteArrayBuffer(1024); + + @Override + protected void write0(byte[] buffer, int offset, int length) + throws IOException { + bab.append(buffer, offset, length); + } + + @Override + protected Storage toStorage0() throws IOException { + return new MemoryStorage(bab.buffer(), bab.length()); + } + } + + static final class MemoryStorage implements Storage { + private byte[] data; + private final int count; + + public MemoryStorage(byte[] data, int count) { + this.data = data; + this.count = count; + } + + public InputStream getInputStream() throws IOException { + if (data == null) + throw new IllegalStateException("storage has been deleted"); + + return new ByteArrayInputStream(data, 0, count); + } + + public void delete() { + data = null; + } + } + +} diff --git a/src/org/apache/james/mime4j/storage/MultiReferenceStorage.java b/src/org/apache/james/mime4j/storage/MultiReferenceStorage.java new file mode 100644 index 000000000..d239dfbc7 --- /dev/null +++ b/src/org/apache/james/mime4j/storage/MultiReferenceStorage.java @@ -0,0 +1,131 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.storage; + +import java.io.IOException; +import java.io.InputStream; + +/** + *

+ * A wrapper around another {@link Storage} that also maintains a reference + * counter. The inner storage gets deleted only if the reference counter reaches + * zero. + *

+ *

+ * Reference counting is used to delete the storage when it is no longer needed. + * So, any users of this class should note: + *

+ *
    + *
  • The reference count is set up one on construction. In all other cases, + * {@link #addReference()} should be called when the storage is shared.
  • + *
  • The caller of {@link #addReference()} should ensure that + * {@link #delete()} is called once and only once.
  • + *
  • Sharing the {@link Storage} instance passed into + * {@link #MultiReferenceStorage(Storage)} may lead to miscounting and premature + * deletion
  • + *
+ */ +public class MultiReferenceStorage implements Storage { + + private final Storage storage; + private int referenceCounter; + + /** + * Creates a new MultiReferenceStorage instance for the given + * back-end. The reference counter is initially set to one so the caller + * does not have to call {@link #addReference()} after this constructor. + * + * @param storage + * storage back-end that should be reference counted. + * @throws IllegalArgumentException + * when storage is null + */ + public MultiReferenceStorage(Storage storage) { + if (storage == null) + throw new IllegalArgumentException(); + + this.storage = storage; + this.referenceCounter = 1; // caller holds first reference + } + + /** + * Increments the reference counter. + * + * @throws IllegalStateException + * if the reference counter is zero which implies that the + * backing storage has already been deleted. + */ + public void addReference() { + incrementCounter(); + } + + /** + * Decrements the reference counter and deletes the inner + * Storage object if the reference counter reaches zero. + *

+ * A client that holds a reference to this object must make sure not to + * invoke this method a second time. + * + * @throws IllegalStateException + * if the reference counter is zero which implies that the + * backing storage has already been deleted. + */ + public void delete() { + if (decrementCounter()) { + storage.delete(); + } + } + + /** + * Returns the input stream of the inner Storage object. + * + * @return an input stream. + */ + public InputStream getInputStream() throws IOException { + return storage.getInputStream(); + } + + /** + * Synchronized increment of reference count. + * + * @throws IllegalStateException + * when counter is already zero + */ + private synchronized void incrementCounter() { + if (referenceCounter == 0) + throw new IllegalStateException("storage has been deleted"); + + referenceCounter++; + } + + /** + * Synchronized decrement of reference count. + * + * @return true when counter has reached zero, false otherwise + * @throws IllegalStateException + * when counter is already zero + */ + private synchronized boolean decrementCounter() { + if (referenceCounter == 0) + throw new IllegalStateException("storage has been deleted"); + + return --referenceCounter == 0; + } +} diff --git a/src/org/apache/james/mime4j/storage/Storage.java b/src/org/apache/james/mime4j/storage/Storage.java new file mode 100644 index 000000000..0179d01c6 --- /dev/null +++ b/src/org/apache/james/mime4j/storage/Storage.java @@ -0,0 +1,53 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.storage; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Can be used to read data that has been stored by a {@link StorageProvider}. + */ +public interface Storage { + /** + * Returns an InputStream that can be used to read the stored + * data. The input stream should be closed by the caller when it is no + * longer needed. + *

+ * Note: The stream should NOT be wrapped in a + * BufferedInputStream by the caller. If the implementing + * Storage creates a stream which would benefit from being + * buffered it is the Storage's responsibility to wrap it. + * + * @return an InputStream for reading the stored data. + * @throws IOException + * if an I/O error occurs. + * @throws IllegalStateException + * if this Storage instance has been deleted. + */ + InputStream getInputStream() throws IOException; + + /** + * Deletes the data held by this Storage as soon as possible. + * Deleting an already deleted Storage has no effect. + */ + void delete(); + +} diff --git a/src/org/apache/james/mime4j/storage/StorageOutputStream.java b/src/org/apache/james/mime4j/storage/StorageOutputStream.java new file mode 100644 index 000000000..7c3d74c82 --- /dev/null +++ b/src/org/apache/james/mime4j/storage/StorageOutputStream.java @@ -0,0 +1,170 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.storage; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * This class implements an output stream that can be used to create a + * {@link Storage} object. An instance of this class is obtained by calling + * {@link StorageProvider#createStorageOutputStream()}. The user can then write + * data to this instance and invoke {@link #toStorage()} to retrieve a + * {@link Storage} object that contains the data that has been written. + *

+ * Note that the StorageOutputStream does not have to be closed + * explicitly because {@link #toStorage()} invokes {@link #close()} if + * necessary. Also note that {@link #toStorage()} may be invoked only once. One + * StorageOutputStream can create only one Storage + * instance. + */ +public abstract class StorageOutputStream extends OutputStream { + + private byte[] singleByte; + private boolean closed; + private boolean usedUp; + + /** + * Sole constructor. + */ + protected StorageOutputStream() { + } + + /** + * Closes this output stream if it has not already been closed and returns a + * {@link Storage} object which contains the bytes that have been written to + * this output stream. + *

+ * Note that this method may not be invoked a second time. This is because + * for some implementations it is not possible to create another + * Storage object that can be read from and deleted + * independently (e.g. if the implementation writes to a file). + * + * @return a Storage object as described above. + * @throws IOException + * if an I/O error occurs. + * @throws IllegalStateException + * if this method has already been called. + */ + public final Storage toStorage() throws IOException { + if (usedUp) + throw new IllegalStateException( + "toStorage may be invoked only once"); + + if (!closed) + close(); + + usedUp = true; + return toStorage0(); + } + + @Override + public final void write(int b) throws IOException { + if (closed) + throw new IOException("StorageOutputStream has been closed"); + + if (singleByte == null) + singleByte = new byte[1]; + + singleByte[0] = (byte) b; + write0(singleByte, 0, 1); + } + + @Override + public final void write(byte[] buffer) throws IOException { + if (closed) + throw new IOException("StorageOutputStream has been closed"); + + if (buffer == null) + throw new NullPointerException(); + + if (buffer.length == 0) + return; + + write0(buffer, 0, buffer.length); + } + + @Override + public final void write(byte[] buffer, int offset, int length) + throws IOException { + if (closed) + throw new IOException("StorageOutputStream has been closed"); + + if (buffer == null) + throw new NullPointerException(); + + if (offset < 0 || length < 0 || offset + length > buffer.length) + throw new IndexOutOfBoundsException(); + + if (length == 0) + return; + + write0(buffer, offset, length); + } + + /** + * Closes this output stream. Subclasses that override this method have to + * invoke super.close(). + *

+ * This implementation never throws an {@link IOException} but a subclass + * might. + * + * @throws IOException + * if an I/O error occurs. + */ + @Override + public void close() throws IOException { + closed = true; + } + + /** + * Has to implemented by a concrete subclass to write bytes from the given + * byte array to this StorageOutputStream. This method gets + * called by {@link #write(int)}, {@link #write(byte[])} and + * {@link #write(byte[], int, int)}. All the required preconditions have + * already been checked by these methods, including the check if the output + * stream has already been closed. + * + * @param buffer + * buffer containing bytes to write. + * @param offset + * start offset in the buffer. + * @param length + * number of bytes to write. + * @throws IOException + * if an I/O error occurs. + */ + protected abstract void write0(byte[] buffer, int offset, int length) + throws IOException; + + /** + * Has to be implemented by a concrete subclass to create a {@link Storage} + * object from the bytes that have been written to this + * StorageOutputStream. This method gets called by + * {@link #toStorage()} after the preconditions have been checked. The + * implementation can also be sure that this methods gets invoked only once. + * + * @return a Storage object as described above. + * @throws IOException + * if an I/O error occurs. + */ + protected abstract Storage toStorage0() throws IOException; + +} diff --git a/src/org/apache/james/mime4j/storage/StorageProvider.java b/src/org/apache/james/mime4j/storage/StorageProvider.java new file mode 100644 index 000000000..5a6c89e1c --- /dev/null +++ b/src/org/apache/james/mime4j/storage/StorageProvider.java @@ -0,0 +1,51 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.storage; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Provides a strategy for storing the contents of an InputStream + * or retrieving the content written to an OutputStream. + */ +public interface StorageProvider { + /** + * Stores the contents of the given InputStream. + * + * @param in stream containing the data to store. + * @return a {@link Storage} instance that can be used to retrieve the + * stored content. + * @throws IOException if an I/O error occurs. + */ + Storage store(InputStream in) throws IOException; + + /** + * Creates a {@link StorageOutputStream} where data to be stored can be + * written to. Subsequently the user can call + * {@link StorageOutputStream#toStorage() toStorage()} on that object to get + * a {@link Storage} instance that holds the data that has been written. + * + * @return a {@link StorageOutputStream} where data can be written to. + * @throws IOException + * if an I/O error occurs. + */ + StorageOutputStream createStorageOutputStream() throws IOException; +} diff --git a/src/org/apache/james/mime4j/storage/TempFileStorageProvider.java b/src/org/apache/james/mime4j/storage/TempFileStorageProvider.java new file mode 100644 index 000000000..6c30acb8a --- /dev/null +++ b/src/org/apache/james/mime4j/storage/TempFileStorageProvider.java @@ -0,0 +1,183 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.storage; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * A {@link StorageProvider} that stores the data in temporary files. The files + * are stored either in a user-specified directory or the default temporary-file + * directory (specified by system property java.io.tmpdir). + *

+ * Example usage: + * + *

+ * File directory = new File("/tmp/mime4j");
+ * StorageProvider provider = new TempFileStorageProvider(directory);
+ * DefaultStorageProvider.setInstance(provider);
+ * 
+ */ +public class TempFileStorageProvider extends AbstractStorageProvider { + + private static final String DEFAULT_PREFIX = "m4j"; + + private final String prefix; + private final String suffix; + private final File directory; + + /** + * Equivalent to using constructor + * TempFileStorageProvider("m4j", null, null). + */ + public TempFileStorageProvider() { + this(DEFAULT_PREFIX, null, null); + } + + /** + * Equivalent to using constructor + * TempFileStorageProvider("m4j", null, directory). + */ + public TempFileStorageProvider(File directory) { + this(DEFAULT_PREFIX, null, directory); + } + + /** + * Creates a new TempFileStorageProvider using the given + * values. + * + * @param prefix + * prefix for generating the temporary file's name; must be at + * least three characters long. + * @param suffix + * suffix for generating the temporary file's name; may be + * null to use the suffix ".tmp". + * @param directory + * the directory in which the file is to be created, or + * null if the default temporary-file directory is + * to be used (specified by the system property + * java.io.tmpdir). + * @throws IllegalArgumentException + * if the given prefix is less than three characters long or the + * given directory does not exist and cannot be created (if it + * is not null). + */ + public TempFileStorageProvider(String prefix, String suffix, File directory) { + if (prefix == null || prefix.length() < 3) + throw new IllegalArgumentException("invalid prefix"); + + if (directory != null && !directory.isDirectory() + && !directory.mkdirs()) + throw new IllegalArgumentException("invalid directory"); + + this.prefix = prefix; + this.suffix = suffix; + this.directory = directory; + } + + public StorageOutputStream createStorageOutputStream() throws IOException { + File file = File.createTempFile(prefix, suffix, directory); + file.deleteOnExit(); + + return new TempFileStorageOutputStream(file); + } + + private static final class TempFileStorageOutputStream extends + StorageOutputStream { + private File file; + private OutputStream out; + + public TempFileStorageOutputStream(File file) throws IOException { + this.file = file; + this.out = new FileOutputStream(file); + } + + @Override + public void close() throws IOException { + super.close(); + out.close(); + } + + @Override + protected void write0(byte[] buffer, int offset, int length) + throws IOException { + out.write(buffer, offset, length); + } + + @Override + protected Storage toStorage0() throws IOException { + // out has already been closed because toStorage calls close + return new TempFileStorage(file); + } + } + + private static final class TempFileStorage implements Storage { + + private File file; + + private static final Set filesToDelete = new HashSet(); + + public TempFileStorage(File file) { + this.file = file; + } + + public void delete() { + // deleting a file might not immediately succeed if there are still + // streams left open (especially under Windows). so we keep track of + // the files that have to be deleted and try to delete all these + // files each time this method gets invoked. + + // a better but more complicated solution would be to start a + // separate thread that tries to delete the files periodically. + + synchronized (filesToDelete) { + if (file != null) { + filesToDelete.add(file); + file = null; + } + + for (Iterator iterator = filesToDelete.iterator(); iterator + .hasNext();) { + File file = iterator.next(); + if (file.delete()) { + iterator.remove(); + } + } + } + } + + public InputStream getInputStream() throws IOException { + if (file == null) + throw new IllegalStateException("storage has been deleted"); + + return new BufferedInputStream(new FileInputStream(file)); + } + + } + +} diff --git a/src/org/apache/james/mime4j/storage/ThresholdStorageProvider.java b/src/org/apache/james/mime4j/storage/ThresholdStorageProvider.java new file mode 100644 index 000000000..32ad99223 --- /dev/null +++ b/src/org/apache/james/mime4j/storage/ThresholdStorageProvider.java @@ -0,0 +1,161 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.storage; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; + +import org.apache.james.mime4j.util.ByteArrayBuffer; + +/** + * A {@link StorageProvider} that keeps small amounts of data in memory and + * writes the remainder to another StorageProvider (the back-end) + * if a certain threshold size gets exceeded. + *

+ * Example usage: + * + *

+ * StorageProvider tempStore = new TempFileStorageProvider();
+ * StorageProvider provider = new ThresholdStorageProvider(tempStore, 4096);
+ * DefaultStorageProvider.setInstance(provider);
+ * 
+ */ +public class ThresholdStorageProvider extends AbstractStorageProvider { + + private final StorageProvider backend; + private final int thresholdSize; + + /** + * Creates a new ThresholdStorageProvider for the given + * back-end using a threshold size of 2048 bytes. + */ + public ThresholdStorageProvider(StorageProvider backend) { + this(backend, 2048); + } + + /** + * Creates a new ThresholdStorageProvider for the given + * back-end and threshold size. + * + * @param backend + * used to store the remainder of the data if the threshold size + * gets exceeded. + * @param thresholdSize + * determines how much bytes are kept in memory before that + * back-end storage provider is used to store the remainder of + * the data. + */ + public ThresholdStorageProvider(StorageProvider backend, int thresholdSize) { + if (backend == null) + throw new IllegalArgumentException(); + if (thresholdSize < 1) + throw new IllegalArgumentException(); + + this.backend = backend; + this.thresholdSize = thresholdSize; + } + + public StorageOutputStream createStorageOutputStream() { + return new ThresholdStorageOutputStream(); + } + + private final class ThresholdStorageOutputStream extends + StorageOutputStream { + + private final ByteArrayBuffer head; + private StorageOutputStream tail; + + public ThresholdStorageOutputStream() { + final int bufferSize = Math.min(thresholdSize, 1024); + head = new ByteArrayBuffer(bufferSize); + } + + @Override + public void close() throws IOException { + super.close(); + + if (tail != null) + tail.close(); + } + + @Override + protected void write0(byte[] buffer, int offset, int length) + throws IOException { + int remainingHeadSize = thresholdSize - head.length(); + if (remainingHeadSize > 0) { + int n = Math.min(remainingHeadSize, length); + head.append(buffer, offset, n); + offset += n; + length -= n; + } + + if (length > 0) { + if (tail == null) + tail = backend.createStorageOutputStream(); + + tail.write(buffer, offset, length); + } + } + + @Override + protected Storage toStorage0() throws IOException { + if (tail == null) + return new MemoryStorageProvider.MemoryStorage(head.buffer(), + head.length()); + + return new ThresholdStorage(head.buffer(), head.length(), tail + .toStorage()); + } + + } + + private static final class ThresholdStorage implements Storage { + + private byte[] head; + private final int headLen; + private Storage tail; + + public ThresholdStorage(byte[] head, int headLen, Storage tail) { + this.head = head; + this.headLen = headLen; + this.tail = tail; + } + + public void delete() { + if (head != null) { + head = null; + tail.delete(); + tail = null; + } + } + + public InputStream getInputStream() throws IOException { + if (head == null) + throw new IllegalStateException("storage has been deleted"); + + InputStream headStream = new ByteArrayInputStream(head, 0, headLen); + InputStream tailStream = tail.getInputStream(); + return new SequenceInputStream(headStream, tailStream); + } + + } +} diff --git a/src/org/apache/james/mime4j/stream/AbstractEntity.java b/src/org/apache/james/mime4j/stream/AbstractEntity.java new file mode 100644 index 000000000..ba7493076 --- /dev/null +++ b/src/org/apache/james/mime4j/stream/AbstractEntity.java @@ -0,0 +1,337 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + +import java.io.IOException; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.io.LineReaderInputStream; +import org.apache.james.mime4j.io.MaxHeaderLengthLimitException; +import org.apache.james.mime4j.io.MaxHeaderLimitException; +import org.apache.james.mime4j.io.MaxLineLimitException; +import org.apache.james.mime4j.util.ByteArrayBuffer; +import org.apache.james.mime4j.util.CharsetUtil; + +/** + * Abstract MIME entity. + */ +public abstract class AbstractEntity implements EntityStateMachine { + + protected final int startState; + protected final int endState; + protected final MimeEntityConfig config; + protected final MutableBodyDescriptor body; + + protected int state; + + private final ByteArrayBuffer linebuf; + + private int lineCount; + private RawField field; + private boolean endOfHeader; + private int headerCount; + protected final DecodeMonitor monitor; + + /** + * Internal state, not exposed. + */ + private static final int T_IN_BODYPART = -2; + /** + * Internal state, not exposed. + */ + private static final int T_IN_MESSAGE = -3; + + AbstractEntity( + MutableBodyDescriptor body, + int startState, + int endState, + MimeEntityConfig config, + DecodeMonitor monitor) { + this.state = startState; + this.startState = startState; + this.endState = endState; + this.config = config; + this.body = body; + this.linebuf = new ByteArrayBuffer(64); + this.lineCount = 0; + this.endOfHeader = false; + this.headerCount = 0; + this.monitor = monitor; + } + + public int getState() { + return state; + } + + /** + * Returns the current line number or -1 if line number + * information is not available. + */ + protected abstract int getLineNumber(); + + protected abstract LineReaderInputStream getDataStream(); + + private ByteArrayBuffer fillFieldBuffer() throws IOException, MimeException { + if (endOfHeader) + throw new IllegalStateException(); + + int maxHeaderLen = config.getMaxHeaderLen(); + LineReaderInputStream instream = getDataStream(); + ByteArrayBuffer fieldbuf = new ByteArrayBuffer(64); + + try { + for (;;) { + // If there's still data stuck in the line buffer + // copy it to the field buffer + int len = linebuf.length(); + if (maxHeaderLen > 0 && fieldbuf.length() + len >= maxHeaderLen) { + throw new MaxHeaderLengthLimitException("Maximum header length limit exceeded"); + } + if (len > 0) { + fieldbuf.append(linebuf.buffer(), 0, len); + } + linebuf.clear(); + if (instream.readLine(linebuf) == -1) { + monitor(Event.HEADERS_PREMATURE_END); + endOfHeader = true; + break; + } + len = linebuf.length(); + if (len > 0 && linebuf.byteAt(len - 1) == '\n') { + len--; + } + if (len > 0 && linebuf.byteAt(len - 1) == '\r') { + len--; + } + if (len == 0) { + // empty line detected + endOfHeader = true; + break; + } + lineCount++; + if (lineCount > 1) { + int ch = linebuf.byteAt(0); + if (ch != CharsetUtil.SP && ch != CharsetUtil.HT) { + // new header detected + break; + } + } + } + } catch (MaxLineLimitException e) { + throw new MimeException(e); + } + + return fieldbuf; + } + + protected boolean parseField() throws MimeException, IOException { + int maxHeaderCount = config.getMaxHeaderCount(); + // the loop is here to transparently skip invalid headers + for (;;) { + if (endOfHeader) { + return false; + } + if (maxHeaderCount > 0 && headerCount >= maxHeaderCount) { + throw new MaxHeaderLimitException("Maximum header limit exceeded"); + } + + ByteArrayBuffer fieldbuf = fillFieldBuffer(); + headerCount++; + + // Strip away line delimiter + int origLen = fieldbuf.length(); + int len = fieldbuf.length(); + if (len > 0 && fieldbuf.byteAt(len - 1) == '\n') { + len--; + } + if (len > 0 && fieldbuf.byteAt(len - 1) == '\r') { + len--; + } + fieldbuf.setLength(len); + + try { + field = new RawField(fieldbuf); + if (field.isObsoleteSyntax()) { + monitor(Event.OBSOLETE_HEADER); + } + body.addField(field); + return true; + } catch (MimeException e) { + monitor(Event.INVALID_HEADER); + if (config.isMalformedHeaderStartsBody()) { + fieldbuf.setLength(origLen); + LineReaderInputStream instream = getDataStream(); + if (!instream.unread(fieldbuf)) throw new MimeParseEventException(Event.INVALID_HEADER); + return false; + } + } + } + } + + /** + *

Gets a descriptor for the current entity. + * This method is valid if {@link #getState()} returns:

+ *
    + *
  • {@link EntityStates#T_BODY}
  • + *
  • {@link EntityStates#T_START_MULTIPART}
  • + *
  • {@link EntityStates#T_EPILOGUE}
  • + *
  • {@link EntityStates#T_PREAMBLE}
  • + *
+ * @return BodyDescriptor, not nulls + */ + public BodyDescriptor getBodyDescriptor() { + switch (getState()) { + case EntityStates.T_BODY: + case EntityStates.T_START_MULTIPART: + case EntityStates.T_PREAMBLE: + case EntityStates.T_EPILOGUE: + case EntityStates.T_END_OF_STREAM: + return body; + default: + throw new IllegalStateException("Invalid state :" + stateToString(state)); + } + } + + /** + * This method is valid, if {@link #getState()} returns {@link EntityStates#T_FIELD}. + * @return String with the fields raw contents. + * @throws IllegalStateException {@link #getState()} returns another + * value than {@link EntityStates#T_FIELD}. + */ + public RawField getField() { + switch (getState()) { + case EntityStates.T_FIELD: + return field; + default: + throw new IllegalStateException("Invalid state :" + stateToString(state)); + } + } + + /** + * Monitors the given event. + * Subclasses may override to perform actions upon events. + * Base implementation logs at warn. + * @param event Event, not null + * @throws MimeException subclasses may elect to throw this exception upon + * invalid content + * @throws IOException subclasses may elect to throw this exception + */ + protected void monitor(Event event) throws MimeException, IOException { + if (monitor.isListening()) { + String message = message(event); + if (monitor.warn(message, "ignoring")) { + throw new MimeParseEventException(event); + } + } + } + + /** + * Creates an indicative message suitable for display + * based on the given event and the current state of the system. + * @param event Event, not null + * @return message suitable for use as a message in an exception + * or for logging + */ + protected String message(Event event) { + final String message; + if (event == null) { + message = "Event is unexpectedly null."; + } else { + message = event.toString(); + } + + int lineNumber = getLineNumber(); + if (lineNumber <= 0) + return message; + else + return "Line " + lineNumber + ": " + message; + } + + @Override + public String toString() { + return getClass().getName() + " [" + stateToString(state) + + "][" + body.getMimeType() + "][" + body.getBoundary() + "]"; + } + + /** + * Renders a state as a string suitable for logging. + * @param state + * @return rendered as string, not null + */ + public static final String stateToString(int state) { + final String result; + switch (state) { + case EntityStates.T_END_OF_STREAM: + result = "End of stream"; + break; + case EntityStates.T_START_MESSAGE: + result = "Start message"; + break; + case EntityStates.T_END_MESSAGE: + result = "End message"; + break; + case EntityStates.T_RAW_ENTITY: + result = "Raw entity"; + break; + case EntityStates.T_START_HEADER: + result = "Start header"; + break; + case EntityStates.T_FIELD: + result = "Field"; + break; + case EntityStates.T_END_HEADER: + result = "End header"; + break; + case EntityStates.T_START_MULTIPART: + result = "Start multipart"; + break; + case EntityStates.T_END_MULTIPART: + result = "End multipart"; + break; + case EntityStates.T_PREAMBLE: + result = "Preamble"; + break; + case EntityStates.T_EPILOGUE: + result = "Epilogue"; + break; + case EntityStates.T_START_BODYPART: + result = "Start bodypart"; + break; + case EntityStates.T_END_BODYPART: + result = "End bodypart"; + break; + case EntityStates.T_BODY: + result = "Body"; + break; + case T_IN_BODYPART: + result = "Bodypart"; + break; + case T_IN_MESSAGE: + result = "In message"; + break; + default: + result = "Unknown"; + break; + } + return result; + } + +} diff --git a/src/org/apache/james/mime4j/stream/BodyDescriptor.java b/src/org/apache/james/mime4j/stream/BodyDescriptor.java new file mode 100644 index 000000000..e6430bcf5 --- /dev/null +++ b/src/org/apache/james/mime4j/stream/BodyDescriptor.java @@ -0,0 +1,35 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + +/** + * Encapsulates the values of the MIME-specific header fields + * (which starts with Content-). + */ +public interface BodyDescriptor extends ContentDescriptor { + + /** + * Returns the body descriptors boundary. + * @return Boundary string, if known, or null. The latter may be the + * case, in particular, if the body is no multipart entity. + */ + String getBoundary(); + +} diff --git a/src/org/apache/james/mime4j/stream/ContentDescriptor.java b/src/org/apache/james/mime4j/stream/ContentDescriptor.java new file mode 100644 index 000000000..06f11a456 --- /dev/null +++ b/src/org/apache/james/mime4j/stream/ContentDescriptor.java @@ -0,0 +1,88 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + +import java.util.Map; + +/** + * Represents common content properties. + */ +public interface ContentDescriptor { + + /** + * Returns the body descriptors MIME type. + * @see #getMediaType() + * @see #getSubType() + * @return The MIME type, which has been parsed from the + * content-type definition. Must not be null, but + * "text/plain", if no content-type was specified. + */ + String getMimeType(); + + /** + * Gets the defaulted MIME media type for this content. + * For example TEXT, IMAGE, MULTIPART + * @see #getMimeType() + * @return the MIME media type when content-type specified, + * otherwise the correct default (TEXT) + */ + String getMediaType(); + + /** + * Gets the defaulted MIME sub type for this content. + * @see #getMimeType() + * @return the MIME media type when content-type is specified, + * otherwise the correct default (PLAIN) + */ + String getSubType(); + + /** + *

The body descriptors character set, defaulted appropriately for the MIME type.

+ *

+ * For TEXT types, this will be defaulted to us-ascii. + * For other types, when the charset parameter is missing this property will be null. + *

+ * @return Character set, which has been parsed from the + * content-type definition. Not null for TEXT types, when unset will + * be set to default us-ascii. For other types, when unset, + * null will be returned. + */ + String getCharset(); + + /** + * Returns the body descriptors transfer encoding. + * @return The transfer encoding. Must not be null, but "7bit", + * if no transfer-encoding was specified. + */ + String getTransferEncoding(); + + /** + * Returns the map of parameters of the content-type header. + */ + Map getContentTypeParameters(); + + /** + * Returns the body descriptors content-length. + * @return Content length, if known, or -1, to indicate the absence of a + * content-length header. + */ + long getContentLength(); + +} diff --git a/src/org/apache/james/mime4j/BodyDescriptor.java b/src/org/apache/james/mime4j/stream/DefaultBodyDescriptor.java similarity index 56% rename from src/org/apache/james/mime4j/BodyDescriptor.java rename to src/org/apache/james/mime4j/stream/DefaultBodyDescriptor.java index ca447b032..630396a9c 100644 --- a/src/org/apache/james/mime4j/BodyDescriptor.java +++ b/src/org/apache/james/mime4j/stream/DefaultBodyDescriptor.java @@ -17,159 +17,277 @@ * under the License. * ****************************************************************/ -package org.apache.james.mime4j; +package org.apache.james.mime4j.stream; import java.util.HashMap; import java.util.Map; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.util.MimeUtil; /** - * Encapsulates the values of the MIME-specific header fields - * (which starts with Content-). - * - * - * @version $Id: BodyDescriptor.java,v 1.4 2005/02/11 10:08:37 ntherning Exp $ + * Encapsulates the values of the MIME-specific header fields + * (which starts with Content-). */ -public class BodyDescriptor { - private static Log log = LogFactory.getLog(BodyDescriptor.class); - - private String mimeType = "text/plain"; +public class DefaultBodyDescriptor implements MutableBodyDescriptor { + private static final String US_ASCII = "us-ascii"; + + private static final String SUB_TYPE_EMAIL = "rfc822"; + + private static final String MEDIA_TYPE_TEXT = "text"; + + private static final String MEDIA_TYPE_MESSAGE = "message"; + + private static final String EMAIL_MESSAGE_MIME_TYPE = MEDIA_TYPE_MESSAGE + "/" + SUB_TYPE_EMAIL; + + private static final String DEFAULT_SUB_TYPE = "plain"; + + private static final String DEFAULT_MEDIA_TYPE = MEDIA_TYPE_TEXT; + + private static final String DEFAULT_MIME_TYPE = DEFAULT_MEDIA_TYPE + "/" + DEFAULT_SUB_TYPE; + + private final DecodeMonitor monitor; + + private String mediaType = DEFAULT_MEDIA_TYPE; + private String subType = DEFAULT_SUB_TYPE; + private String mimeType = DEFAULT_MIME_TYPE; private String boundary = null; - private String charset = "us-ascii"; + private String charset = US_ASCII; private String transferEncoding = "7bit"; - private Map parameters = new HashMap(); - private boolean contentTypeSet = false; - private boolean contentTransferEncSet = false; - + private Map parameters = new HashMap(); + private boolean contentTypeSet; + private boolean contentTransferEncSet; + private long contentLength = -1; + /** * Creates a new root BodyDescriptor instance. */ - public BodyDescriptor() { - this(null); + public DefaultBodyDescriptor() { + this(null, null); } /** * Creates a new BodyDescriptor instance. - * + * * @param parent the descriptor of the parent or null if this * is the root descriptor. */ - public BodyDescriptor(BodyDescriptor parent) { - if (parent != null && parent.isMimeType("multipart/digest")) { - mimeType = "message/rfc822"; + public DefaultBodyDescriptor(final BodyDescriptor parent, final DecodeMonitor monitor) { + if (parent != null && MimeUtil.isSameMimeType("multipart/digest", parent.getMimeType())) { + this.mimeType = EMAIL_MESSAGE_MIME_TYPE; + this.subType = SUB_TYPE_EMAIL; + this.mediaType = MEDIA_TYPE_MESSAGE; } else { - mimeType = "text/plain"; + this.mimeType = DEFAULT_MIME_TYPE; + this.subType = DEFAULT_SUB_TYPE; + this.mediaType = DEFAULT_MEDIA_TYPE; } + this.monitor = monitor != null ? monitor : DecodeMonitor.SILENT; } - + + protected DecodeMonitor getDecodeMonitor() { + return monitor; + } + + public MutableBodyDescriptor newChild() { + return new DefaultBodyDescriptor(this, getDecodeMonitor()); + } + /** - * Should be called for each Content- header field of + * Should be called for each Content- header field of * a MIME message or part. - * - * @param name the field name. - * @param value the field value. + * + * @param field the MIME field. */ - public void addField(String name, String value) { - + public void addField(RawField field) throws MimeException { + String name = field.getName(); + String value = field.getBody(); + name = name.trim().toLowerCase(); - + if (name.equals("content-transfer-encoding") && !contentTransferEncSet) { contentTransferEncSet = true; - + value = value.trim().toLowerCase(); if (value.length() > 0) { transferEncoding = value; } - + + } else if (name.equals("content-length") && contentLength == -1) { + try { + contentLength = Long.parseLong(value.trim()); + } catch (NumberFormatException e) { + if (monitor.warn("Invalid content length: " + value, + "ignoring Content-Length header")) { + throw new MimeException("Invalid Content-Length header: " + value); + } + } } else if (name.equals("content-type") && !contentTypeSet) { - contentTypeSet = true; - - value = value.trim(); - - /* - * Unfold Content-Type value - */ - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (c == '\r' || c == '\n') { - continue; - } - sb.append(c); - } - - Map params = getHeaderParams(sb.toString()); - - String main = (String) params.get(""); - if (main != null) { - main = main.toLowerCase().trim(); - int index = main.indexOf('/'); - boolean valid = false; - if (index != -1) { - String type = main.substring(0, index).trim(); - String subtype = main.substring(index + 1).trim(); - if (type.length() > 0 && subtype.length() > 0) { - main = type + "/" + subtype; - valid = true; - } - } - - if (!valid) { - main = null; - } - } - String b = (String) params.get("boundary"); - - if (main != null - && ((main.startsWith("multipart/") && b != null) - || !main.startsWith("multipart/"))) { - - mimeType = main; - } - - if (isMultipart()) { - boundary = b; - } - - String c = (String) params.get("charset"); - if (c != null) { - c = c.trim(); - if (c.length() > 0) { - charset = c.toLowerCase(); - } - } - - /* - * Add all other parameters to parameters. - */ - parameters.putAll(params); - parameters.remove(""); - parameters.remove("boundary"); - parameters.remove("charset"); + parseContentType(value); } } - - private Map getHeaderParams(String headerValue) { - Map result = new HashMap(); + + private void parseContentType(String value) throws MimeException { + contentTypeSet = true; + + Map params = DefaultBodyDescriptor.getHeaderParams(value, getDecodeMonitor()); + + String main = params.get(""); + String type = null; + String subtype = null; + if (main != null) { + main = main.toLowerCase().trim(); + int index = main.indexOf('/'); + boolean valid = false; + if (index != -1) { + type = main.substring(0, index).trim(); + subtype = main.substring(index + 1).trim(); + if (type.length() > 0 && subtype.length() > 0) { + main = type + "/" + subtype; + valid = true; + } + } + + if (!valid) { + main = null; + type = null; + subtype = null; + } + } + String b = params.get("boundary"); + + if (main != null + && ((main.startsWith("multipart/") && b != null) + || !main.startsWith("multipart/"))) { + mimeType = main; + this.subType = subtype; + this.mediaType = type; + } + + if (MimeUtil.isMultipart(mimeType)) { + boundary = b; + } + + String c = params.get("charset"); + charset = null; + if (c != null) { + c = c.trim(); + if (c.length() > 0) { + charset = c.toLowerCase(); + } + } + if (charset == null && MEDIA_TYPE_TEXT.equals(mediaType)) { + charset = US_ASCII; + } + + /* + * Add all other parameters to parameters. + */ + parameters.putAll(params); + parameters.remove(""); + parameters.remove("boundary"); + parameters.remove("charset"); + } + + /** + * Return the MimeType + * + * @return mimeType + */ + public String getMimeType() { + return mimeType; + } + + /** + * Return the boundary + * + * @return boundary + */ + public String getBoundary() { + return boundary; + } + + /** + * Return the charset + * + * @return charset + */ + public String getCharset() { + return charset; + } + + /** + * Return all parameters for the BodyDescriptor + * + * @return parameters + */ + public Map getContentTypeParameters() { + return parameters; + } + + /** + * Return the TransferEncoding + * + * @return transferEncoding + */ + public String getTransferEncoding() { + return transferEncoding; + } + + @Override + public String toString() { + return mimeType; + } + + public long getContentLength() { + return contentLength; + } + + public String getMediaType() { + return mediaType; + } + + public String getSubType() { + return subType; + } + + /** + *

Parses a complex field value into a map of key/value pairs. You may + * use this, for example, to parse a definition like + *

+     *   text/plain; charset=UTF-8; boundary=foobar
+     * 
+ * The above example would return a map with the keys "", "charset", + * and "boundary", and the values "text/plain", "UTF-8", and "foobar". + *

+ * Header value will be unfolded and excess white space trimmed. + *

+ * @param pValue The field value to parse. + * @return The result map; use the key "" to retrieve the first value. + */ + public static Map getHeaderParams( + String pValue, DecodeMonitor monitor) throws MimeException { + pValue = pValue.trim(); + + Map result = new HashMap(); // split main value and parameters String main; String rest; - if (headerValue.indexOf(";") == -1) { - main = headerValue; + if (pValue.indexOf(";") == -1) { + main = pValue; rest = null; } else { - main = headerValue.substring(0, headerValue.indexOf(";")); - rest = headerValue.substring(main.length() + 1); + main = pValue.substring(0, pValue.indexOf(";")); + rest = pValue.substring(main.length() + 1); } result.put("", main); if (rest != null) { char[] chars = rest.toCharArray(); - StringBuffer paramName = new StringBuffer(); - StringBuffer paramValue = new StringBuffer(); + StringBuilder paramName = new StringBuilder(64); + StringBuilder paramValue = new StringBuilder(64); final byte READY_FOR_NAME = 0; final byte IN_NAME = 1; @@ -181,9 +299,7 @@ public class BodyDescriptor { byte state = READY_FOR_NAME; boolean escaped = false; - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; - + for (char c : chars) { switch (state) { case ERROR: if (c == ';') @@ -192,13 +308,15 @@ public class BodyDescriptor { case READY_FOR_NAME: if (c == '=') { - log.error("Expected header param name, got '='"); + if (monitor.warn("Expected header param name, got '='", "ignoring")) { + throw new MimeException("Expected header param name, got '='"); + } state = ERROR; break; } - paramName = new StringBuffer(); - paramValue = new StringBuffer(); + paramName.setLength(0); + paramValue.setLength(0); state = IN_NAME; // fall-through @@ -271,7 +389,7 @@ public class BodyDescriptor { break; } break; - + case IN_QUOTED_VALUE: switch (c) { case '"': @@ -283,10 +401,10 @@ public class BodyDescriptor { state = VALUE_DONE; } else { escaped = false; - paramValue.append(c); + paramValue.append(c); } break; - + case '\\': if (escaped) { paramValue.append('\\'); @@ -317,94 +435,4 @@ public class BodyDescriptor { return result; } - - - public boolean isMimeType(String mimeType) { - return this.mimeType.equals(mimeType.toLowerCase()); - } - - /** - * Return true if the BodyDescriptor belongs to a message - * - * @return - */ - public boolean isMessage() { - return mimeType.equals("message/rfc822"); - } - - /** - * Retrun true if the BodyDescripotro belogns to a multipart - * - * @return - */ - public boolean isMultipart() { - return mimeType.startsWith("multipart/"); - } - - /** - * Return the MimeType - * - * @return mimeType - */ - public String getMimeType() { - return mimeType; - } - - /** - * Return the boundary - * - * @return boundary - */ - public String getBoundary() { - return boundary; - } - - /** - * Return the charset - * - * @return charset - */ - public String getCharset() { - return charset; - } - - /** - * Return all parameters for the BodyDescriptor - * - * @return parameters - */ - public Map getParameters() { - return parameters; - } - - /** - * Return the TransferEncoding - * - * @return transferEncoding - */ - public String getTransferEncoding() { - return transferEncoding; - } - - /** - * Return true if it's base64 encoded - * - * @return - * - */ - public boolean isBase64Encoded() { - return "base64".equals(transferEncoding); - } - - /** - * Return true if it's quoted-printable - * @return - */ - public boolean isQuotedPrintableEncoded() { - return "quoted-printable".equals(transferEncoding); - } - - public String toString() { - return mimeType; - } } diff --git a/src/org/apache/james/mime4j/stream/EntityStateMachine.java b/src/org/apache/james/mime4j/stream/EntityStateMachine.java new file mode 100644 index 000000000..fcefe8ba3 --- /dev/null +++ b/src/org/apache/james/mime4j/stream/EntityStateMachine.java @@ -0,0 +1,112 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + +import org.apache.james.mime4j.MimeException; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Represents the interal state of a MIME entity, which is being retrieved + * from an input stream by a MIME parser. + */ +public interface EntityStateMachine { + + /** + * Return the current state of the entity. + * + * @see EntityStates + * + * @return current state + */ + int getState(); + + /** + * Sets the current recursion mode. + * The recursion mode specifies the approach taken to parsing parts. + * {@link RecursionMode#M_RAW} mode does not parse the part at all. + * {@link RecursionMode#M_RECURSE} mode recursively parses each mail + * when an message/rfc822 part is encounted; + * {@link RecursionMode#M_NO_RECURSE} does not. + * + * @see RecursionMode + * + * @param recursionMode + */ + void setRecursionMode(int recursionMode); + + /** + * Advances the state machine to the next state in the + * process of the MIME stream parsing. This method + * may return an new state machine that represents an embedded + * entity, which must be parsed before the parsing process of + * the current entity can proceed. + * + * @return a state machine of an embedded entity, if encountered, + * null otherwise. + * + * @throws IOException if an I/O error occurs. + * @throws MimeException if the message can not be processed due + * to the MIME specification violation. + */ + EntityStateMachine advance() throws IOException, MimeException; + + /** + * Returns description of the entity body. + * + * @return body description + * + * @throws IllegalStateException if the body description cannot be + * obtained at the current stage of the parsing process. + */ + BodyDescriptor getBodyDescriptor() throws IllegalStateException; + + /** + * Returns content stream of the entity body. + * + * @return input stream + * + * @throws IllegalStateException if the content stream cannot be + * obtained at the current stage of the parsing process. + */ + InputStream getContentStream() throws IllegalStateException; + + /** + * Returns the decoded content stream of the entity body. + * + * @return input stream + * + * @throws IllegalStateException if the content stream cannot be + * obtained at the current stage of the parsing process. + */ + InputStream getDecodedContentStream() throws IllegalStateException; + + /** + * Returns current header field. + * + * @return header field + * + * @throws IllegalStateException if a header field cannot be + * obtained at the current stage of the parsing process. + */ + RawField getField() throws IllegalStateException; + +} diff --git a/src/org/apache/james/mime4j/stream/EntityStates.java b/src/org/apache/james/mime4j/stream/EntityStates.java new file mode 100644 index 000000000..363bacba5 --- /dev/null +++ b/src/org/apache/james/mime4j/stream/EntityStates.java @@ -0,0 +1,95 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + +/** + * Enumeration of states an entity is expected to go through + * in the process of the MIME stream parsing. + */ +public interface EntityStates { + + /** + * This token indicates, that the MIME stream has been completely + * and successfully parsed, and no more data is available. + */ + int T_END_OF_STREAM = -1; + /** + * This token indicates, that the MIME stream is currently + * at the beginning of a message. + */ + int T_START_MESSAGE = 0; + /** + * This token indicates, that the MIME stream is currently + * at the end of a message. + */ + int T_END_MESSAGE = 1; + /** + * This token indicates, that a raw entity is currently being processed. + */ + int T_RAW_ENTITY = 2; + /** + * This token indicates, that a message parts headers are now + * being parsed. + */ + int T_START_HEADER = 3; + /** + * This token indicates, that a message parts field has now + * been parsed. + */ + int T_FIELD = 4; + /** + * This token indicates, that part headers have now been + * parsed. + */ + int T_END_HEADER = 5; + /** + * This token indicates, that a multipart body is being parsed. + */ + int T_START_MULTIPART = 6; + /** + * This token indicates, that a multipart body has been parsed. + */ + int T_END_MULTIPART = 7; + /** + * This token indicates, that a multiparts preamble is being + * parsed. + */ + int T_PREAMBLE = 8; + /** + * This token indicates, that a multiparts epilogue is being + * parsed. + */ + int T_EPILOGUE = 9; + /** + * This token indicates, that the MIME stream is currently + * at the beginning of a body part. + */ + int T_START_BODYPART = 10; + /** + * This token indicates, that the MIME stream is currently + * at the end of a body part. + */ + int T_END_BODYPART = 11; + /** + * This token indicates, that an atomic entity is being parsed. + */ + int T_BODY = 12; + +} diff --git a/src/org/apache/james/mime4j/stream/Event.java b/src/org/apache/james/mime4j/stream/Event.java new file mode 100644 index 000000000..d80b8cbf1 --- /dev/null +++ b/src/org/apache/james/mime4j/stream/Event.java @@ -0,0 +1,74 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + +/** + * Enumerates events which can be monitored. + */ +public final class Event { + + /** Indicates that a body part ended prematurely. */ + public static final Event MIME_BODY_PREMATURE_END + = new Event("Body part ended prematurely. " + + "Boundary detected in header or EOF reached."); + /** Indicates that unexpected end of headers detected.*/ + public static final Event HEADERS_PREMATURE_END + = new Event("Unexpected end of headers detected. " + + "Higher level boundary detected or EOF reached."); + /** Indicates that unexpected end of headers detected.*/ + public static final Event INVALID_HEADER + = new Event("Invalid header encountered"); + /** Indicates that an obsolete syntax header has been detected */ + public static final Event OBSOLETE_HEADER + = new Event("Obsolete header encountered"); + + private final String code; + + public Event(final String code) { + super(); + if (code == null) { + throw new IllegalArgumentException("Code may not be null"); + } + this.code = code; + } + + @Override + public int hashCode() { + return code.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (obj instanceof Event) { + Event that = (Event) obj; + return this.code.equals(that.code); + } else { + return false; + } + } + + @Override + public String toString() { + return code; + } + +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/stream/MimeEntity.java b/src/org/apache/james/mime4j/stream/MimeEntity.java new file mode 100644 index 000000000..fa69b294e --- /dev/null +++ b/src/org/apache/james/mime4j/stream/MimeEntity.java @@ -0,0 +1,291 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.codec.Base64InputStream; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.codec.QuotedPrintableInputStream; +import org.apache.james.mime4j.io.BufferedLineReaderInputStream; +import org.apache.james.mime4j.io.LimitedInputStream; +import org.apache.james.mime4j.io.LineNumberSource; +import org.apache.james.mime4j.io.LineReaderInputStream; +import org.apache.james.mime4j.io.LineReaderInputStreamAdaptor; +import org.apache.james.mime4j.io.MimeBoundaryInputStream; +import org.apache.james.mime4j.util.MimeUtil; + +public class MimeEntity extends AbstractEntity { + + private final LineNumberSource lineSource; + private final BufferedLineReaderInputStream inbuffer; + + private int recursionMode; + private MimeBoundaryInputStream currentMimePartStream; + private LineReaderInputStreamAdaptor dataStream; + + private byte[] tmpbuf; + + public MimeEntity( + LineNumberSource lineSource, + InputStream instream, + MutableBodyDescriptor body, + int startState, + int endState, + MimeEntityConfig config, + DecodeMonitor monitor) { + super(body, startState, endState, config, monitor); + this.lineSource = lineSource; + this.inbuffer = new BufferedLineReaderInputStream( + instream, + 4 * 1024, + config.getMaxLineLen()); + this.dataStream = new LineReaderInputStreamAdaptor( + inbuffer, + config.getMaxLineLen()); + } + + public MimeEntity( + LineNumberSource lineSource, + InputStream instream, + MutableBodyDescriptor body, + int startState, + int endState, + MimeEntityConfig config) { + this(lineSource, instream, body, startState, endState, config, config.isStrictParsing() ? DecodeMonitor.STRICT : DecodeMonitor.SILENT); + } + + public MimeEntity( + LineNumberSource lineSource, + InputStream instream, + MutableBodyDescriptor body) { + this(lineSource, instream, body, EntityStates.T_START_MESSAGE, EntityStates.T_END_MESSAGE, + new MimeEntityConfig(), DecodeMonitor.SILENT); + } + + public int getRecursionMode() { + return recursionMode; + } + + public void setRecursionMode(int recursionMode) { + this.recursionMode = recursionMode; + } + + public void stop() { + this.inbuffer.truncate(); + } + + @Override + protected int getLineNumber() { + if (lineSource == null) + return -1; + else + return lineSource.getLineNumber(); + } + + @Override + protected LineReaderInputStream getDataStream() { + return dataStream; + } + + public EntityStateMachine advance() throws IOException, MimeException { + switch (state) { + case EntityStates.T_START_MESSAGE: + state = EntityStates.T_START_HEADER; + break; + case EntityStates.T_START_BODYPART: + state = EntityStates.T_START_HEADER; + break; + case EntityStates.T_START_HEADER: + case EntityStates.T_FIELD: + state = parseField() ? EntityStates.T_FIELD : EntityStates.T_END_HEADER; + break; + case EntityStates.T_END_HEADER: + String mimeType = body.getMimeType(); + if (recursionMode == RecursionMode.M_FLAT) { + state = EntityStates.T_BODY; + } else if (MimeUtil.isMultipart(mimeType)) { + state = EntityStates.T_START_MULTIPART; + clearMimePartStream(); + } else if (recursionMode != RecursionMode.M_NO_RECURSE + && MimeUtil.isMessage(mimeType)) { + state = EntityStates.T_BODY; + return nextMessage(); + } else { + state = EntityStates.T_BODY; + } + break; + case EntityStates.T_START_MULTIPART: + if (dataStream.isUsed()) { + advanceToBoundary(); + state = EntityStates.T_END_MULTIPART; + break; + } else { + createMimePartStream(); + state = EntityStates.T_PREAMBLE; + + if (!currentMimePartStream.isEmptyStream()) break; + // continue to next state + } + case EntityStates.T_PREAMBLE: + // removed specific code. Fallback to T_IN_BODYPART that + // better handle missing parts. + // Removed the T_IN_BODYPART state (always use T_PREAMBLE) + advanceToBoundary(); + if (currentMimePartStream.eof() && !currentMimePartStream.isLastPart()) { + monitor(Event.MIME_BODY_PREMATURE_END); + } else { + if (!currentMimePartStream.isLastPart()) { + clearMimePartStream(); + createMimePartStream(); + return nextMimeEntity(); + } + } + clearMimePartStream(); + state = EntityStates.T_EPILOGUE; + break; + case EntityStates.T_EPILOGUE: + state = EntityStates.T_END_MULTIPART; + break; + case EntityStates.T_BODY: + case EntityStates.T_END_MULTIPART: + state = endState; + break; + default: + if (state == endState) { + state = EntityStates.T_END_OF_STREAM; + break; + } + throw new IllegalStateException("Invalid state: " + stateToString(state)); + } + return null; + } + + private void createMimePartStream() throws MimeException, IOException { + String boundary = body.getBoundary(); + // TODO move the following lines inside the MimeBoundaryInputStream constructor + int bufferSize = 2 * boundary.length(); + if (bufferSize < 4096) { + bufferSize = 4096; + } + try { + inbuffer.ensureCapacity(bufferSize); + currentMimePartStream = new MimeBoundaryInputStream(inbuffer, boundary); + } catch (IllegalArgumentException e) { + // thrown when boundary is too long + throw new MimeException(e.getMessage(), e); + } + dataStream = new LineReaderInputStreamAdaptor( + currentMimePartStream, + config.getMaxLineLen()); + } + + private void clearMimePartStream() { + currentMimePartStream = null; + dataStream = new LineReaderInputStreamAdaptor( + inbuffer, + config.getMaxLineLen()); + } + + private void advanceToBoundary() throws IOException { + if (!dataStream.eof()) { + if (tmpbuf == null) { + tmpbuf = new byte[2048]; + } + InputStream instream = getLimitedContentStream(); + while (instream.read(tmpbuf)!= -1) { + } + } + } + + private EntityStateMachine nextMessage() { + // optimize nesting of streams returning the "lower" stream instead of + // always return dataStream (that would add a LineReaderInputStreamAdaptor in the chain) + InputStream instream = currentMimePartStream != null ? currentMimePartStream : inbuffer; + instream = decodedStream(instream); + return nextMimeEntity(EntityStates.T_START_MESSAGE, EntityStates.T_END_MESSAGE, instream); + } + + private InputStream decodedStream(InputStream instream) { + String transferEncoding = body.getTransferEncoding(); + if (MimeUtil.isBase64Encoding(transferEncoding)) { + instream = new Base64InputStream(instream, monitor); + } else if (MimeUtil.isQuotedPrintableEncoded(transferEncoding)) { + instream = new QuotedPrintableInputStream(instream, monitor); + } + return instream; + } + + private EntityStateMachine nextMimeEntity() { + return nextMimeEntity(EntityStates.T_START_BODYPART, EntityStates.T_END_BODYPART, currentMimePartStream); + } + + private EntityStateMachine nextMimeEntity(int startState, int endState, InputStream instream) { + if (recursionMode == RecursionMode.M_RAW) { + RawEntity message = new RawEntity(instream); + return message; + } else { + MimeEntity mimeentity = new MimeEntity( + lineSource, + instream, + body.newChild(), + startState, + endState, + config, + monitor); + mimeentity.setRecursionMode(recursionMode); + return mimeentity; + } + } + + private InputStream getLimitedContentStream() { + long maxContentLimit = config.getMaxContentLen(); + if (maxContentLimit >= 0) { + return new LimitedInputStream(dataStream, maxContentLimit); + } else { + return dataStream; + } + } + + /** + * @see org.apache.james.mime4j.stream.EntityStateMachine#getContentStream() + */ + public InputStream getContentStream() { + switch (state) { + case EntityStates.T_START_MULTIPART: + case EntityStates.T_PREAMBLE: + case EntityStates.T_EPILOGUE: + case EntityStates.T_BODY: + return getLimitedContentStream(); + default: + throw new IllegalStateException("Invalid state: " + stateToString(state)); + } + } + + /** + * @see org.apache.james.mime4j.stream.EntityStateMachine#getDecodedContentStream() + */ + public InputStream getDecodedContentStream() throws IllegalStateException { + return decodedStream(getContentStream()); + } + +} diff --git a/src/org/apache/james/mime4j/stream/MimeEntityConfig.java b/src/org/apache/james/mime4j/stream/MimeEntityConfig.java new file mode 100644 index 000000000..a0d8e34be --- /dev/null +++ b/src/org/apache/james/mime4j/stream/MimeEntityConfig.java @@ -0,0 +1,265 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + +import org.apache.james.mime4j.MimeException; + +/** + * MIME entity configuration + */ +public final class MimeEntityConfig implements Cloneable { + + private boolean strictParsing; + private int maxLineLen; + private int maxHeaderCount; + private int maxHeaderLen; + private long maxContentLen; + private boolean countLineNumbers; + private String headlessParsing; + private boolean malformedHeaderStartsBody; + + public MimeEntityConfig() { + this.strictParsing = false; + this.countLineNumbers = false; + this.malformedHeaderStartsBody = false; + this.maxLineLen = 1000; + this.maxHeaderCount = 1000; + this.maxHeaderLen = 10000; + this.maxContentLen = -1; + this.headlessParsing = null; + } + + /** + * @see #setMalformedHeaderStartsBody(boolean) + * + * @return true if malformed header should "end" the headers and be + * part of the body + */ + public boolean isMalformedHeaderStartsBody() { + return malformedHeaderStartsBody; + } + + /** + * Define the behaviour for dealing with malformed headers while in lenient + * mode + * + * @param malformedHeaderStartsBody true to make the parser + * interpret a malformed header as end of the headers and + * as part of the body (as if the CRLF separator was missing). + * false to simply ignore malformed headers and + * continue parsing headers from the following line. + */ + public void setMalformedHeaderStartsBody(boolean malformedHeaderStartsBody) { + this.malformedHeaderStartsBody = malformedHeaderStartsBody; + } + + /** + * Returns the value of the strict parsing mode + * @see #setStrictParsing(boolean) + * + * @return value of the strict parsing mode + */ + public boolean isStrictParsing() { + return this.strictParsing; + } + + /** + * Defines whether minor violations of the MIME specification should be + * tolerated or should result in a {@link MimeException}. If this parameter + * is set to true, a strict interpretation of the MIME + * specification will be enforced, If this parameter is set to false + * minor violations will result in a warning in the log. + *

+ * Default value: false + * + * @param strictParsing value of the strict parsing mode + */ + public void setStrictParsing(boolean strictParsing) { + this.strictParsing = strictParsing; + } + + /** + * Returns the maximum line length limit + * @see #setMaxLineLen(int) + * + * @return value of the the maximum line length limit + */ + public int getMaxLineLen() { + return this.maxLineLen; + } + + /** + * Sets the maximum line length limit. Parsing of a MIME entity will be terminated + * with a {@link MimeException} if a line is encountered that exceeds the maximum + * length limit. If this parameter is set to a non positive value the line length + * check will be disabled. + *

+ * Default value: 1000 + * + * @param maxLineLen maximum line length limit + */ + public void setMaxLineLen(int maxLineLen) { + this.maxLineLen = maxLineLen; + } + + /** + * Returns the maximum header limit + * @see #setMaxHeaderCount(int) + * + * @return value of the the maximum header limit + */ + public int getMaxHeaderCount() { + return this.maxHeaderCount; + } + + /** + * Sets the maximum header limit. Parsing of a MIME entity will be terminated + * with a {@link MimeException} if the number of headers exceeds the maximum + * limit. If this parameter is set to a non positive value the header limit check + * will be disabled. + *

+ * Default value: 1000 + * + * @param maxHeaderCount maximum header limit + */ + public void setMaxHeaderCount(int maxHeaderCount) { + this.maxHeaderCount = maxHeaderCount; + } + + /** + * Returns the maximum header length limit + * @see #setMaxHeaderLen(int) + * + * @return value of the maximum header length limit + */ + public int getMaxHeaderLen() { + return maxHeaderLen; + } + + /** + * Sets the maximum header length limit. Parsing of a MIME entity will be terminated + * with a {@link MimeException} if the total length of a header exceeds this limit. + * If this parameter is set to a non positive value the header length check will be + * disabled. + *

+ * A message header may be folded across multiple lines. This configuration parameter + * is used to limit the total length of a header, i.e. the sum of the length of all + * lines the header spans across (including line terminators). + *

+ * Default value: 10000 + * + * @param maxHeaderLen maximum header length limit + */ + public void setMaxHeaderLen(int maxHeaderLen) { + this.maxHeaderLen = maxHeaderLen; + } + + /** + * Returns the maximum content length limit + * @see #setMaxContentLen(long) + * + * @return value of the the maximum content length limit + */ + public long getMaxContentLen() { + return maxContentLen; + } + + /** + * Sets the maximum content length limit. Parsing of a MIME entity will be terminated + * with a {@link MimeException} if a content body exceeds the maximum length limit. + * If this parameter is set to a non positive value the content length + * check will be disabled. + *

+ * Default value: -1 + * + * @param maxContentLen maximum content length limit + */ + public void setMaxContentLen(long maxContentLen) { + this.maxContentLen = maxContentLen; + } + + /** + * Returns the value of the line number counting mode. + * + * @return value of the line number counting mode. + */ + public boolean isCountLineNumbers() { + return countLineNumbers; + } + + /** + * Defines whether the parser should count line numbers. If enabled line + * numbers are included in the debug output. + *

+ * Default value: false + * + * @param countLineNumbers + * value of the line number counting mode. + */ + public void setCountLineNumbers(boolean countLineNumbers) { + this.countLineNumbers = countLineNumbers; + } + + /** + * Returns the value of the default content type. + * When not null, indicates that the parsing should be headless. + * + * @return default content type when parsing headless, + * null otherwise + * @see org.apache.james.mime4j.parser.MimeStreamParser#parse(java.io.InputStream) + */ + public String getHeadlessParsing() { + return headlessParsing; + } + + /** + * Defines a default content type. + * When not null, indicates that the parsing should be headless. + *

+ * Default value: null + * + * @param contentType + * value of the default content type when parsing headless, + * null otherwise + * @see org.apache.james.mime4j.parser.MimeStreamParser#parse(java.io.InputStream) + */ + public void setHeadlessParsing(String contentType) { + this.headlessParsing = contentType; + } + + @Override + public MimeEntityConfig clone() { + try { + return (MimeEntityConfig) super.clone(); + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + } + + @Override + public String toString() { + return "[strict parsing: " + strictParsing + ", max line length: " + + maxLineLen + ", max header count: " + maxHeaderCount + + ", max content length: " + maxContentLen + + ", count line numbers: " + countLineNumbers + "]"; + } + +} diff --git a/src/org/apache/james/mime4j/field/UnstructuredField.java b/src/org/apache/james/mime4j/stream/MimeParseEventException.java similarity index 60% rename from src/org/apache/james/mime4j/field/UnstructuredField.java rename to src/org/apache/james/mime4j/stream/MimeParseEventException.java index 6084e4435..8350520f7 100644 --- a/src/org/apache/james/mime4j/field/UnstructuredField.java +++ b/src/org/apache/james/mime4j/stream/MimeParseEventException.java @@ -17,33 +17,34 @@ * under the License. * ****************************************************************/ -package org.apache.james.mime4j.field; - -import org.apache.james.mime4j.decoder.DecoderUtil; +package org.apache.james.mime4j.stream; +import org.apache.james.mime4j.MimeException; /** - * Simple unstructured field such as Subject. - * - * - * @version $Id: UnstructuredField.java,v 1.3 2004/10/25 07:26:46 ntherning Exp $ + * Indicates that strict parsing has been enabled + * and an optional invality has been found in the input. + * {@link #getEvent()} indicates the type of invalidity. */ -public class UnstructuredField extends Field { - private String value; - - protected UnstructuredField(String name, String body, String raw, String value) { - super(name, body, raw); - this.value = value; +public class MimeParseEventException extends MimeException { + + private static final long serialVersionUID = 4632991604246852302L; + private final Event event; + + /** + * Constructs an exception + * @param event MimeTokenStream.Event, not null + */ + public MimeParseEventException(final Event event) { + super(event.toString()); + this.event = event; } - public String getValue() { - return value; - } - - public static class Parser implements FieldParser { - public Field parse(final String name, final String body, final String raw) { - final String value = DecoderUtil.decodeEncodedWords(body); - return new UnstructuredField(name, body, raw, value); - } + /** + * Gets the causal parse event. + * @return MimeTokenStream.Event, not null + */ + public Event getEvent() { + return event; } } diff --git a/src/org/apache/james/mime4j/stream/MimeTokenStream.java b/src/org/apache/james/mime4j/stream/MimeTokenStream.java new file mode 100644 index 000000000..d169e5b3a --- /dev/null +++ b/src/org/apache/james/mime4j/stream/MimeTokenStream.java @@ -0,0 +1,385 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.LinkedList; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.io.LineNumberInputStream; +import org.apache.james.mime4j.io.LineNumberSource; +import org.apache.james.mime4j.util.CharsetUtil; + +/** + *

+ * Parses MIME (or RFC822) message streams of bytes or characters. + * The stream is converted into an event stream. + *

+ *

+ * Typical usage: + *

+ *
+ *      MimeTokenStream stream = new MimeTokenStream();
+ *      stream.parse(new FileInputStream("mime.msg"));
+ *      for (int state = stream.getState();
+ *           state != MimeTokenStream.T_END_OF_STREAM;
+ *           state = stream.next()) {
+ *          switch (state) {
+ *            case MimeTokenStream.T_BODY:
+ *              System.out.println("Body detected, contents = "
+ *                + stream.getInputStream() + ", header data = "
+ *                + stream.getBodyDescriptor());
+ *              break;
+ *            case MimeTokenStream.T_FIELD:
+ *              System.out.println("Header field detected: "
+ *                + stream.getField());
+ *              break;
+ *            case MimeTokenStream.T_START_MULTIPART:
+ *              System.out.println("Multipart message detexted,"
+ *                + " header data = "
+ *                + stream.getBodyDescriptor());
+ *            ...
+ *          }
+ *      }
+ * 
+ *

Instances of {@link MimeTokenStream} are reusable: Invoking the + * method {@link #parse(InputStream)} resets the token streams internal + * state. However, they are definitely not thread safe. If you + * have a multi threaded application, then the suggested use is to have + * one instance per thread.

+ */ +public class MimeTokenStream implements EntityStates, RecursionMode { + + private final MimeEntityConfig config; + private final DecodeMonitor monitor; + private final MutableBodyDescriptorFactory bodyDescFactory; + private final LinkedList entities = new LinkedList(); + + private int state = T_END_OF_STREAM; + private EntityStateMachine currentStateMachine; + private int recursionMode = M_RECURSE; + private MimeEntity rootentity; + + /** + * Constructs a standard (lax) stream. + * Optional validation events will be logged only. + * Use {@link MimeEntityConfig#setStrictParsing(boolean)} to turn on strict + * parsing mode and pass the config object to + * {@link MimeTokenStream#MimeTokenStream(MimeEntityConfig)} to create + * a stream that strictly validates the input. + */ + public MimeTokenStream() { + this(new MimeEntityConfig()); + } + + public MimeTokenStream(final MimeEntityConfig config) { + this(config, null, null); + } + + public MimeTokenStream( + final MimeEntityConfig config, + final MutableBodyDescriptorFactory bodyDescFactory) { + this(config, bodyDescFactory, null); + } + + public MimeTokenStream( + final MimeEntityConfig config, + final MutableBodyDescriptorFactory bodyDescFactory, + final DecodeMonitor monitor) { + super(); + this.config = config; + this.monitor = monitor != null ? monitor : + (config.isStrictParsing() ? DecodeMonitor.STRICT : DecodeMonitor.SILENT); + this.bodyDescFactory = bodyDescFactory; + } + + /** Instructs the {@code MimeTokenStream} to parse the given streams contents. + * If the {@code MimeTokenStream} has already been in use, resets the streams + * internal state. + */ + public void parse(InputStream stream) { + doParse(stream, newBodyDescriptor(), T_START_MESSAGE); + } + + /** Instructs the {@code MimeTokenStream} to parse the given content with + * the content type. The message stream is assumed to have no message header + * and is expected to begin with a message body. This can be the case when + * the message content is transmitted using a different transport protocol + * such as HTTP. + *

+ * If the {@code MimeTokenStream} has already been in use, resets the streams + * internal state. + */ + public void parseHeadless(InputStream stream, String contentType) { + if (contentType == null) { + throw new IllegalArgumentException("Content type may not be null"); + } + MutableBodyDescriptor newBodyDescriptor = newBodyDescriptor(); + try { + newBodyDescriptor.addField(new RawField("Content-Type", contentType)); + } catch (MimeException ex) { + // should never happen + throw new IllegalArgumentException(ex.getMessage()); + } + doParse(stream, newBodyDescriptor, T_END_HEADER); + try { + next(); + } catch (IOException e) { + // Should never happend: the first next after END_HEADER does not produce IO + throw new IllegalStateException(e); + } catch (MimeException e) { + // This should never happen + throw new IllegalStateException(e); + } + } + + /** + * Creates a new instance of {@link BodyDescriptor}. Subclasses may override + * this in order to create body descriptors, that provide more specific + * information. + */ + protected MutableBodyDescriptor newBodyDescriptor() { + final MutableBodyDescriptor result; + if (bodyDescFactory != null) { + result = bodyDescFactory.newInstance(monitor); + } else { + result = new DefaultBodyDescriptor(null, monitor); + } + return result; + } + + public void doParse(InputStream stream, + MutableBodyDescriptor newBodyDescriptor, int start) { + LineNumberSource lineSource = null; + if (config.isCountLineNumbers()) { + LineNumberInputStream lineInput = new LineNumberInputStream(stream); + lineSource = lineInput; + stream = lineInput; + } + + rootentity = new MimeEntity( + lineSource, + stream, + newBodyDescriptor, + start, + T_END_MESSAGE, + config, + monitor); + + rootentity.setRecursionMode(recursionMode); + currentStateMachine = rootentity; + entities.clear(); + entities.add(currentStateMachine); + state = currentStateMachine.getState(); + } + + /** + * Determines if this parser is currently in raw mode. + * + * @return true if in raw mode, false + * otherwise. + * @see #setRecursionMode(int) + */ + public boolean isRaw() { + return recursionMode == M_RAW; + } + + /** + * Gets the current recursion mode. + * The recursion mode specifies the approach taken to parsing parts. + * {@link #M_RAW} mode does not parse the part at all. + * {@link #M_RECURSE} mode recursively parses each mail + * when an message/rfc822 part is encounted; + * {@link #M_NO_RECURSE} does not. + * @return {@link #M_RECURSE}, {@link #M_RAW} or {@link #M_NO_RECURSE} + */ + public int getRecursionMode() { + return recursionMode; + } + + /** + * Sets the current recursion. + * The recursion mode specifies the approach taken to parsing parts. + * {@link #M_RAW} mode does not parse the part at all. + * {@link #M_RECURSE} mode recursively parses each mail + * when an message/rfc822 part is encounted; + * {@link #M_NO_RECURSE} does not. + * @param mode {@link #M_RECURSE}, {@link #M_RAW} or {@link #M_NO_RECURSE} + */ + public void setRecursionMode(int mode) { + recursionMode = mode; + if (currentStateMachine != null) { + currentStateMachine.setRecursionMode(mode); + } + } + + /** + * Finishes the parsing and stops reading lines. + * NOTE: No more lines will be parsed but the parser + * will still trigger 'end' events to match previously + * triggered 'start' events. + */ + public void stop() { + rootentity.stop(); + } + + /** + * Returns the current state. + */ + public int getState() { + return state; + } + + /** + * This method returns the raw entity, preamble, or epilogue contents. + *

+ * This method is valid, if {@link #getState()} returns either of + * {@link #T_RAW_ENTITY}, {@link #T_PREAMBLE}, or {@link #T_EPILOGUE}. + * + * @return Data stream, depending on the current state. + * @throws IllegalStateException {@link #getState()} returns an + * invalid value. + */ + public InputStream getInputStream() { + return currentStateMachine.getContentStream(); + } + + /** + * This method returns a transfer decoded stream based on the MIME + * fields with the standard defaults. + *

+ * This method is valid, if {@link #getState()} returns either of + * {@link #T_RAW_ENTITY}, {@link #T_PREAMBLE}, or {@link #T_EPILOGUE}. + * + * @return Data stream, depending on the current state. + * @throws IllegalStateException {@link #getState()} returns an + * invalid value. + */ + public InputStream getDecodedInputStream() { + return currentStateMachine.getDecodedContentStream(); + } + + /** + * Gets a reader configured for the current body or body part. + * The reader will return a transfer and charset decoded + * stream of characters based on the MIME fields with the standard + * defaults. + * This is a conveniance method and relies on {@link #getInputStream()}. + * Consult the javadoc for that method for known limitations. + * + * @return Reader, not null + * @see #getInputStream + * @throws IllegalStateException {@link #getState()} returns an + * invalid value + * @throws UnsupportedCharsetException if there is no JVM support + * for decoding the charset + * @throws IllegalCharsetNameException if the charset name specified + * in the mime type is illegal + */ + public Reader getReader() { + final BodyDescriptor bodyDescriptor = getBodyDescriptor(); + final String mimeCharset = bodyDescriptor.getCharset(); + final Charset charset; + if (mimeCharset == null || "".equals(mimeCharset)) { + charset = CharsetUtil.US_ASCII; + } else { + charset = Charset.forName(mimeCharset); + } + final InputStream instream = getDecodedInputStream(); + return new InputStreamReader(instream, charset); + } + + /** + *

Gets a descriptor for the current entity. + * This method is valid if {@link #getState()} returns:

+ *
    + *
  • {@link #T_BODY}
  • + *
  • {@link #T_START_MULTIPART}
  • + *
  • {@link #T_EPILOGUE}
  • + *
  • {@link #T_PREAMBLE}
  • + *
+ * @return BodyDescriptor, not nulls + */ + public BodyDescriptor getBodyDescriptor() { + return currentStateMachine.getBodyDescriptor(); + } + + /** + * This method is valid, if {@link #getState()} returns {@link #T_FIELD}. + * @return String with the fields raw contents. + * @throws IllegalStateException {@link #getState()} returns another + * value than {@link #T_FIELD}. + */ + public RawField getField() { + return currentStateMachine.getField(); + } + + /** + * This method advances the token stream to the next token. + * @throws IllegalStateException The method has been called, although + * {@link #getState()} was already {@link #T_END_OF_STREAM}. + */ + public int next() throws IOException, MimeException { + if (state == T_END_OF_STREAM || currentStateMachine == null) { + throw new IllegalStateException("No more tokens are available."); + } + while (currentStateMachine != null) { + EntityStateMachine next = currentStateMachine.advance(); + if (next != null) { + entities.add(next); + currentStateMachine = next; + } + state = currentStateMachine.getState(); + if (state != T_END_OF_STREAM) { + return state; + } + entities.removeLast(); + if (entities.isEmpty()) { + currentStateMachine = null; + } else { + currentStateMachine = entities.getLast(); + currentStateMachine.setRecursionMode(recursionMode); + } + } + state = T_END_OF_STREAM; + return state; + } + + /** + * Renders a state as a string suitable for logging. + * @param state + * @return rendered as string, not null + */ + public static final String stateToString(int state) { + return AbstractEntity.stateToString(state); + } + + + public MimeEntityConfig getConfig() { + return config; + } +} diff --git a/src/org/apache/james/mime4j/stream/MutableBodyDescriptor.java b/src/org/apache/james/mime4j/stream/MutableBodyDescriptor.java new file mode 100644 index 000000000..de070b6bf --- /dev/null +++ b/src/org/apache/james/mime4j/stream/MutableBodyDescriptor.java @@ -0,0 +1,38 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + + +package org.apache.james.mime4j.stream; + +import org.apache.james.mime4j.MimeException; + +/** + * Adds mutator. + */ +public interface MutableBodyDescriptor extends BodyDescriptor { + + /** + * Adds a field to the body descriptor. + * @param field the MIME field. + */ + void addField(RawField field) throws MimeException; + + MutableBodyDescriptor newChild(); + +} diff --git a/src/org/apache/james/mime4j/stream/MutableBodyDescriptorFactory.java b/src/org/apache/james/mime4j/stream/MutableBodyDescriptorFactory.java new file mode 100644 index 000000000..089368e6e --- /dev/null +++ b/src/org/apache/james/mime4j/stream/MutableBodyDescriptorFactory.java @@ -0,0 +1,31 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + +import org.apache.james.mime4j.codec.DecodeMonitor; + +/** + * {@link MutableBodyDescriptor} factory. + */ +public interface MutableBodyDescriptorFactory { + + MutableBodyDescriptor newInstance(DecodeMonitor monitor); + +} diff --git a/src/org/apache/james/mime4j/stream/RawEntity.java b/src/org/apache/james/mime4j/stream/RawEntity.java new file mode 100644 index 000000000..fda03595a --- /dev/null +++ b/src/org/apache/james/mime4j/stream/RawEntity.java @@ -0,0 +1,98 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + + +import java.io.InputStream; + +/** + * Raw MIME entity. Such entities will not be parsed into elements + * by the parser. They are meant to be consumed as a raw data stream + * by the caller. + */ +public class RawEntity implements EntityStateMachine { + + private final InputStream stream; + + private int state; + + RawEntity(InputStream stream) { + this.stream = stream; + this.state = EntityStates.T_RAW_ENTITY; + } + + public int getState() { + return state; + } + + /** + * This method has no effect. + */ + public void setRecursionMode(int recursionMode) { + } + + public EntityStateMachine advance() { + state = EntityStates.T_END_OF_STREAM; + return null; + } + + /** + * Returns raw data stream. + */ + public InputStream getContentStream() { + return stream; + } + + /** + * This method has no effect and always returns null. + */ + public BodyDescriptor getBodyDescriptor() { + return null; + } + + /** + * This method has no effect and always returns null. + */ + public RawField getField() { + return null; + } + + /** + * This method has no effect and always returns null. + */ + public String getFieldName() { + return null; + } + + /** + * This method has no effect and always returns null. + */ + public String getFieldValue() { + return null; + } + + /** + * @see org.apache.james.mime4j.stream.EntityStateMachine#getDecodedContentStream() + */ + public InputStream getDecodedContentStream() throws IllegalStateException { + throw new IllegalStateException("Raw entity does not support stream decoding"); + } + +} diff --git a/src/org/apache/james/mime4j/stream/RawField.java b/src/org/apache/james/mime4j/stream/RawField.java new file mode 100644 index 000000000..a06ca9586 --- /dev/null +++ b/src/org/apache/james/mime4j/stream/RawField.java @@ -0,0 +1,136 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.stream; + +import java.util.BitSet; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; +import org.apache.james.mime4j.util.MimeUtil; + +/** + * The basic immutable MIME field. + */ +public class RawField { + + private static final BitSet fieldChars = new BitSet(); + + static { + for (int i = 0x21; i <= 0x39; i++) { + fieldChars.set(i); + } + for (int i = 0x3b; i <= 0x7e; i++) { + fieldChars.set(i); + } + } + + + private ByteSequence raw; + private int colonIdx; + private int headerNameEndIdx; + + private String name; + private String body; + private final boolean obsoleteSyntax; + + public RawField(String name, String body) { + this.name = name; + this.body = body; + this.raw = null; + this.obsoleteSyntax = false; + } + + /** + * @param raw bytes + * @throws MimeException on malformed data + */ + public RawField(ByteSequence raw) throws MimeException { + this.raw = raw; + + colonIdx = -1; + boolean obsolete = false; + for (int i = 0; i < raw.length(); i++) { + if (!fieldChars.get(raw.byteAt(i) & 0xff)) { + headerNameEndIdx = i; + for (; i < raw.length(); i++) { + int j = raw.byteAt(i) & 0xff; + if (j == ':') { + colonIdx = i; + break; + } else if (j != 0x20 && j != 0x09) { + throw new MimeException("Invalid header"); + } else { + obsolete = true; + } + } + break; + } + } + if (colonIdx == -1) throw new MimeException("Invalid header. No colon found."); + obsoleteSyntax = obsolete; + } + + public String getName() { + if (name == null) { + name = parseName(); + } + + return name; + } + + public String getBody() { + if (body == null) { + body = parseBody(); + } + + return body; + } + + public ByteSequence getRaw() { + if (raw == null) { + raw = ContentUtil.encode(MimeUtil.fold(name+": "+body, 0)); + } + return raw; + } + + @Override + public String toString() { + return getName() + ": " + getBody(); + } + + private String parseName() { + // make sure we ignore ending WSP (obsolete rfc822 syntax) + return ContentUtil.decode(raw, 0, headerNameEndIdx); + } + + private String parseBody() { + int offset = colonIdx + 1; + // if the header body starts with a space we remove it. + if (raw.length() > offset + 1 && (raw.byteAt(offset) & 0xff) == 0x20) offset++; + int length = raw.length() - offset; + return MimeUtil.unfold(ContentUtil.decode(raw, offset, length)); + } + + public boolean isObsoleteSyntax() { + return obsoleteSyntax; + } + +} diff --git a/src/org/apache/james/mime4j/message/AbstractBody.java b/src/org/apache/james/mime4j/stream/RecursionMode.java similarity index 66% rename from src/org/apache/james/mime4j/message/AbstractBody.java rename to src/org/apache/james/mime4j/stream/RecursionMode.java index 190e882e0..bb3204d5a 100644 --- a/src/org/apache/james/mime4j/message/AbstractBody.java +++ b/src/org/apache/james/mime4j/stream/RecursionMode.java @@ -17,31 +17,29 @@ * under the License. * ****************************************************************/ -package org.apache.james.mime4j.message; - +package org.apache.james.mime4j.stream; /** - * Abstract Body implementation providing the parent - * functionality required by bodies. - * - * - * @version $Id: AbstractBody.java,v 1.2 2004/10/02 12:41:11 ntherning Exp $ + * Enumeration of parsing modes. */ -public abstract class AbstractBody implements Body { - private Entity parent = null; - +public interface RecursionMode { + /** - * @see org.apache.james.mime4j.message.Body#getParent() + * Recursively parse every message/rfc822 part */ - public Entity getParent() { - return parent; - } - + int M_RECURSE = 0; /** - * @see org.apache.james.mime4j.message.Body#setParent(org.apache.james.mime4j.message.Entity) + * Do not recurse message/rfc822 parts */ - public void setParent(Entity parent) { - this.parent = parent; - } + int M_NO_RECURSE = 1; + /** + * Parse into raw entities + */ + int M_RAW = 2; + /** + * Do not recurse message/rfc822 parts + * and treat multiparts as a single flat body. + */ + int M_FLAT = 3; } diff --git a/src/org/apache/james/mime4j/util/ByteArrayBuffer.java b/src/org/apache/james/mime4j/util/ByteArrayBuffer.java new file mode 100644 index 000000000..6aa0f1933 --- /dev/null +++ b/src/org/apache/james/mime4j/util/ByteArrayBuffer.java @@ -0,0 +1,181 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.util; + + +/** + * A resizable byte array. + */ +public final class ByteArrayBuffer implements ByteSequence { + + private byte[] buffer; + private int len; + + public ByteArrayBuffer(int capacity) { + super(); + if (capacity < 0) { + throw new IllegalArgumentException("Buffer capacity may not be negative"); + } + this.buffer = new byte[capacity]; + } + + public ByteArrayBuffer(byte[] bytes, boolean dontCopy) { + this(bytes, bytes.length, dontCopy); + } + + public ByteArrayBuffer(byte[] bytes, int len, boolean dontCopy) { + if (bytes == null) + throw new IllegalArgumentException(); + if (len < 0 || len > bytes.length) + throw new IllegalArgumentException(); + + if (dontCopy) { + this.buffer = bytes; + } else { + this.buffer = new byte[len]; + System.arraycopy(bytes, 0, this.buffer, 0, len); + } + + this.len = len; + } + + private void expand(int newlen) { + byte newbuffer[] = new byte[Math.max(this.buffer.length << 1, newlen)]; + System.arraycopy(this.buffer, 0, newbuffer, 0, this.len); + this.buffer = newbuffer; + } + + public void append(final byte[] b, int off, int len) { + if (b == null) { + return; + } + if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) < 0) || ((off + len) > b.length)) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return; + } + int newlen = this.len + len; + if (newlen > this.buffer.length) { + expand(newlen); + } + System.arraycopy(b, off, this.buffer, this.len, len); + this.len = newlen; + } + + public void append(int b) { + int newlen = this.len + 1; + if (newlen > this.buffer.length) { + expand(newlen); + } + this.buffer[this.len] = (byte)b; + this.len = newlen; + } + + public void clear() { + this.len = 0; + } + + public byte[] toByteArray() { + byte[] b = new byte[this.len]; + if (this.len > 0) { + System.arraycopy(this.buffer, 0, b, 0, this.len); + } + return b; + } + + public byte byteAt(int i) { + if (i < 0 || i >= this.len) + throw new IndexOutOfBoundsException(); + + return this.buffer[i]; + } + + public int capacity() { + return this.buffer.length; + } + + public int length() { + return this.len; + } + + public byte[] buffer() { + return this.buffer; + } + + public int indexOf(byte b) { + return indexOf(b, 0, this.len); + } + + public int indexOf(byte b, int beginIndex, int endIndex) { + if (beginIndex < 0) { + beginIndex = 0; + } + if (endIndex > this.len) { + endIndex = this.len; + } + if (beginIndex > endIndex) { + return -1; + } + for (int i = beginIndex; i < endIndex; i++) { + if (this.buffer[i] == b) { + return i; + } + } + return -1; + } + + public void setLength(int len) { + if (len < 0 || len > this.buffer.length) { + throw new IndexOutOfBoundsException(); + } + this.len = len; + } + + public void remove(int off, int len) { + if ((off < 0) || (off > this.len) || (len < 0) || + ((off + len) < 0) || ((off + len) > this.len)) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return; + } + int remaining = this.len - off - len; + if (remaining > 0) { + System.arraycopy(this.buffer, off + len, this.buffer, off, remaining); + } + this.len -= len; + } + + public boolean isEmpty() { + return this.len == 0; + } + + public boolean isFull() { + return this.len == this.buffer.length; + } + + @Override + public String toString() { + return new String(toByteArray()); + } + +} diff --git a/src/org/apache/james/mime4j/util/ByteSequence.java b/src/org/apache/james/mime4j/util/ByteSequence.java new file mode 100644 index 000000000..edcf046f0 --- /dev/null +++ b/src/org/apache/james/mime4j/util/ByteSequence.java @@ -0,0 +1,58 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.util; + +/** + * An immutable sequence of bytes. + */ +public interface ByteSequence { + + /** + * An empty byte sequence. + */ + ByteSequence EMPTY = new EmptyByteSequence(); + + /** + * Returns the length of this byte sequence. + * + * @return the number of bytes in this sequence. + */ + int length(); + + /** + * Returns the byte value at the specified index. + * + * @param index + * the index of the byte value to be returned. + * @return the corresponding byte value + * @throws IndexOutOfBoundsException + * if index < 0 || index >= length(). + */ + byte byteAt(int index); + + /** + * Copies the contents of this byte sequence into a newly allocated byte + * array and returns that array. + * + * @return a byte array holding a copy of this byte sequence. + */ + byte[] toByteArray(); + +} diff --git a/src/org/apache/james/mime4j/util/CharsetUtil.java b/src/org/apache/james/mime4j/util/CharsetUtil.java index 289fb4a1d..ccdb6a4c8 100644 --- a/src/org/apache/james/mime4j/util/CharsetUtil.java +++ b/src/org/apache/james/mime4j/util/CharsetUtil.java @@ -20,14 +20,11 @@ package org.apache.james.mime4j.util; import java.io.UnsupportedEncodingException; -import java.nio.charset.IllegalCharsetNameException; -import java.nio.charset.UnsupportedCharsetException; import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; import java.util.TreeSet; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - /** * Utility class for working with character sets. It is somewhat similar to * the Java 1.4 java.nio.charset.Charset class but knows many @@ -526,7 +523,7 @@ import org.apache.commons.logging.LogFactory; * * GBK * windows-936 - * CP936 MS936 ms_936 x-mswin-936 x-gbk + * CP936 MS936 ms_936 x-mswin-936 * * * ISCII91 @@ -784,14 +781,10 @@ import org.apache.commons.logging.LogFactory; * * * - * - * - * @version $Id: CharsetUtil.java,v 1.1 2004/10/25 07:26:46 ntherning Exp $ */ public class CharsetUtil { - private static Log log = LogFactory.getLog(CharsetUtil.class); - private static class Charset implements Comparable { + private static class Charset implements Comparable { private String canonical = null; private String mime = null; private String[] aliases = null; @@ -802,8 +795,7 @@ public class CharsetUtil { this.aliases = aliases; } - public int compareTo(Object o) { - Charset c = (Charset) o; + public int compareTo(Charset c) { return this.canonical.compareTo(c.canonical); } } @@ -863,7 +855,7 @@ public class CharsetUtil { "euckr"}), new Charset("GB18030", "GB18030", new String[] {"gb18030-2000"}), new Charset("EUC_CN", "GB2312", new String[] {"x-EUC-CN", "csGB2312", "euccn", "euc-cn", "gb2312-80", "gb2312-1980", "CN-GB", "CN-GB-ISOIR165"}), - new Charset("GBK", "windows-936", new String[] {"CP936", "MS936", "ms_936", "x-mswin-936", "x-gbk"}), + new Charset("GBK", "windows-936", new String[] {"CP936", "MS936", "ms_936", "x-mswin-936"}), new Charset("Cp037", "IBM037", new String[] {"ebcdic-cp-us", "ebcdic-cp-ca", "ebcdic-cp-wt", "ebcdic-cp-nl", "csIBM037"}), new Charset("Cp273", "IBM273", new String[] {"csIBM273"}), @@ -1001,65 +993,42 @@ public class CharsetUtil { * Contains the canonical names of character sets which can be used to * decode bytes into Java chars. */ - private static TreeSet decodingSupported = null; + private static SortedSet decodingSupported = new TreeSet(); /** * Contains the canonical names of character sets which can be used to * encode Java chars into bytes. */ - private static TreeSet encodingSupported = null; + private static SortedSet encodingSupported = new TreeSet(); /** * Maps character set names to Charset objects. All possible names of * a charset will be mapped to the Charset. */ - private static HashMap charsetMap = null; + private static Map charsetMap = null; + + /** + * Map tracking which charset encoding/decodings have been + * tested during runtime. + */ + private static Map charsetsTested = new HashMap(); static { - decodingSupported = new TreeSet(); - encodingSupported = new TreeSet(); - byte[] dummy = new byte[] {'d', 'u', 'm', 'm', 'y'}; - for (int i = 0; i < JAVA_CHARSETS.length; i++) { - try { - String s = new String(dummy, JAVA_CHARSETS[i].canonical); - decodingSupported.add(JAVA_CHARSETS[i].canonical.toLowerCase()); - } catch (UnsupportedOperationException e) { - } catch (UnsupportedEncodingException e) { - } - try { - "dummy".getBytes(JAVA_CHARSETS[i].canonical); - encodingSupported.add(JAVA_CHARSETS[i].canonical.toLowerCase()); - } catch (UnsupportedOperationException e) { - } catch (UnsupportedEncodingException e) { - } - } - - charsetMap = new HashMap(); - for (int i = 0; i < JAVA_CHARSETS.length; i++) { - Charset c = JAVA_CHARSETS[i]; + + charsetMap = new HashMap(); + for (Charset c : JAVA_CHARSETS) { charsetMap.put(c.canonical.toLowerCase(), c); if (c.mime != null) { charsetMap.put(c.mime.toLowerCase(), c); } if (c.aliases != null) { - for (int j = 0; j < c.aliases.length; j++) { - charsetMap.put(c.aliases[j].toLowerCase(), c); + for (String str : c.aliases) { + charsetMap.put(str.toLowerCase(), c); } } } - - if (log.isDebugEnabled()) { - log.debug("Character sets which support decoding: " - + decodingSupported); - log.debug("Character sets which support encoding: " - + encodingSupported); - } } - - /** - * ANDROID: THE FOLLOWING SET OF STATIC STRINGS ARE COPIED FROM A NEWER VERSION OF MIME4J - */ - + /** carriage return - line feed sequence */ public static final String CRLF = "\r\n"; @@ -1072,9 +1041,9 @@ public class CharsetUtil { /** US-ASCII SP, space (32) */ public static final int SP = ' '; - /** US-ASCII HT, horizontal-tab (9)*/ + /** US-ASCII HT, horizontal-tab (9) */ public static final int HT = '\t'; - + public static final java.nio.charset.Charset US_ASCII = java.nio.charset.Charset .forName("US-ASCII"); @@ -1084,12 +1053,47 @@ public class CharsetUtil { public static final java.nio.charset.Charset UTF_8 = java.nio.charset.Charset .forName("UTF-8"); + public static final java.nio.charset.Charset DEFAULT_CHARSET = US_ASCII; + + /** + * Returns true if the specified character falls into the US + * ASCII character set (Unicode range 0000 to 007f). + * + * @param ch + * character to test. + * @return true if the specified character falls into the US + * ASCII character set, false otherwise. + */ + public static boolean isASCII(char ch) { + return (0xFF80 & ch) == 0; + } + + /** + * Returns true if the specified string consists entirely of + * US ASCII characters. + * + * @param s + * string to test. + * @return true if the specified string consists entirely of + * US ASCII characters, false otherwise. + */ + public static boolean isASCII(final String s) { + if (s == null) { + throw new IllegalArgumentException("String may not be null"); + } + final int len = s.length(); + for (int i = 0; i < len; i++) { + if (!isASCII(s.charAt(i))) { + return false; + } + } + return true; + } + /** * Returns true if the specified character is a whitespace * character (CR, LF, SP or HT). * - * ANDROID: COPIED FROM A NEWER VERSION OF MIME4J - * * @param ch * character to test. * @return true if the specified character is a whitespace @@ -1103,8 +1107,6 @@ public class CharsetUtil { * Returns true if the specified string consists entirely of * whitespace characters. * - * ANDROID: COPIED FROM A NEWER VERSION OF MIME4J - * * @param s * string to test. * @return true if the specified string consists entirely of @@ -1135,6 +1137,9 @@ public class CharsetUtil { * otherwise. */ public static boolean isEncodingSupported(String charsetName) { + if (!charsetsTested.containsKey(charsetName.toLowerCase())) { + testCharset(charsetName.toLowerCase()); + } return encodingSupported.contains(charsetName.toLowerCase()); } @@ -1150,9 +1155,46 @@ public class CharsetUtil { * otherwise. */ public static boolean isDecodingSupported(String charsetName) { + if (!charsetsTested.containsKey(charsetName.toLowerCase())) { + testCharset(charsetName.toLowerCase()); + } return decodingSupported.contains(charsetName.toLowerCase()); } + + /** + * Runs underlying encoding/decodings tests to determine appropriate + * responses for {@link #isDecodingSupported(String)} and {@link #isEncodingSupported(String)} + * + * @param charsetName the characters set name. + */ + private static void testCharset(String charsetName) { + byte[] dummy = new byte[] {'d', 'u', 'm', 'm', 'y'}; + Charset c = charsetMap.get(charsetName.toLowerCase()); + if (null == c) { + charsetsTested.put(charsetName.toLowerCase(), null); + return; + } + + try { + new String(dummy, c.canonical); + decodingSupported.add(c.canonical.toLowerCase()); + } catch (UnsupportedOperationException e) { + } catch (UnsupportedEncodingException e) { + } + + try { + "dummy".getBytes(c.canonical); + encodingSupported.add(c.canonical.toLowerCase()); + } catch (UnsupportedOperationException e) { + } catch (UnsupportedEncodingException e) { + } + + charsetsTested.put(charsetName.toLowerCase(), null); + } + + + /** * Gets the preferred MIME character set name for the specified * character set or null if not known. @@ -1161,7 +1203,7 @@ public class CharsetUtil { * @return the MIME preferred name or null if not known. */ public static String toMimeCharset(String charsetName) { - Charset c = (Charset) charsetMap.get(charsetName.toLowerCase()); + Charset c = charsetMap.get(charsetName.toLowerCase()); if (c != null) { return c.mime; } @@ -1180,32 +1222,13 @@ public class CharsetUtil { * @return the canonical Java name or null if not known. */ public static String toJavaCharset(String charsetName) { - Charset c = (Charset) charsetMap.get(charsetName.toLowerCase()); + Charset c = charsetMap.get(charsetName.toLowerCase()); if (c != null) { return c.canonical; } return null; } - public static java.nio.charset.Charset getCharset(String charsetName) { - String defaultCharset = "ISO-8859-1"; - - // Use the default chareset if given charset is null - if(charsetName == null) charsetName = defaultCharset; - - try { - return java.nio.charset.Charset.forName(charsetName); - } catch (IllegalCharsetNameException e) { - log.info("Illegal charset " + charsetName + ", fallback to " + defaultCharset + ": " + e); - // Use default charset on exception - return java.nio.charset.Charset.forName(defaultCharset); - } catch (UnsupportedCharsetException ex) { - log.info("Unsupported charset " + charsetName + ", fallback to " + defaultCharset + ": " + ex); - // Use default charset on exception - return java.nio.charset.Charset.forName(defaultCharset); - } - - } /* * Uncomment the code below and run the main method to regenerate the * Javadoc table above when the known charsets change. @@ -1213,9 +1236,9 @@ public class CharsetUtil { /* private static String dumpHtmlTable() { - LinkedList l = new LinkedList(Arrays.asList(JAVA_CHARSETS)); + List l = new LinkedList(Arrays.asList(JAVA_CHARSETS)); Collections.sort(l); - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); sb.append(" * \n"); sb.append(" * \n"); sb.append(" * \n"); @@ -1223,8 +1246,7 @@ public class CharsetUtil { sb.append(" * \n"); sb.append(" * \n"); - for (Iterator it = l.iterator(); it.hasNext();) { - Charset c = (Charset) it.next(); + for (Charset c : l) { sb.append(" * \n"); sb.append(" * \n"); sb.append(" * \n"); @@ -1241,5 +1263,6 @@ public class CharsetUtil { public static void main(String[] args) { System.out.println(dumpHtmlTable()); - }*/ -} + } + */ +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/util/ContentUtil.java b/src/org/apache/james/mime4j/util/ContentUtil.java new file mode 100644 index 000000000..2f43dd629 --- /dev/null +++ b/src/org/apache/james/mime4j/util/ContentUtil.java @@ -0,0 +1,138 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.util; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; + +/** + * Utility methods for converting textual content of a message. + */ +public class ContentUtil { + + private ContentUtil() { + } + + /** + * Encodes the specified string into an immutable sequence of bytes using + * the US-ASCII charset. + * + * @param string + * string to encode. + * @return encoded string as an immutable sequence of bytes. + */ + public static ByteSequence encode(String string) { + return encode(CharsetUtil.US_ASCII, string); + } + + /** + * Encodes the specified string into an immutable sequence of bytes using + * the specified charset. + * + * @param charset + * Java charset to be used for the conversion. + * @param string + * string to encode. + * @return encoded string as an immutable sequence of bytes. + */ + public static ByteSequence encode(Charset charset, String string) { + ByteBuffer encoded = charset.encode(CharBuffer.wrap(string)); + ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining()); + bab.append(encoded.array(), encoded.position(), encoded.remaining()); + return bab; + } + + /** + * Decodes the specified sequence of bytes into a string using the US-ASCII + * charset. + * + * @param byteSequence + * sequence of bytes to decode. + * @return decoded string. + */ + public static String decode(ByteSequence byteSequence) { + return decode(CharsetUtil.US_ASCII, byteSequence, 0, byteSequence + .length()); + } + + /** + * Decodes the specified sequence of bytes into a string using the specified + * charset. + * + * @param charset + * Java charset to be used for the conversion. + * @param byteSequence + * sequence of bytes to decode. + * @return decoded string. + */ + public static String decode(Charset charset, ByteSequence byteSequence) { + return decode(charset, byteSequence, 0, byteSequence.length()); + } + + /** + * Decodes a sub-sequence of the specified sequence of bytes into a string + * using the US-ASCII charset. + * + * @param byteSequence + * sequence of bytes to decode. + * @param offset + * offset into the byte sequence. + * @param length + * number of bytes. + * @return decoded string. + */ + public static String decode(ByteSequence byteSequence, int offset, + int length) { + return decode(CharsetUtil.US_ASCII, byteSequence, offset, length); + } + + /** + * Decodes a sub-sequence of the specified sequence of bytes into a string + * using the specified charset. + * + * @param charset + * Java charset to be used for the conversion. + * @param byteSequence + * sequence of bytes to decode. + * @param offset + * offset into the byte sequence. + * @param length + * number of bytes. + * @return decoded string. + */ + public static String decode(Charset charset, ByteSequence byteSequence, + int offset, int length) { + if (byteSequence instanceof ByteArrayBuffer) { + ByteArrayBuffer bab = (ByteArrayBuffer) byteSequence; + return decode(charset, bab.buffer(), offset, length); + } else { + byte[] bytes = byteSequence.toByteArray(); + return decode(charset, bytes, offset, length); + } + } + + private static String decode(Charset charset, byte[] buffer, int offset, + int length) { + return charset.decode(ByteBuffer.wrap(buffer, offset, length)) + .toString(); + } + +} diff --git a/src/org/apache/james/mime4j/util/EmptyByteSequence.java b/src/org/apache/james/mime4j/util/EmptyByteSequence.java new file mode 100644 index 000000000..01cdd19d1 --- /dev/null +++ b/src/org/apache/james/mime4j/util/EmptyByteSequence.java @@ -0,0 +1,36 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.util; + +final class EmptyByteSequence implements ByteSequence { + private static final byte[] EMPTY_BYTES = {}; + + public int length() { + return 0; + } + + public byte byteAt(int index) { + throw new IndexOutOfBoundsException(); + } + + public byte[] toByteArray() { + return EMPTY_BYTES; + } +} \ No newline at end of file diff --git a/src/org/apache/james/mime4j/util/LangUtils.java b/src/org/apache/james/mime4j/util/LangUtils.java new file mode 100644 index 000000000..aedd47079 --- /dev/null +++ b/src/org/apache/james/mime4j/util/LangUtils.java @@ -0,0 +1,69 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.util; + +/** + * A set of utility methods to help produce consistent + * {@link Object#equals equals} and {@link Object#hashCode hashCode} methods. + */ +public final class LangUtils { + + public static final int HASH_SEED = 17; + public static final int HASH_OFFSET = 37; + + /** Disabled default constructor. */ + private LangUtils() { + } + + public static int hashCode(final int seed, final int hashcode) { + return seed * HASH_OFFSET + hashcode; + } + + public static int hashCode(final int seed, final boolean b) { + return hashCode(seed, b ? 1 : 0); + } + + public static int hashCode(final int seed, final Object obj) { + return hashCode(seed, obj != null ? obj.hashCode() : 0); + } + + /** + * Check if two objects are equal. + * + * @param obj1 first object to compare, may be {@code null} + * @param obj2 second object to compare, may be {@code null} + * @return {@code true} if the objects are equal or both null + */ + public static boolean equals(final Object obj1, final Object obj2) { + return obj1 == null ? obj2 == null : obj1.equals(obj2); + } + + /** + * Check if two strings are equal, ignoring case considerations. + * + * @param s1 first string to compare, may be {@code null} + * @param s2 second string to compare, may be {@code null} + * @return {@code true} if the objects are equal or both null + */ + public static boolean equalsIgnoreCase(final String s1, final String s2) { + return s1 == null ? s2 == null : s1.equalsIgnoreCase(s2); + } + +} diff --git a/src/org/apache/james/mime4j/util/MimeUtil.java b/src/org/apache/james/mime4j/util/MimeUtil.java new file mode 100644 index 000000000..f203bf381 --- /dev/null +++ b/src/org/apache/james/mime4j/util/MimeUtil.java @@ -0,0 +1,365 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.util; + +import java.text.DateFormat; +import java.text.FieldPosition; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.Random; +import java.util.TimeZone; + +/** + * A utility class, which provides some MIME related application logic. + */ +public final class MimeUtil { + + /** + * The quoted-printable encoding. + */ + public static final String ENC_QUOTED_PRINTABLE = "quoted-printable"; + /** + * The binary encoding. + */ + public static final String ENC_BINARY = "binary"; + /** + * The base64 encoding. + */ + public static final String ENC_BASE64 = "base64"; + /** + * The 8bit encoding. + */ + public static final String ENC_8BIT = "8bit"; + /** + * The 7bit encoding. + */ + public static final String ENC_7BIT = "7bit"; + + /** MIME-Version header name (lowercase) */ + public static final String MIME_HEADER_MIME_VERSION = "mime-version"; + /** Content-ID header name (lowercase) */ + public static final String MIME_HEADER_CONTENT_ID = "content-id"; + /** Content-Description header name (lowercase) */ + public static final String MIME_HEADER_CONTENT_DESCRIPTION = "content-description"; + /** + * Content-Disposition header name (lowercase). + * See RFC2183. + */ + public static final String MIME_HEADER_CONTENT_DISPOSITION = "content-disposition"; + /** + * Content-Disposition filename parameter (lowercase). + * See RFC2183. + */ + public static final String PARAM_FILENAME = "filename"; + /** + * Content-Disposition modification-date parameter (lowercase). + * See RFC2183. + */ + public static final String PARAM_MODIFICATION_DATE = "modification-date"; + /** + * Content-Disposition creation-date parameter (lowercase). + * See RFC2183. + */ + public static final String PARAM_CREATION_DATE = "creation-date"; + /** + * Content-Disposition read-date parameter (lowercase). + * See RFC2183. + */ + public static final String PARAM_READ_DATE = "read-date"; + /** + * Content-Disposition size parameter (lowercase). + * See RFC2183. + */ + public static final String PARAM_SIZE = "size"; + /** + * Content-Langauge header (lower case). + * See RFC4646. + */ + public static final String MIME_HEADER_LANGAUGE = "content-language"; + /** + * Content-Location header (lower case). + * See RFC2557. + */ + public static final String MIME_HEADER_LOCATION = "content-location"; + /** + * Content-MD5 header (lower case). + * See RFC1864. + */ + public static final String MIME_HEADER_MD5 = "content-md5"; + + // used to create unique ids + private static final Random random = new Random(); + + // used to create unique ids + private static int counter = 0; + + private MimeUtil() { + // this is an utility class to be used statically. + // this constructor protect from instantiation. + } + + /** + * Returns, whether the given two MIME types are identical. + */ + public static boolean isSameMimeType(String pType1, String pType2) { + return pType1 != null && pType2 != null && pType1.equalsIgnoreCase(pType2); + } + + /** + * Returns true, if the given MIME type is that of a message. + */ + public static boolean isMessage(String pMimeType) { + return pMimeType != null && pMimeType.equalsIgnoreCase("message/rfc822"); + } + + /** + * Return true, if the given MIME type indicates a multipart entity. + */ + public static boolean isMultipart(String pMimeType) { + return pMimeType != null && pMimeType.toLowerCase().startsWith("multipart/"); + } + + /** + * Returns, whether the given transfer-encoding is "base64". + */ + public static boolean isBase64Encoding(String pTransferEncoding) { + return ENC_BASE64.equalsIgnoreCase(pTransferEncoding); + } + + /** + * Returns, whether the given transfer-encoding is "quoted-printable". + */ + public static boolean isQuotedPrintableEncoded(String pTransferEncoding) { + return ENC_QUOTED_PRINTABLE.equalsIgnoreCase(pTransferEncoding); + } + + /** + * Creates a new unique message boundary string that can be used as boundary + * parameter for the Content-Type header field of a message. + * + * @return a new unique message boundary string. + */ + /* TODO - From rfc2045: + * Since the hyphen character ("-") may be represented as itself in the + * Quoted-Printable encoding, care must be taken, when encapsulating a + * quoted-printable encoded body inside one or more multipart entities, + * to ensure that the boundary delimiter does not appear anywhere in the + * encoded body. (A good strategy is to choose a boundary that includes + * a character sequence such as "=_" which can never appear in a + * quoted-printable body. See the definition of multipart messages in + * RFC 2046.) + */ + public static String createUniqueBoundary() { + StringBuilder sb = new StringBuilder(); + sb.append("-=Part."); + sb.append(Integer.toHexString(nextCounterValue())); + sb.append('.'); + sb.append(Long.toHexString(random.nextLong())); + sb.append('.'); + sb.append(Long.toHexString(System.currentTimeMillis())); + sb.append('.'); + sb.append(Long.toHexString(random.nextLong())); + sb.append("=-"); + return sb.toString(); + } + + /** + * Creates a new unique message identifier that can be used in message + * header field such as Message-ID or In-Reply-To. If the given host name is + * not null it will be used as suffix for the message ID + * (following an at sign). + * + * The resulting string is enclosed in angle brackets (< and >); + * + * @param hostName host name to be included in the message ID or + * null if no host name should be included. + * @return a new unique message identifier. + */ + public static String createUniqueMessageId(String hostName) { + StringBuilder sb = new StringBuilder("'); + return sb.toString(); + } + + /** + * Formats the specified date into a RFC 822 date-time string. + * + * @param date + * date to be formatted into a string. + * @param zone + * the time zone to use or null to use the default + * time zone. + * @return the formatted time string. + */ + public static String formatDate(Date date, TimeZone zone) { + DateFormat df = RFC822_DATE_FORMAT.get(); + + if (zone == null) { + df.setTimeZone(TimeZone.getDefault()); + } else { + df.setTimeZone(zone); + } + + return df.format(date); + } + + /** + * Splits the specified string into a multiple-line representation with + * lines no longer than 76 characters (because the line might contain + * encoded words; see RFC + * 2047 section 2). If the string contains non-whitespace sequences + * longer than 76 characters a line break is inserted at the whitespace + * character following the sequence resulting in a line longer than 76 + * characters. + * + * @param s + * string to split. + * @param usedCharacters + * number of characters already used up. Usually the number of + * characters for header field name plus colon and one space. + * @return a multiple-line representation of the given string. + */ + public static String fold(String s, int usedCharacters) { + final int maxCharacters = 76; + + final int length = s.length(); + if (usedCharacters + length <= maxCharacters) + return s; + + StringBuilder sb = new StringBuilder(); + + int lastLineBreak = -usedCharacters; + int wspIdx = indexOfWsp(s, 0); + while (true) { + if (wspIdx == length) { + sb.append(s.substring(Math.max(0, lastLineBreak))); + return sb.toString(); + } + + int nextWspIdx = indexOfWsp(s, wspIdx + 1); + + if (nextWspIdx - lastLineBreak > maxCharacters) { + sb.append(s.substring(Math.max(0, lastLineBreak), wspIdx)); + sb.append("\r\n"); + lastLineBreak = wspIdx; + } + + wspIdx = nextWspIdx; + } + } + + /** + * Unfold a multiple-line representation into a single line. + * + * @param s + * string to unfold. + * @return unfolded string. + */ + public static String unfold(String s) { + final int length = s.length(); + for (int idx = 0; idx < length; idx++) { + char c = s.charAt(idx); + if (c == '\r' || c == '\n') { + return unfold0(s, idx); + } + } + + return s; + } + + private static String unfold0(String s, int crlfIdx) { + final int length = s.length(); + StringBuilder sb = new StringBuilder(length); + + if (crlfIdx > 0) { + sb.append(s.substring(0, crlfIdx)); + } + + for (int idx = crlfIdx + 1; idx < length; idx++) { + char c = s.charAt(idx); + if (c != '\r' && c != '\n') { + sb.append(c); + } + } + + return sb.toString(); + } + + private static int indexOfWsp(String s, int fromIndex) { + final int len = s.length(); + for (int index = fromIndex; index < len; index++) { + char c = s.charAt(index); + if (c == ' ' || c == '\t') + return index; + } + return len; + } + + private static synchronized int nextCounterValue() { + return counter++; + } + + private static final ThreadLocal RFC822_DATE_FORMAT = new ThreadLocal() { + @Override + protected DateFormat initialValue() { + return new Rfc822DateFormat(); + } + }; + + private static final class Rfc822DateFormat extends SimpleDateFormat { + private static final long serialVersionUID = 1L; + + public Rfc822DateFormat() { + super("EEE, d MMM yyyy HH:mm:ss ", Locale.US); + } + + @Override + public StringBuffer format(Date date, StringBuffer toAppendTo, + FieldPosition pos) { + StringBuffer sb = super.format(date, toAppendTo, pos); + + int zoneMillis = calendar.get(GregorianCalendar.ZONE_OFFSET); + int dstMillis = calendar.get(GregorianCalendar.DST_OFFSET); + int minutes = (zoneMillis + dstMillis) / 1000 / 60; + + if (minutes < 0) { + sb.append('-'); + minutes = -minutes; + } else { + sb.append('+'); + } + + sb.append(String.format("%02d%02d", minutes / 60, minutes % 60)); + + return sb; + } + } +} diff --git a/src/org/apache/james/mime4j/util/SimpleTempStorage.java b/src/org/apache/james/mime4j/util/SimpleTempStorage.java deleted file mode 100644 index 51ddd51f7..000000000 --- a/src/org/apache/james/mime4j/util/SimpleTempStorage.java +++ /dev/null @@ -1,236 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.util; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Random; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * - * @version $Id: SimpleTempStorage.java,v 1.2 2004/10/02 12:41:11 ntherning Exp $ - */ -public class SimpleTempStorage extends TempStorage { - private static Log log = LogFactory.getLog(SimpleTempStorage.class); - - private TempPath rootPath = null; - private Random random = new Random(); - - /** - * Creates a new SimpleTempStorageManager instance. - */ - public SimpleTempStorage() { - rootPath = new SimpleTempPath(System.getProperty("java.io.tmpdir")); - } - - private TempPath createTempPath(TempPath parent, String prefix) - throws IOException { - - if (prefix == null) { - prefix = ""; - } - - File p = null; - int count = 1000; - do { - long n = Math.abs(random.nextLong()); - p = new File(parent.getAbsolutePath(), prefix + n); - count--; - } while (p.exists() && count > 0); - - if (p.exists() || !p.mkdirs()) { - log.error("Unable to mkdirs on " + p.getAbsolutePath()); - throw new IOException("Creating dir '" - + p.getAbsolutePath() + "' failed."); - } - - return new SimpleTempPath(p); - } - - private TempFile createTempFile(TempPath parent, String prefix, - String suffix) throws IOException { - - if (prefix == null) { - prefix = ""; - } - if (suffix == null) { - suffix = ".tmp"; - } - - File f = null; - - int count = 1000; - synchronized (this) { - do { - long n = Math.abs(random.nextLong()); - f = new File(parent.getAbsolutePath(), prefix + n + suffix); - count--; - } while (f.exists() && count > 0); - - if (f.exists()) { - throw new IOException("Creating temp file failed: " - + "Unable to find unique file name"); - } - - try { - f.createNewFile(); - } catch (IOException e) { - throw new IOException("Creating dir '" - + f.getAbsolutePath() + "' failed."); - } - } - - return new SimpleTempFile(f); - } - - /** - * @see org.apache.james.mime4j.util.TempStorage#getRootTempPath() - */ - public TempPath getRootTempPath() { - return rootPath; - } - - private class SimpleTempPath implements TempPath { - private File path = null; - - private SimpleTempPath(String path) { - this.path = new File(path); - } - - private SimpleTempPath(File path) { - this.path = path; - } - - /** - * @see org.apache.james.mime4j.util.TempPath#createTempFile() - */ - public TempFile createTempFile() throws IOException { - return SimpleTempStorage.this.createTempFile(this, null, null); - } - - /** - * @see org.apache.james.mime4j.util.TempPath#createTempFile(java.lang.String, java.lang.String) - */ - public TempFile createTempFile(String prefix, String suffix) - throws IOException { - - return SimpleTempStorage.this.createTempFile(this, prefix, suffix); - } - - /** - * @see org.apache.james.mime4j.util.TempPath#createTempFile(java.lang.String, java.lang.String, boolean) - */ - public TempFile createTempFile(String prefix, String suffix, - boolean allowInMemory) - throws IOException { - - return SimpleTempStorage.this.createTempFile(this, prefix, suffix); - } - - /** - * @see org.apache.james.mime4j.util.TempPath#getAbsolutePath() - */ - public String getAbsolutePath() { - return path.getAbsolutePath(); - } - - /** - * Do nothing - */ - public void delete() { - } - - /** - * @see org.apache.james.mime4j.util.TempPath#createTempPath() - */ - public TempPath createTempPath() throws IOException { - return SimpleTempStorage.this.createTempPath(this, null); - } - - /** - * @see org.apache.james.mime4j.util.TempPath#createTempPath(java.lang.String) - */ - public TempPath createTempPath(String prefix) throws IOException { - return SimpleTempStorage.this.createTempPath(this, prefix); - } - - } - - private class SimpleTempFile implements TempFile { - private File file = null; - - private SimpleTempFile(File file) { - this.file = file; - this.file.deleteOnExit(); - } - - /** - * @see org.apache.james.mime4j.util.TempFile#getInputStream() - */ - public InputStream getInputStream() throws IOException { - return new BufferedInputStream(new FileInputStream(file)); - } - - /** - * @see org.apache.james.mime4j.util.TempFile#getOutputStream() - */ - public OutputStream getOutputStream() throws IOException { - return new BufferedOutputStream(new FileOutputStream(file)); - } - - /** - * @see org.apache.james.mime4j.util.TempFile#getAbsolutePath() - */ - public String getAbsolutePath() { - return file.getAbsolutePath(); - } - - /** - * Do nothing - */ - public void delete() { - // Not implementated - } - - /** - * @see org.apache.james.mime4j.util.TempFile#isInMemory() - */ - public boolean isInMemory() { - return false; - } - - /** - * @see org.apache.james.mime4j.util.TempFile#length() - */ - public long length() { - return file.length(); - } - - } -} diff --git a/src/org/apache/james/mime4j/util/TempFile.java b/src/org/apache/james/mime4j/util/TempFile.java deleted file mode 100644 index cc2ea6d98..000000000 --- a/src/org/apache/james/mime4j/util/TempFile.java +++ /dev/null @@ -1,84 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.util; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * @version $Id: TempFile.java,v 1.3 2004/10/02 12:41:11 ntherning Exp $ - */ -public interface TempFile { - /** - * Gets an InputStream to read bytes from this temporary file. - * NOTE: The stream should NOT be wrapped in - * BufferedInputStream by the caller. If the implementing - * TempFile creates a FileInputStream or any - * other stream which would benefit from being buffered it's the - * TempFile's responsibility to wrap it. - * - * @return the stream. - * @throws IOException - */ - InputStream getInputStream() throws IOException; - - /** - * Gets an OutputStream to write bytes to this temporary file. - * NOTE: The stream should NOT be wrapped in - * BufferedOutputStream by the caller. If the implementing - * TempFile creates a FileOutputStream or any - * other stream which would benefit from being buffered it's the - * TempFile's responsibility to wrap it. - * - * @return the stream. - * @throws IOException - */ - OutputStream getOutputStream() throws IOException; - - /** - * Returns the absolute path including file name of this - * TempFile. The path may be null if this is - * an in-memory file. - * - * @return the absolute path. - */ - String getAbsolutePath(); - - /** - * Deletes this file as soon as possible. - */ - void delete(); - - /** - * Determines if this is an in-memory file. - * - * @return true if this file is currently in memory, - * false otherwise. - */ - boolean isInMemory(); - - /** - * Gets the length of this temporary file. - * - * @return the length. - */ - long length(); -} diff --git a/src/org/apache/james/mime4j/util/TempPath.java b/src/org/apache/james/mime4j/util/TempPath.java deleted file mode 100644 index 3c4cf8163..000000000 --- a/src/org/apache/james/mime4j/util/TempPath.java +++ /dev/null @@ -1,73 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.util; - -import java.io.IOException; - -/** - * - * @version $Id: TempPath.java,v 1.2 2004/10/02 12:41:11 ntherning Exp $ - */ -public interface TempPath { - TempPath createTempPath() throws IOException; - TempPath createTempPath(String prefix) throws IOException; - - /** - * Creates a new temporary file. Wheter it will be be created in memory - * or on disk is up to to the implementation. - * The prefix will be empty and the suffix will be - * .tmp if created on disk. - * - * @return the temporary file. - */ - TempFile createTempFile() throws IOException; - - /** - * Creates a new temporary file. Wheter it will be be created in memory - * or on disk is up to to the implementation. - * The prefix and suffix can be set by the user. - * - * @param prefix the prefix to use. null gives no prefix. - * @param suffix the suffix to use. null gives - * .tmp. - * @return the temporary file. - */ - TempFile createTempFile(String prefix, String suffix) throws IOException; - - /** - * Creates a new temporary file. Wheter it will be be created in memory - * or on disk can be specified using the allowInMemory - * parameter. If the implementation doesn't support in-memory files - * the new file will be created on disk. - * The prefix and suffix can be set by the user. - * - * @param prefix the prefix to use. null gives no prefix. - * @param suffix the suffix to use. null gives - * .tmp. - * @param allowInMemory if true the file MIGHT be created in - * memory if supported by the implentation. If false the - * file MUST be created on disk. - * @return the temporary file. - */ - TempFile createTempFile(String prefix, String suffix, - boolean allowInMemory) throws IOException; - String getAbsolutePath(); - void delete(); -} diff --git a/src/org/apache/james/mime4j/util/TempStorage.java b/src/org/apache/james/mime4j/util/TempStorage.java deleted file mode 100644 index a8546a1e5..000000000 --- a/src/org/apache/james/mime4j/util/TempStorage.java +++ /dev/null @@ -1,70 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.mime4j.util; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * - * @version $Id: TempStorage.java,v 1.2 2004/10/02 12:41:11 ntherning Exp $ - */ -public abstract class TempStorage { - private static Log log = LogFactory.getLog(TempStorage.class); - private static TempStorage inst = null; - - static { - - String clazz = System.getProperty("org.apache.james.mime4j.tempStorage"); - try { - - if (inst != null) { - inst = (TempStorage) Class.forName(clazz).newInstance(); - } - - } catch (Throwable t) { - log.warn("Unable to create or instantiate TempStorage class '" - + clazz + "' using SimpleTempStorage instead", t); - } - - if (inst == null) { - inst = new SimpleTempStorage(); - } - } - - /** - * Gets the root temporary path which should be used to - * create new temporary paths or files. - * - * @return the root temporary path. - */ - public abstract TempPath getRootTempPath(); - - public static TempStorage getInstance() { - return inst; - } - - public static void setInstance(TempStorage inst) { - if (inst == null) { - throw new NullPointerException("inst"); - } - TempStorage.inst = inst; - } -}
Canonical (Java) nameAliases
" + c.canonical + "" + (c.mime == null ? "?" : c.mime)+ "