001: /**
002: * Title: Oyster Project
003: * Description: Creating S/MIME email transport capabilities.
004: * Copyright: Copyright (c) 2001
005: * @Author Vladimir Radisic
006: * @Version 2.1.6
007: */package org.enhydra.oyster.mail;
008:
009: import java.io.InputStream;
010: import java.io.FileInputStream;
011: import java.io.BufferedInputStream;
012: import java.io.IOException;
013: import java.io.File;
014: import org.enhydra.oyster.exception.SMIMEException;
015:
016: /**
017: * Analyzes content which will be placed in MimeBodyPart and suggests which
018: * Content-Transfer-Encoding header parameter should be used (7bit,
019: * quoted-printable or base64). ContentAnalyzer makes the following checks, and
020: * according to them decisions about which Content-Transfer-Encoding to suggest:<BR>
021: * <BR>
022: * 7bit: if all checked characters (or bytes) are in group of ASCII characters
023: * which are less than 127 (7F hexadecimal) and higher than 31 (1F hexadecimal).
024: * Characters: HT (Horizontal Tab) 9 (09 hexadecimal), and combinaton of CR
025: * (Carriage Return) 13 (0D hexadecimal) and LF (Line Feed) 11 (0A hexadecimal)
026: * are also allowed. Single usage of CR or LF characters or it's combination
027: * LFCR are not permitted for this type Content-Transfer-Encoding. Also, acording
028: * to RFC822, no line should be longer than 1000 characters with included trailing
029: * CRLF charachters.<BR>
030: * <BR>
031: * quoted-printable: if at least one character does not satisfy previous condition,
032: * and if number of those characters do not exceed 30 percent of all characters.<BR>
033: * <BR>
034: * base64: if number of those characters which do not satisfy 7bit condition
035: * exceed 30 percent of all characters.<BR>
036: */
037: public class ContentAnalyzer {
038:
039: /**
040: * Number of bytes for processed content data.
041: */
042: private int numberOfBytes = 0;
043:
044: /**
045: * Number of bytes which could not be represented as 7bit for processed
046: * content data.
047: */
048: private int numberOfNon7bitBytes = 0;
049:
050: /**
051: * This variable is used for counting line lenght. It shouldn't be higher
052: * than 1000.
053: */
054: private int lineLenght = 0;
055:
056: /**
057: * Indicates that line lenght at least once exceeded 1000 characters.
058: */
059: private boolean errorInLineLenght = false;
060:
061: /**
062: * Indicates that the previous character was CR (Carriage Return).
063: */
064: private boolean CRCharIndcator = false;
065:
066: /**
067: * Simple constructor.
068: */
069: public ContentAnalyzer() {
070:
071: }
072:
073: /**
074: * Method checks one charachter passed as integer, if it passes condition for
075: * implementation of 7bit Content-Transfer-Encoding. For checking content
076: * data which is constructed from more than one byte, this method must be called
077: * in loop for every byte that belongs to examined content data.
078: * @param dataToCheck integer represetation of passed byte for check
079: */
080: public void isCharFor7BitEncoding(int dataToCheck) {
081: lineLenght++;
082: numberOfBytes++;
083: if (lineLenght > 1000)
084: errorInLineLenght = true;
085: if (dataToCheck < 0x7F && dataToCheck > 0x1F) {
086: return;
087: }
088: if (dataToCheck == 0x09)
089: return;
090: if (dataToCheck == 0x0D) {
091: if (CRCharIndcator) {
092: numberOfNon7bitBytes++;
093: return;
094: } else {
095: CRCharIndcator = true;
096: return;
097: }
098: }
099: if (dataToCheck == 0x0A) {
100: if (CRCharIndcator) {
101: CRCharIndcator = false;
102: lineLenght = 0; // CRLF resets line lenght to 0
103: return;
104: } else {
105: numberOfNon7bitBytes++;
106: return;
107: }
108: }
109: numberOfNon7bitBytes++;
110: }
111:
112: /**
113: * Method checks data passed as InputStream which will be used later in the
114: * process of creation of content in MimeBodyPart, if it passes condition for
115: * implementation of 7bit Content-Transfer-Encoding.
116: * @param dataToCheck content data which has to be checked represented as
117: * InputStream
118: * @exception SMIMEException caused by non SMIMEException which is IOException
119: */
120: public void isFor7BitEncoding(InputStream dataToCheck)
121: throws SMIMEException {
122: BufferedInputStream buf = new BufferedInputStream(dataToCheck,
123: 1000);
124: try {
125: int available = buf.available();
126: int markPoint = 0;
127: while (markPoint < available) {
128: buf.mark(markPoint);
129: buf.reset();
130: int byteVal = buf.read();
131: while (byteVal != -1) {
132: this .isCharFor7BitEncoding(byteVal);
133: byteVal = buf.read();
134: }
135: markPoint += 1000;
136: }
137: buf.close();
138: } catch (IOException e) {
139: throw new SMIMEException(e);
140: }
141: }
142:
143: /**
144: * Method checks data passed from File which will be used later in the process
145: * of creation of content in MimeBodyPart, if it passes condition for
146: * implementation of 7bit Content-Transfer-Encoding.
147: * @param fileToCheck pointer to file which data has to be checked.
148: * @exception SMIMEException caused by non SMIMEException which is IOException
149: */
150: public void isFor7BitEncoding(File fileToCheck)
151: throws SMIMEException {
152: try {
153: FileInputStream fi = new FileInputStream(fileToCheck);
154: this .isFor7BitEncoding(fi);
155: } catch (IOException e) {
156: throw new SMIMEException(e);
157: }
158: }
159:
160: /**
161: * Performs analyze of content data and accoding to that decides which
162: * Content-Transfer-Encoding to suggesst.
163: */
164: public void analyse() {
165:
166: }
167:
168: /**
169: * Returns suggested encoding, after finished analyze.
170: * @return the name of Content-Transfer-Encoding header parameter which can
171: * take values: 7bit, quoted-printable, base64 or null in case of error.
172: */
173: public String getPreferedEncoding() {
174: if (numberOfBytes == 0)
175: return "7bit";
176: if (numberOfNon7bitBytes == 0 && !errorInLineLenght)
177: return "7bit";
178: if (numberOfNon7bitBytes == 0 && errorInLineLenght)
179: return "quoted-printable";
180: if (numberOfNon7bitBytes != 0) {
181: int percentage = 100 * numberOfNon7bitBytes / numberOfBytes;
182: if (percentage > 30)
183: return "base64";
184: else
185: return "quoted-printable";
186: }
187: return null;
188: }
189:
190: /**
191: * Resets all storaged values in ContentAnalyzer object to default values and
192: * prepares object for new use.
193: */
194: public void reset() {
195: numberOfBytes = 0;
196: numberOfNon7bitBytes = 0;
197: lineLenght = 0;
198: errorInLineLenght = false;
199: CRCharIndcator = false;
200: }
201: }
|