You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by jl...@apache.org on 2022/05/03 12:22:12 UTC
svn commit: r1900504 [15/22] - in /geronimo/specs/trunk: ./ geronimo-activation_2.0_spec/ geronimo-activation_2.0_spec/src/ geronimo-activation_2.0_spec/src/main/ geronimo-activation_2.0_spec/src/main/java/ geronimo-activation_2.0_spec/src/main/java/ja...
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/jakarta/mail/util/SharedFileInputStream.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/jakarta/mail/util/SharedFileInputStream.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/jakarta/mail/util/SharedFileInputStream.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/jakarta/mail/util/SharedFileInputStream.java Tue May 3 12:22:08 2022
@@ -0,0 +1,596 @@
+/*
+ * 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 jakarta.mail.util;
+
+import jakarta.mail.internet.SharedInputStream;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+
+public class SharedFileInputStream extends BufferedInputStream implements SharedInputStream {
+
+
+ // This initial size isn't documented, but bufsize is 2048 after initialization for the
+ // Sun implementation.
+ private static final int DEFAULT_BUFFER_SIZE = 2048;
+
+ // the shared file information, used to synchronize opens/closes of the base file.
+ private SharedFileSource source;
+
+ /**
+ * The file offset that is the first byte in the read buffer.
+ */
+ protected long bufpos;
+
+ /**
+ * The normal size of the read buffer.
+ */
+ protected int bufsize;
+
+ /**
+ * The size of the file subset represented by this stream instance.
+ */
+ protected long datalen;
+
+ /**
+ * The source of the file data. This is shared across multiple
+ * instances.
+ */
+ protected RandomAccessFile in;
+
+ /**
+ * The starting position of data represented by this stream relative
+ * to the start of the file data. This stream instance represents
+ * data in the range start to (start + datalen - 1).
+ */
+ protected long start;
+
+
+ /**
+ * Construct a SharedFileInputStream from a file name, using the default buffer size.
+ *
+ * @param file The name of the file.
+ *
+ * @exception IOException
+ */
+ public SharedFileInputStream(final String file) throws IOException {
+ this(file, DEFAULT_BUFFER_SIZE);
+ }
+
+
+ /**
+ * Construct a SharedFileInputStream from a File object, using the default buffer size.
+ *
+ * @param file The name of the file.
+ *
+ * @exception IOException
+ */
+ public SharedFileInputStream(final File file) throws IOException {
+ this(file, DEFAULT_BUFFER_SIZE);
+ }
+
+
+ /**
+ * Construct a SharedFileInputStream from a file name, with a given initial buffer size.
+ *
+ * @param file The name of the file.
+ * @param bufferSize The initial buffer size.
+ *
+ * @exception IOException
+ */
+ public SharedFileInputStream(final String file, final int bufferSize) throws IOException {
+ // I'm not sure this is correct or not. The SharedFileInputStream spec requires this
+ // be a subclass of BufferedInputStream. The BufferedInputStream constructor takes a stream,
+ // which we're not really working from at this point. Using null seems to work so far.
+ super(null);
+ init(new File(file), bufferSize);
+ }
+
+
+ /**
+ * Construct a SharedFileInputStream from a File object, with a given initial buffer size.
+ *
+ * @param file The name of the file.
+ * @param bufferSize The initial buffer size.
+ *
+ * @exception IOException
+ */
+ public SharedFileInputStream(final File file, final int bufferSize) throws IOException {
+ // I'm not sure this is correct or not. The SharedFileInputStream spec requires this
+ // be a subclass of BufferedInputStream. The BufferedInputStream constructor takes a stream,
+ // which we're not really working from at this point. Using null seems to work so far.
+ super(null);
+ init(file, bufferSize);
+ }
+
+
+ /**
+ * Private constructor used to spawn off a shared instance
+ * of this stream.
+ *
+ * @param source The internal class object that manages the shared resources of
+ * the stream.
+ * @param start The starting offset relative to the beginning of the file.
+ * @param len The length of file data in this shared instance.
+ * @param bufsize The initial buffer size (same as the spawning parent.
+ */
+ private SharedFileInputStream(final SharedFileSource source, final long start, final long len, final int bufsize) {
+ super(null);
+ this.source = source;
+ in = source.open();
+ this.start = start;
+ bufpos = start;
+ datalen = len;
+ this.bufsize = bufsize;
+ buf = new byte[bufsize];
+ // other fields such as pos and count initialized by the super class constructor.
+ }
+
+
+ /**
+ * Shared initializtion routine for the constructors.
+ *
+ * @param file The file we're accessing.
+ * @param bufferSize The initial buffer size to use.
+ *
+ * @exception IOException
+ */
+ private void init(final File file, final int bufferSize) throws IOException {
+ if (bufferSize <= 0) {
+ throw new IllegalArgumentException("Buffer size must be positive");
+ }
+ // create a random access file for accessing the data, then create an object that's used to share
+ // instances of the same stream.
+ source = new SharedFileSource(file);
+ // we're opening the first one.
+ in = source.open();
+ // this represents the entire file, for now.
+ start = 0;
+ // use the current file length for the bounds
+ datalen = in.length();
+ // now create our buffer version
+ bufsize = bufferSize;
+ bufpos = 0;
+ // NB: this is using the super class protected variable.
+ buf = new byte[bufferSize];
+ }
+
+
+ /**
+ * Check to see if we need to read more data into our buffer.
+ *
+ * @return False if there's not valid data in the buffer (generally means
+ * an EOF condition).
+ * @exception IOException
+ */
+ private boolean checkFill() throws IOException {
+ // if we have data in the buffer currently, just return
+ if (pos < count) {
+ return true;
+ }
+
+ // ugh, extending BufferedInputStream also means supporting mark positions. That complicates everything.
+ // life is so much easier if marks are not used....
+ if (markpos < 0) {
+ // reset back to the buffer position
+ pos = 0;
+ // this will be the new position within the file once we're read some data.
+ bufpos += count;
+ }
+ else {
+ // we have marks to worry about....damn.
+ // if we have room in the buffer to read more data, then we will. Otherwise, we need to see
+ // if it's possible to shift the data in the buffer or extend the buffer (up to the mark limit).
+ if (pos >= buf.length) {
+ // the mark position is not at the beginning of the buffer, so just shuffle the bytes, leaving
+ // us room to read more data.
+ if (markpos > 0) {
+ // this is the size of the data we need to keep.
+ final int validSize = pos - markpos;
+ // perform the shift operation.
+ System.arraycopy(buf, markpos, buf, 0, validSize);
+ // now adjust the positional markers for this shift.
+ pos = validSize;
+ bufpos += markpos;
+ markpos = 0;
+ }
+ // the mark is at the beginning, and we've used up the buffer. See if we're allowed to
+ // extend this.
+ else if (buf.length < marklimit) {
+ // try to double this, but throttle to the mark limit
+ final int newSize = Math.min(buf.length * 2, marklimit);
+
+ final byte[] newBuffer = new byte[newSize];
+ System.arraycopy(buf, 0, newBuffer, 0, buf.length);
+
+ // replace the old buffer. Note that all other positional markers remain the same here.
+ buf = newBuffer;
+ }
+ // we've got further than allowed, so invalidate the mark, and just reset the buffer
+ else {
+ markpos = -1;
+ pos = 0;
+ bufpos += count;
+ }
+ }
+ }
+
+ // if we're past our designated end, force an eof.
+ if (bufpos + pos >= start + datalen) {
+ // make sure we zero the count out, otherwise we'll reuse this data
+ // if called again.
+ count = pos;
+ return false;
+ }
+
+ // seek to the read location start. Note this is a shared file, so this assumes all of the methods
+ // doing buffer fills will be synchronized.
+ int fillLength = buf.length - pos;
+
+ // we might be working with a subset of the file data, so normal eof processing might not apply.
+ // we need to limit how much we read to the data length.
+ if (bufpos - start + pos + fillLength > datalen) {
+ fillLength = (int)(datalen - (bufpos - start + pos));
+ }
+
+ // finally, try to read more data into the buffer.
+ fillLength = source.read(bufpos + pos, buf, pos, fillLength);
+
+ // we weren't able to read anything, count this as an eof failure.
+ if (fillLength <= 0) {
+ // make sure we zero the count out, otherwise we'll reuse this data
+ // if called again.
+ count = pos;
+ return false;
+ }
+
+ // set the new buffer count
+ count = fillLength + pos;
+
+ // we have data in the buffer.
+ return true;
+ }
+
+
+ /**
+ * Return the number of bytes available for reading without
+ * blocking for a long period.
+ *
+ * @return For this stream, this is the number of bytes between the
+ * current read position and the indicated end of the file.
+ * @exception IOException
+ */
+ @Override
+ public synchronized int available() throws IOException {
+ checkOpen();
+
+ // this is backed by a file, which doesn't really block. We can return all the way to the
+ // marked data end, if necessary
+ final long endMarker = start + datalen;
+ return (int)(endMarker - (bufpos + pos));
+ }
+
+
+ /**
+ * Return the current read position of the stream.
+ *
+ * @return The current position relative to the beginning of the stream.
+ * This is not the position relative to the start of the file, since
+ * the stream starting position may be other than the beginning.
+ */
+ public long getPosition() {
+ checkOpenRuntime();
+
+ return bufpos + pos - start;
+ }
+
+
+ /**
+ * Mark the current position for retracing.
+ *
+ * @param readlimit The limit for the distance the read position can move from
+ * the mark position before the mark is reset.
+ */
+ @Override
+ public synchronized void mark(final int readlimit) {
+ checkOpenRuntime();
+ marklimit = readlimit;
+ markpos = pos;
+ }
+
+
+ /**
+ * Read a single byte of data from the input stream.
+ *
+ * @return The read byte. Returns -1 if an eof condition has been hit.
+ * @exception IOException
+ */
+ @Override
+ public synchronized int read() throws IOException {
+ checkOpen();
+
+ // check to see if we can fill more data
+ if (!checkFill()) {
+ return -1;
+ }
+
+ // return the current byte...anded to prevent sign extension.
+ return buf[pos++] & 0xff;
+ }
+
+
+ /**
+ * Read multiple bytes of data and place them directly into
+ * a byte-array buffer.
+ *
+ * @param buffer The target buffer.
+ * @param offset The offset within the buffer to place the data.
+ * @param length The length to attempt to read.
+ *
+ * @return The number of bytes actually read. Returns -1 for an EOF
+ * condition.
+ * @exception IOException
+ */
+ @Override
+ public synchronized int read(final byte buffer[], int offset, int length) throws IOException {
+ checkOpen();
+
+ // asked to read nothing? That's what we'll do.
+ if (length == 0) {
+ return 0;
+ }
+
+
+ int returnCount = 0;
+ while (length > 0) {
+ // check to see if we can/must fill more data
+ if (!checkFill()) {
+ // we've hit the end, but if we've read data, then return that.
+ if (returnCount > 0) {
+ return returnCount;
+ }
+ // trun eof.
+ return -1;
+ }
+
+ final int available = count - pos;
+ final int given = Math.min(available, length);
+
+ System.arraycopy(buf, pos, buffer, offset, given);
+
+ // now adjust all of our positions and counters
+ pos += given;
+ length -= given;
+ returnCount += given;
+ offset += given;
+ }
+ // return the accumulated count.
+ return returnCount;
+ }
+
+
+ /**
+ * Skip the read pointer ahead a given number of bytes.
+ *
+ * @param n The number of bytes to skip.
+ *
+ * @return The number of bytes actually skipped.
+ * @exception IOException
+ */
+ @Override
+ public synchronized long skip(final long n) throws IOException {
+ checkOpen();
+
+ // nothing to skip, so don't skip
+ if (n <= 0) {
+ return 0;
+ }
+
+ // see if we need to fill more data, and potentially shift the mark positions
+ if (!checkFill()) {
+ return 0;
+ }
+
+ final long available = count - pos;
+
+ // the skipped contract allows skipping within the current buffer bounds, so cap it there.
+ final long skipped = available < n ? available : n;
+ pos += skipped;
+ return skipped;
+ }
+
+ /**
+ * Reset the mark position.
+ *
+ * @exception IOException
+ */
+ @Override
+ public synchronized void reset() throws IOException {
+ checkOpen();
+ if (markpos < 0) {
+ throw new IOException("Resetting to invalid mark position");
+ }
+ // if we have a markpos, it will still be in the buffer bounds.
+ pos = markpos;
+ }
+
+
+ /**
+ * Indicates the mark() operation is supported.
+ *
+ * @return Always returns true.
+ */
+ @Override
+ public boolean markSupported() {
+ return true;
+ }
+
+
+ /**
+ * Close the stream. This does not close the source file until
+ * the last shared instance is closed.
+ *
+ * @exception IOException
+ */
+ @Override
+ public void close() throws IOException {
+ // already closed? This is not an error
+ if (in == null) {
+ return;
+ }
+
+ try {
+ // perform a close on the source version.
+ source.close();
+ } finally {
+ in = null;
+ }
+ }
+
+
+ /**
+ * Create a new stream from this stream, using the given
+ * start offset and length.
+ *
+ * @param offset The offset relative to the start of this stream instance.
+ * @param end The end offset of the substream. If -1, the end of the parent stream is used.
+ *
+ * @return A new SharedFileInputStream object sharing the same source
+ * input file.
+ */
+ public InputStream newStream(final long offset, long end) {
+ checkOpenRuntime();
+
+ if (offset < 0) {
+ throw new IllegalArgumentException("Start position is less than 0");
+ }
+ // the default end position is the datalen of the one we're spawning from.
+ if (end == -1) {
+ end = datalen;
+ }
+
+ // create a new one using the private constructor
+ return new SharedFileInputStream(source, start + (int)offset, (int)(end - offset), bufsize);
+ }
+
+
+ /**
+ * Check if the file is open and throw an IOException if not.
+ *
+ * @exception IOException
+ */
+ private void checkOpen() throws IOException {
+ if (in == null) {
+ throw new IOException("Stream has been closed");
+ }
+ }
+
+
+ /**
+ * Check if the file is open and throw an IOException if not. This version is
+ * used because several API methods are not defined as throwing IOException, so
+ * checkOpen() can't be used. The Sun implementation just throws RuntimeExceptions
+ * in those methods, hence 2 versions.
+ *
+ * @exception RuntimeException
+ */
+ private void checkOpenRuntime() {
+ if (in == null) {
+ throw new RuntimeException("Stream has been closed");
+ }
+ }
+
+
+ /**
+ * Internal class used to manage resources shared between the
+ * ShareFileInputStream instances.
+ */
+ class SharedFileSource {
+ // the file source
+ public RandomAccessFile source;
+ // the shared instance count for this file (open instances)
+ public int instanceCount = 0;
+
+ public SharedFileSource(final File file) throws IOException {
+ source = new RandomAccessFile(file, "r");
+ }
+
+ /**
+ * Open the shared stream to keep track of open instances.
+ */
+ public synchronized RandomAccessFile open() {
+ instanceCount++;
+ return source;
+ }
+
+ /**
+ * Process a close request for this stream. If there are multiple
+ * instances using this underlying stream, the stream will not
+ * be closed.
+ *
+ * @exception IOException
+ */
+ public synchronized void close() throws IOException {
+ if (instanceCount > 0) {
+ instanceCount--;
+ // if the last open instance, close the real source file.
+ if (instanceCount == 0) {
+ source.close();
+ }
+ }
+ }
+
+ /**
+ * Read a buffer of data from the shared file.
+ *
+ * @param position The position to read from.
+ * @param buf The target buffer for storing the read data.
+ * @param offset The starting offset within the buffer.
+ * @param length The length to attempt to read.
+ *
+ * @return The number of bytes actually read.
+ * @exception IOException
+ */
+ public synchronized int read(final long position, final byte[] buf, final int offset, final int length) throws IOException {
+ // seek to the read location start. Note this is a shared file, so this assumes all of the methods
+ // doing buffer fills will be synchronized.
+ source.seek(position);
+ return source.read(buf, offset, length);
+ }
+
+
+ /**
+ * Ensure the stream is closed when this shared object is finalized.
+ *
+ * @exception Throwable
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ if (instanceCount > 0) {
+ source.close();
+ }
+ }
+ }
+}
+
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/Activator.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/Activator.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/Activator.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/Activator.java Tue May 3 12:22:08 2022
@@ -0,0 +1,101 @@
+/**
+ * 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.geronimo.mail;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+/**
+ * The activator that starts and manages the tracking of
+ * JAF activation command maps
+ */
+public class Activator extends org.apache.geronimo.osgi.locator.Activator {
+ // tracker to watch for bundle updates
+ protected BundleTracker bt;
+ // service tracker for a logging service
+ protected ServiceTracker lst;
+ // an array of all active logging services.
+ protected List<LogService> logServices = new ArrayList<LogService>();
+
+ @Override
+ public synchronized void start(final BundleContext context) throws Exception {
+ super.start(context);
+ lst = new LogServiceTracker(context, LogService.class.getName(), null);
+ lst.open();
+ bt = new BundleTracker(context, Bundle.ACTIVE, new MailProviderBundleTrackerCustomizer(this, context.getBundle()));
+ bt.open();
+ }
+
+ @Override
+ public synchronized void stop(final BundleContext context) throws Exception {
+ bt.close();
+ lst.close();
+ super.stop(context);
+ }
+
+ void log(final int level, final String message) {
+ synchronized (logServices) {
+ for (final LogService log : logServices) {
+ log.log(level, message);
+ }
+ }
+ }
+
+ void log(final int level, final String message, final Throwable th) {
+ synchronized (logServices) {
+ for (final LogService log : logServices) {
+ log.log(level, message, th);
+ }
+ }
+ }
+
+ private final class LogServiceTracker extends ServiceTracker {
+ private LogServiceTracker(final BundleContext context, final String clazz,
+ final ServiceTrackerCustomizer customizer) {
+ super(context, clazz, customizer);
+ }
+
+ @Override
+ public Object addingService(final ServiceReference reference) {
+ final Object svc = super.addingService(reference);
+ if (svc instanceof LogService) {
+ synchronized (logServices) {
+ logServices.add((LogService) svc);
+ }
+ }
+ return svc;
+ }
+
+ @Override
+ public void removedService(final ServiceReference reference, final Object service) {
+ synchronized (logServices) {
+ logServices.remove(service);
+ }
+ super.removedService(reference, service);
+ }
+ }
+}
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/MailProviderBundleTrackerCustomizer.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/MailProviderBundleTrackerCustomizer.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/MailProviderBundleTrackerCustomizer.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/MailProviderBundleTrackerCustomizer.java Tue May 3 12:22:08 2022
@@ -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.geronimo.mail;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.util.tracker.BundleTrackerCustomizer;
+
+public class MailProviderBundleTrackerCustomizer implements BundleTrackerCustomizer {
+ // our base Activator (used as a service source)
+ private final Activator activator;
+ // the bundle hosting the activation code
+ private final Bundle activationBundle;
+
+ public MailProviderBundleTrackerCustomizer(final Activator a, final Bundle b) {
+ activator = a;
+ activationBundle = b;
+ }
+
+ /**
+ * Handle the activation of a new bundle.
+ *
+ * @param bundle The source bundle.
+ * @param event The bundle event information.
+ *
+ * @return A return object.
+ */
+ public Object addingBundle(final Bundle bundle, final BundleEvent event) {
+ if (bundle.equals(activationBundle)) {
+ return null;
+ }
+
+ return MailProviderRegistry.registerBundle(bundle);
+ }
+
+
+ public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Object object) {
+ // this will update for the new bundle
+ MailProviderRegistry.registerBundle(bundle);
+ }
+
+ public void removedBundle(final Bundle bundle, final BundleEvent event, final Object object) {
+ MailProviderRegistry.unregisterBundle(bundle);
+ }
+
+ private void log(final int level, final String message) {
+ activator.log(level, message);
+ }
+
+ private void log(final int level, final String message, final Throwable th) {
+ activator.log(level, message, th);
+ }
+}
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/MailProviderRegistry.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/MailProviderRegistry.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/MailProviderRegistry.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/MailProviderRegistry.java Tue May 3 12:22:08 2022
@@ -0,0 +1,102 @@
+/**
+ * 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.geronimo.mail;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * The activator that starts and manages the tracking of
+ * JAF activation command maps
+ */
+public class MailProviderRegistry {
+ // a list of all active mail provider config files
+ static ConcurrentMap<Long, URL> providers = new ConcurrentHashMap<Long, URL>();
+ // a list of all active default provider config files
+ static ConcurrentMap<Long, URL> defaultProviders = new ConcurrentHashMap<Long, URL>();
+
+ /**
+ * Perform the check for an existing mailcap file when
+ * a bundle is registered.
+ *
+ * @param bundle The potential provider bundle.
+ *
+ * @return A URL object if this bundle contains a mailcap file.
+ */
+ static Object registerBundle(final Bundle bundle) {
+ // potential tracker return result
+ Object result = null;
+ // a given bundle might have a javamail.providers definition and/or a
+ // default providers definition.
+ URL url = bundle.getResource("META-INF/javamail.providers");
+ if (url != null) {
+ providers.put(bundle.getBundleId(), url);
+ // this indicates our interest
+ result = url;
+ }
+
+ url = bundle.getResource("META-INF/javamail.default.providers");
+ if (url != null) {
+ defaultProviders.put(bundle.getBundleId(), url);
+ // this indicates our interest
+ result = url;
+ }
+ // the url marks our interest in additional activity for this
+ // bundle.
+ return result;
+ }
+
+
+ /**
+ * Remove a bundle from our potential mailcap pool.
+ *
+ * @param bundle The potential source bundle.
+ */
+ static void unregisterBundle(final Bundle bundle) {
+ // remove these items
+ providers.remove(bundle.getBundleId());
+ defaultProviders.remove(bundle.getBundleId());
+ }
+
+ /**
+ * Retrieve any located provider definitions
+ * from bundles.
+ *
+ * @return A collection of the provider definition file
+ * URLs.
+ */
+ public static Collection<URL> getProviders() {
+ return providers.values();
+ }
+
+ /**
+ * Retrieve any located default provider definitions
+ * from bundles.
+ *
+ * @return A collection of the default provider definition file
+ * URLs.
+ */
+ public static Collection<URL> getDefaultProviders() {
+ return defaultProviders.values();
+ }
+}
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/HtmlHandler.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/HtmlHandler.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/HtmlHandler.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/HtmlHandler.java Tue May 3 12:22:08 2022
@@ -0,0 +1,29 @@
+/*
+ * 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.geronimo.mail.handlers;
+
+import jakarta.activation.ActivationDataFlavor;
+
+public class HtmlHandler extends TextHandler {
+ public HtmlHandler() {
+ super(new ActivationDataFlavor(java.lang.String.class, "text/html", "HTML String"));
+ }
+}
+
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/MessageHandler.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/MessageHandler.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/MessageHandler.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/MessageHandler.java Tue May 3 12:22:08 2022
@@ -0,0 +1,123 @@
+/*
+ * 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.geronimo.mail.handlers;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import jakarta.activation.ActivationDataFlavor;
+import jakarta.activation.DataContentHandler;
+import jakarta.activation.DataSource;
+import jakarta.mail.Message;
+import jakarta.mail.MessageAware;
+import jakarta.mail.MessageContext;
+import jakarta.mail.MessagingException;
+import jakarta.mail.internet.MimeMessage;
+
+public class MessageHandler implements DataContentHandler {
+ /**
+ * Field dataFlavor
+ */
+ ActivationDataFlavor dataFlavor;
+
+ public MessageHandler(){
+ dataFlavor = new ActivationDataFlavor(java.lang.String.class, "message/rfc822", "Text");
+ }
+
+
+ /**
+ * Method getDF
+ *
+ * @return dataflavor
+ */
+ protected ActivationDataFlavor getDF() {
+ return dataFlavor;
+ }
+
+ /**
+ * Method getTransferDataFlavors
+ *
+ * @return dataflavors
+ */
+ public ActivationDataFlavor[] getTransferDataFlavors() {
+ return (new ActivationDataFlavor[]{dataFlavor});
+ }
+
+ /**
+ * Method getTransferData
+ *
+ * @param dataflavor
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ public Object getTransferData(final ActivationDataFlavor dataflavor, final DataSource datasource)
+ throws IOException {
+ if (getDF().equals(dataflavor)) {
+ return getContent(datasource);
+ }
+ return null;
+ }
+
+ /**
+ * Method getContent
+ *
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ public Object getContent(final DataSource datasource) throws IOException {
+
+ try {
+ // if this is a proper message, it implements the MessageAware interface. We need this to
+ // get the associated session.
+ if (datasource instanceof MessageAware) {
+ final MessageContext context = ((MessageAware)datasource).getMessageContext();
+ // construct a mime message instance from the stream, associating it with the
+ // data source session.
+ return new MimeMessage(context.getSession(), datasource.getInputStream());
+ }
+ } catch (final MessagingException e) {
+ // we need to transform any exceptions into an IOException.
+ throw new IOException("Exception writing MimeMultipart: " + e.toString());
+ }
+ return null;
+ }
+
+ /**
+ * Method writeTo
+ *
+ * @param object
+ * @param s
+ * @param outputstream
+ * @throws IOException
+ */
+ public void writeTo(final Object object, final String s, final OutputStream outputstream) throws IOException {
+ // proper message type?
+ if (object instanceof Message) {
+ try {
+ ((Message)object).writeTo(outputstream);
+ } catch (final MessagingException e) {
+ throw new IOException("Error parsing message: " + e.toString());
+ }
+ }
+ }
+}
+
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/MultipartHandler.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/MultipartHandler.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/MultipartHandler.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/MultipartHandler.java Tue May 3 12:22:08 2022
@@ -0,0 +1,120 @@
+/*
+ * 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.geronimo.mail.handlers;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import jakarta.activation.ActivationDataFlavor;
+import jakarta.activation.DataContentHandler;
+import jakarta.activation.DataSource;
+import jakarta.mail.MessagingException;
+import jakarta.mail.internet.MimeMultipart;
+
+public class MultipartHandler implements DataContentHandler {
+ /**
+ * Field dataFlavor
+ */
+ ActivationDataFlavor dataFlavor;
+
+ public MultipartHandler(){
+ dataFlavor = new ActivationDataFlavor(MimeMultipart.class, "multipart/mixed", "Multipart");
+ }
+
+ /**
+ * Constructor TextHandler
+ *
+ * @param dataFlavor
+ */
+ public MultipartHandler(final ActivationDataFlavor dataFlavor) {
+ this.dataFlavor = dataFlavor;
+ }
+
+ /**
+ * Method getDF
+ *
+ * @return dataflavor
+ */
+ protected ActivationDataFlavor getDF() {
+ return dataFlavor;
+ }
+
+ /**
+ * Method getTransferDataFlavors
+ *
+ * @return dataflavors
+ */
+ public ActivationDataFlavor[] getTransferDataFlavors() {
+ return (new ActivationDataFlavor[]{dataFlavor});
+ }
+
+ /**
+ * Method getTransferData
+ *
+ * @param dataflavor
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ public Object getTransferData(final ActivationDataFlavor dataflavor, final DataSource datasource)
+ throws IOException {
+ if (getDF().equals(dataflavor)) {
+ return getContent(datasource);
+ }
+ return null;
+ }
+
+ /**
+ * Method getContent
+ *
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ public Object getContent(final DataSource datasource) throws IOException {
+ try {
+ return new MimeMultipart(datasource);
+ } catch (final MessagingException e) {
+ // if there is a syntax error from the datasource parsing, the content is
+ // just null.
+ return null;
+ }
+ }
+
+ /**
+ * Method writeTo
+ *
+ * @param object
+ * @param s
+ * @param outputstream
+ * @throws IOException
+ */
+ public void writeTo(final Object object, final String s, final OutputStream outputstream) throws IOException {
+ // if this object is a MimeMultipart, then delegate to the part.
+ if (object instanceof MimeMultipart) {
+ try {
+ ((MimeMultipart)object).writeTo(outputstream);
+ } catch (final MessagingException e) {
+ // we need to transform any exceptions into an IOException.
+ throw new IOException("Exception writing MimeMultipart: " + e.toString());
+ }
+ }
+ }
+}
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/TextHandler.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/TextHandler.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/TextHandler.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/TextHandler.java Tue May 3 12:22:08 2022
@@ -0,0 +1,160 @@
+/*
+ * 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.geronimo.mail.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+
+import jakarta.activation.ActivationDataFlavor;
+import jakarta.activation.DataContentHandler;
+import jakarta.activation.DataSource;
+import jakarta.mail.internet.ContentType;
+import jakarta.mail.internet.MimeUtility;
+import jakarta.mail.internet.ParseException;
+
+public class TextHandler implements DataContentHandler {
+ /**
+ * Field dataFlavor
+ */
+ ActivationDataFlavor dataFlavor;
+
+ public TextHandler(){
+ dataFlavor = new ActivationDataFlavor(java.lang.String.class, "text/plain", "Text String");
+ }
+
+ /**
+ * Constructor TextHandler
+ *
+ * @param dataFlavor
+ */
+ public TextHandler(final ActivationDataFlavor dataFlavor) {
+ this.dataFlavor = dataFlavor;
+ }
+
+ /**
+ * Method getDF
+ *
+ * @return dataflavor
+ */
+ protected ActivationDataFlavor getDF() {
+ return dataFlavor;
+ }
+
+ /**
+ * Method getTransferDataFlavors
+ *
+ * @return dataflavors
+ */
+ public ActivationDataFlavor[] getTransferDataFlavors() {
+ return (new ActivationDataFlavor[]{dataFlavor});
+ }
+
+ /**
+ * Method getTransferData
+ *
+ * @param dataflavor
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ @Override
+ public Object getTransferData(final ActivationDataFlavor dataflavor, final DataSource datasource) throws IOException {
+ if (getDF().equals(dataflavor)) {
+ return getContent(datasource);
+ }
+ return null;
+ }
+
+ /**
+ * Method getContent
+ *
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ public Object getContent(final DataSource datasource) throws IOException {
+ final InputStream is = datasource.getInputStream();
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ int count;
+ final byte[] buffer = new byte[1000];
+
+ try {
+ while ((count = is.read(buffer, 0, buffer.length)) > 0) {
+ os.write(buffer, 0, count);
+ }
+ } finally {
+ is.close();
+ }
+ try {
+ return os.toString(getCharSet(datasource.getContentType()));
+ } catch (final ParseException e) {
+ throw new UnsupportedEncodingException(e.getMessage());
+ }
+ }
+
+
+ /**
+ * Write an object of "our" type out to the provided
+ * output stream. The content type might modify the
+ * result based on the content type parameters.
+ *
+ * @param object The object to write.
+ * @param contentType
+ * The content mime type, including parameters.
+ * @param outputstream
+ * The target output stream.
+ *
+ * @throws IOException
+ */
+ public void writeTo(final Object object, final String contentType, final OutputStream outputstream)
+ throws IOException {
+ OutputStreamWriter os;
+ try {
+ final String charset = getCharSet(contentType);
+ os = new OutputStreamWriter(outputstream, charset);
+ } catch (final Exception ex) {
+ throw new UnsupportedEncodingException(ex.toString());
+ }
+ final String content = (String) object;
+ os.write(content, 0, content.length());
+ os.flush();
+ }
+
+ /**
+ * get the character set from content type
+ * @param contentType
+ * @return
+ * @throws ParseException
+ */
+ protected String getCharSet(final String contentType) throws ParseException {
+ final ContentType type = new ContentType(contentType);
+ String charset = type.getParameter("charset");
+ if (charset == null) {
+ charset = "us-ascii";
+ }
+ return MimeUtility.javaCharset(charset);
+ }
+}
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/XMLHandler.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/XMLHandler.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/XMLHandler.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/handlers/XMLHandler.java Tue May 3 12:22:08 2022
@@ -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.geronimo.mail.handlers;
+
+import jakarta.activation.ActivationDataFlavor;
+
+public class XMLHandler extends TextHandler {
+ public XMLHandler() {
+ super(new ActivationDataFlavor(java.lang.String.class, "text/xml", "XML String"));
+ }
+}
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java Tue May 3 12:22:08 2022
@@ -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.geronimo.mail.util;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Set of utility classes for handling common encoding-related
+ * manipulations.
+ */
+public class ASCIIUtil {
+
+ /**
+ * Test to see if this string contains only US-ASCII (i.e., 7-bit
+ * ASCII) charactes.
+ *
+ * @param s The test string.
+ *
+ * @return true if this is a valid 7-bit ASCII encoding, false if it
+ * contains any non-US ASCII characters.
+ */
+ static public boolean isAscii(final String s) {
+ for (int i = 0; i < s.length(); i++) {
+ if (!isAscii(s.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Test to see if a given character can be considered "valid" ASCII.
+ * The excluded characters are the control characters less than
+ * 32, 8-bit characters greater than 127, EXCEPT the CR, LF and
+ * tab characters ARE considered value (all less than 32).
+ *
+ * @param ch The test character.
+ *
+ * @return true if this character meets the "ascii-ness" criteria, false
+ * otherwise.
+ */
+ static public boolean isAscii(final int ch) {
+ // these are explicitly considered valid.
+ if (ch == '\r' || ch == '\n' || ch == '\t') {
+ return true;
+ }
+
+ // anything else outside the range is just plain wrong.
+ if (ch >= 127 || ch < 32) {
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Examine a stream of text and make a judgement on what encoding
+ * type should be used for the text. Ideally, we want to use 7bit
+ * encoding to determine this, but we may need to use either quoted-printable
+ * or base64. The choice is made on the ratio of 7-bit characters to non-7bit.
+ *
+ * @param content An input stream for the content we're examining.
+ *
+ * @exception IOException
+ */
+ public static String getTextTransferEncoding(final InputStream content) throws IOException {
+
+ // for efficiency, we'll read in blocks.
+ final BufferedInputStream in = new BufferedInputStream(content, 4096);
+
+ int span = 0; // span of characters without a line break.
+ boolean containsLongLines = false;
+ int asciiChars = 0;
+ int nonAsciiChars = 0;
+
+ while (true) {
+ final int ch = in.read();
+ // if we hit an EOF here, go decide what type we've actually found.
+ if (ch == -1) {
+ break;
+ }
+
+ // we found a linebreak. Reset the line length counters on either one. We don't
+ // really need to validate here.
+ if (ch == '\n' || ch == '\r') {
+ // hit a line end, reset our line length counter
+ span = 0;
+ }
+ else {
+ span++;
+ // the text has long lines, we can't transfer this as unencoded text.
+ if (span > 998) {
+ containsLongLines = true;
+ }
+
+ // non-ascii character, we have to transfer this in binary.
+ if (!isAscii(ch)) {
+ nonAsciiChars++;
+ }
+ else {
+ asciiChars++;
+ }
+ }
+ }
+
+ // looking good so far, only valid chars here.
+ if (nonAsciiChars == 0) {
+ // does this contain long text lines? We need to use a Q-P encoding which will
+ // be only slightly longer, but handles folding the longer lines.
+ if (containsLongLines) {
+ return "quoted-printable";
+ }
+ else {
+ // ideal! Easiest one to handle.
+ return "7bit";
+ }
+ }
+ else {
+ // mostly characters requiring encoding? Base64 is our best bet.
+ if (nonAsciiChars > asciiChars) {
+ return "base64";
+ }
+ else {
+ // Q-P encoding will use fewer bytes than the full Base64.
+ return "quoted-printable";
+ }
+ }
+ }
+
+
+ /**
+ * Examine a stream of text and make a judgement on what encoding
+ * type should be used for the text. Ideally, we want to use 7bit
+ * encoding to determine this, but we may need to use either quoted-printable
+ * or base64. The choice is made on the ratio of 7-bit characters to non-7bit.
+ *
+ * @param content A string for the content we're examining.
+ */
+ public static String getTextTransferEncoding(final String content) {
+
+ int asciiChars = 0;
+ int nonAsciiChars = 0;
+
+ for (int i = 0; i < content.length(); i++) {
+ final int ch = content.charAt(i);
+
+ // non-ascii character, we have to transfer this in binary.
+ if (!isAscii(ch)) {
+ nonAsciiChars++;
+ }
+ else {
+ asciiChars++;
+ }
+ }
+
+ // looking good so far, only valid chars here.
+ if (nonAsciiChars == 0) {
+ // ideal! Easiest one to handle.
+ return "7bit";
+ }
+ else {
+ // mostly characters requiring encoding? Base64 is our best bet.
+ if (nonAsciiChars > asciiChars) {
+ return "base64";
+ }
+ else {
+ // Q-P encoding will use fewer bytes than the full Base64.
+ return "quoted-printable";
+ }
+ }
+ }
+
+
+ /**
+ * Determine if the transfer encoding looks like it might be
+ * valid ascii text, and thus transferable as 7bit code. In
+ * order for this to be true, all characters must be valid
+ * 7-bit ASCII code AND all line breaks must be properly formed
+ * (JUST '\r\n' sequences). 7-bit transfers also
+ * typically have a line limit of 1000 bytes (998 + the CRLF), so any
+ * stretch of charactes longer than that will also force Base64 encoding.
+ *
+ * @param content An input stream for the content we're examining.
+ *
+ * @exception IOException
+ */
+ public static String getBinaryTransferEncoding(final InputStream content) throws IOException {
+
+ // for efficiency, we'll read in blocks.
+ final BufferedInputStream in = new BufferedInputStream(content, 4096);
+
+ int previousChar = 0;
+ int span = 0; // span of characters without a line break.
+
+ while (true) {
+ final int ch = in.read();
+ // if we hit an EOF here, we've only found valid text so far, so we can transfer this as
+ // 7-bit ascii.
+ if (ch == -1) {
+ return "7bit";
+ }
+
+ // we found a newline, this is only valid if the previous char was the '\r'
+ if (ch == '\n') {
+ // malformed linebreak? force this to base64 encoding.
+ if (previousChar != '\r') {
+ return "base64";
+ }
+ // hit a line end, reset our line length counter
+ span = 0;
+ }
+ else {
+ span++;
+ // the text has long lines, we can't transfer this as unencoded text.
+ if (span > 998) {
+ return "base64";
+ }
+
+ // non-ascii character, we have to transfer this in binary.
+ if (!isAscii(ch)) {
+ return "base64";
+ }
+ }
+ previousChar = ch;
+ }
+ }
+}
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/Base64.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/Base64.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/Base64.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/Base64.java Tue May 3 12:22:08 2022
@@ -0,0 +1,189 @@
+/*
+ * 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.geronimo.mail.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class Base64
+{
+ private static final Encoder encoder = new Base64Encoder();
+
+ /**
+ * encode the input data producing a base 64 encoded byte array.
+ *
+ * @return a byte array containing the base 64 encoded data.
+ */
+ public static byte[] encode(
+ final byte[] data)
+ {
+ // just forward to the general array encoder.
+ return encode(data, 0, data.length);
+ }
+
+ /**
+ * encode the input data producing a base 64 encoded byte array.
+ *
+ * @param data The data array to encode.
+ * @param offset The starting offset within the data array.
+ * @param length The length of the data to encode.
+ *
+ * @return a byte array containing the base 64 encoded data.
+ */
+ public static byte[] encode(
+ final byte[] data,
+ final int offset,
+ final int length)
+ {
+ final ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.encode(data, 0, data.length, bOut);
+ }
+ catch (final IOException e)
+ {
+ throw new RuntimeException("exception encoding base64 string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * Encode the byte data to base 64 writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ final byte[] data,
+ final OutputStream out)
+ throws IOException
+ {
+ return encoder.encode(data, 0, data.length, out);
+ }
+
+ /**
+ * Encode the byte data to base 64 writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ final byte[] data,
+ final int off,
+ final int length,
+ final OutputStream out)
+ throws IOException
+ {
+ return encoder.encode(data, off, length, out);
+ }
+
+ /**
+ * decode the base 64 encoded input data. It is assumed the input data is valid.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ final byte[] data)
+ {
+ // just decode the entire array of data.
+ return decode(data, 0, data.length);
+ }
+
+
+ /**
+ * decode the base 64 encoded input data. It is assumed the input data is valid.
+ *
+ * @param data The data array to decode.
+ * @param offset The offset of the data array.
+ * @param length The length of data to decode.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ final byte[] data,
+ final int offset,
+ final int length)
+ {
+ final ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.decode(data, offset, length, bOut);
+ }
+ catch (final IOException e)
+ {
+ throw new RuntimeException("exception decoding base64 string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the base 64 encoded String data - whitespace will be ignored.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ final String data)
+ {
+ final ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.decode(data, bOut);
+ }
+ catch (final IOException e)
+ {
+ throw new RuntimeException("exception decoding base64 string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the base 64 encoded String data writing it to the given output stream,
+ * whitespace characters will be ignored.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int decode(
+ final String data,
+ final OutputStream out)
+ throws IOException
+ {
+ return encoder.decode(data, out);
+ }
+
+ /**
+ * decode the base 64 encoded String data writing it to the given output stream,
+ * whitespace characters will be ignored.
+ *
+ * @param data The array data to decode.
+ * @param out The output stream for the data.
+ *
+ * @return the number of bytes produced.
+ * @exception IOException
+ */
+ public static int decode(final byte [] data, final OutputStream out) throws IOException
+ {
+ return encoder.decode(data, 0, data.length, out);
+ }
+}
Added: geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java?rev=1900504&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java (added)
+++ geronimo/specs/trunk/geronimo-jakartamail_2.1_spec/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java Tue May 3 12:22:08 2022
@@ -0,0 +1,215 @@
+/*
+ * 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.geronimo.mail.util;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An implementation of a FilterInputStream that decodes the
+ * stream data in BASE64 encoding format. This version does the
+ * decoding "on the fly" rather than decoding a single block of
+ * data. Since this version is intended for use by the MimeUtilty class,
+ * it also handles line breaks in the encoded data.
+ */
+public class Base64DecoderStream extends FilterInputStream {
+
+ static protected final String MAIL_BASE64_IGNOREERRORS = "mail.mime.base64.ignoreerrors";
+
+ // number of decodeable units we'll try to process at one time. We'll attempt to read that much
+ // data from the input stream and decode in blocks.
+ static protected final int BUFFERED_UNITS = 2000;
+
+ // our decoder for processing the data
+ protected Base64Encoder decoder = new Base64Encoder();
+
+ // can be overridden by a system property.
+ protected boolean ignoreErrors = false;
+
+ // buffer for reading in chars for decoding (which can support larger bulk reads)
+ protected byte[] encodedChars = new byte[BUFFERED_UNITS * 4];
+ // a buffer for one decoding unit's worth of data (3 bytes). This is the minimum amount we
+ // can read at one time.
+ protected byte[] decodedChars = new byte[BUFFERED_UNITS * 3];
+ // count of characters in the buffer
+ protected int decodedCount = 0;
+ // index of the next decoded character
+ protected int decodedIndex = 0;
+
+
+ public Base64DecoderStream(final InputStream in) {
+ super(in);
+ // make sure we get the ignore errors flag
+ ignoreErrors = SessionUtil.getBooleanProperty(MAIL_BASE64_IGNOREERRORS, false);
+ }
+
+ /**
+ * Test for the existance of decoded characters in our buffer
+ * of decoded data.
+ *
+ * @return True if we currently have buffered characters.
+ */
+ private boolean dataAvailable() {
+ return decodedCount != 0;
+ }
+
+ /**
+ * Get the next buffered decoded character.
+ *
+ * @return The next decoded character in the buffer.
+ */
+ private byte getBufferedChar() {
+ decodedCount--;
+ return decodedChars[decodedIndex++];
+ }
+
+ /**
+ * Decode a requested number of bytes of data into a buffer.
+ *
+ * @return true if we were able to obtain more data, false otherwise.
+ */
+ private boolean decodeStreamData() throws IOException {
+ decodedIndex = 0;
+
+ // fill up a data buffer with input data
+ final int readCharacters = fillEncodedBuffer();
+
+ if (readCharacters > 0) {
+ decodedCount = decoder.decode(encodedChars, 0, readCharacters, decodedChars);
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Retrieve a single byte from the decoded characters buffer.
+ *
+ * @return The decoded character or -1 if there was an EOF condition.
+ */
+ private int getByte() throws IOException {
+ if (!dataAvailable()) {
+ if (!decodeStreamData()) {
+ return -1;
+ }
+ }
+ decodedCount--;
+ // we need to ensure this doesn't get sign extended
+ return decodedChars[decodedIndex++] & 0xff;
+ }
+
+ private int getBytes(final byte[] data, int offset, int length) throws IOException {
+
+ int readCharacters = 0;
+ while (length > 0) {
+ // need data? Try to get some
+ if (!dataAvailable()) {
+ // if we can't get this, return a count of how much we did get (which may be -1).
+ if (!decodeStreamData()) {
+ return readCharacters > 0 ? readCharacters : -1;
+ }
+ }
+
+ // now copy some of the data from the decoded buffer to the target buffer
+ final int copyCount = Math.min(decodedCount, length);
+ System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount);
+ decodedIndex += copyCount;
+ decodedCount -= copyCount;
+ offset += copyCount;
+ length -= copyCount;
+ readCharacters += copyCount;
+ }
+ return readCharacters;
+ }
+
+
+ /**
+ * Fill our buffer of input characters for decoding from the
+ * stream. This will attempt read a full buffer, but will
+ * terminate on an EOF or read error. This will filter out
+ * non-Base64 encoding chars and will only return a valid
+ * multiple of 4 number of bytes.
+ *
+ * @return The count of characters read.
+ */
+ private int fillEncodedBuffer() throws IOException
+ {
+ int readCharacters = 0;
+
+ while (true) {
+ // get the next character from the stream
+ final int ch = in.read();
+ // did we hit an EOF condition?
+ if (ch == -1) {
+ // now check to see if this is normal, or potentially an error
+ // if we didn't get characters as a multiple of 4, we may need to complain about this.
+ if ((readCharacters % 4) != 0) {
+ // the error checking can be turned off...normally it isn't
+ if (!ignoreErrors) {
+ throw new IOException("Base64 encoding error, data truncated");
+ }
+ // we're ignoring errors, so round down to a multiple and return that.
+ return (readCharacters / 4) * 4;
+ }
+ // return the count.
+ return readCharacters;
+ }
+ // if this character is valid in a Base64 stream, copy it to the buffer.
+ else if (decoder.isValidBase64(ch)) {
+ encodedChars[readCharacters++] = (byte)ch;
+ // if we've filled up the buffer, time to quit.
+ if (readCharacters >= encodedChars.length) {
+ return readCharacters;
+ }
+ }
+
+ // we're filtering out whitespace and CRLF characters, so just ignore these
+ }
+ }
+
+
+ // in order to function as a filter, these streams need to override the different
+ // read() signature.
+
+ @Override
+ public int read() throws IOException
+ {
+ return getByte();
+ }
+
+
+ @Override
+ public int read(final byte [] buffer, final int offset, final int length) throws IOException {
+ return getBytes(buffer, offset, length);
+ }
+
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+
+ @Override
+ public int available() throws IOException {
+ return ((in.available() / 4) * 3) + decodedCount;
+ }
+}