blob: 4d8fce7fc2419d3e9970ad971a24139e9a00d8df [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002-2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM - Initial API and implementation
*******************************************************************************/
package org.eclipse.wst.wsi.internal.core.profile.validator.impl.message;
import java.util.ArrayList;
import java.util.Iterator;
import org.eclipse.wst.wsi.internal.core.WSIException;
import org.eclipse.wst.wsi.internal.core.analyzer.AssertionFailException;
import org.eclipse.wst.wsi.internal.core.log.MimePart;
import org.eclipse.wst.wsi.internal.core.log.MimeParts;
import org.eclipse.wst.wsi.internal.core.profile.TestAssertion;
import org.eclipse.wst.wsi.internal.core.profile.validator.EntryContext;
import org.eclipse.wst.wsi.internal.core.profile.validator.impl.AssertionProcess;
import org.eclipse.wst.wsi.internal.core.profile.validator.impl.BaseMessageValidator;
import org.eclipse.wst.wsi.internal.core.report.AssertionResult;
import org.eclipse.wst.wsi.internal.core.util.HTTPConstants;
import org.eclipse.wst.wsi.internal.core.util.HTTPUtils;
import org.eclipse.wst.wsi.internal.core.util.Utils;
/**
* AP1935
*
* <context>For a candidate part of a multipart/related message</context>
* <assertionDescription>The encoding of the body of a part in a
* multipart/related message conforms to the encoding indicated by the
* Content-Transfer-Encoding field-value,
* as specified by RFC2045.</assertionDescription>
*/
public class AP1935 extends AssertionProcess
{
private final BaseMessageValidator validator;
/**
* @param WSDLValidatorImpl
*/
public AP1935(BaseMessageValidator impl)
{
super(impl);
this.validator = impl;
}
/* Validates the test assertion.
* @see org.wsi.test.profile.validator.impl.BaseValidatorImpl.AssertionProcess#validate(org.wsi.test.profile.TestAssertion, org.wsi.test.profile.validator.EntryContext)
*/
public AssertionResult validate(
TestAssertion testAssertion,
EntryContext entryContext)
throws WSIException
{
if(!entryContext.getMessageEntry().isMimeContent())
{
result = AssertionResult.RESULT_NOT_APPLICABLE;
}
else
{
// get MIME parts
MimeParts parts = entryContext.getMessageEntry().getMimeParts();
if(parts.count() == 0)
{
result = AssertionResult.RESULT_NOT_APPLICABLE;
}
else
{
// check each part for the encoding match
Iterator iparts = parts.getParts().iterator();
int i = 0;
MimePart root = parts.getRootPart();
while (iparts.hasNext())
{
i = i = 1;
try
{
MimePart part = (MimePart)iparts.next();
// get encoding from header
String encoding = HTTPUtils.getHttpHeaderAttribute(part.getHeaders(),
HTTPConstants.HEADER_CONTENT_TRANSFER_ENCODING);
if ((part == root) ||
((encoding != null) && encoding.equalsIgnoreCase("base64")))
checkPart(part, encoding, false);
else
checkPart(part, encoding, true);
} catch (AssertionFailException e)
{
result = AssertionResult.RESULT_FAILED;
failureDetail = validator.createFailureDetail(
"part "+(i+1)+" Error: " + e.getMessage(), entryContext);
}
}
}
}
// Return assertion result
return validator.createAssertionResult(
testAssertion, result, failureDetail);
}
/**
* Check message entry to encoding conformity
* @param entry message entry
* @throws AssertionFailException if message does not encoding conformity
* @throws WSIException
*/
private void checkPart(MimePart part, String encoding, boolean encoded)
throws AssertionFailException, WSIException
{
String content = null;
if (encoded)
content = new String(Utils.decodeBase64(part.getContent()));
else
content = part.getContent();
if(encoding == null)
{
result = AssertionResult.RESULT_NOT_APPLICABLE;
// check 7bit
} else if(encoding.equalsIgnoreCase("7bit")) {
checkOn7bit(content);
// check 8bit
} else if(encoding.equalsIgnoreCase("8bit")) {
checkOn8bit(content);
// check quoted-printable
} else if(encoding.equalsIgnoreCase("quoted-printable")) {
checkOnQuotedPrintable(content);
// check base64
} else if(encoding.equalsIgnoreCase("base64")) {
checkOnBase64(content);
}
// we dont check binary encoding, since message can contains any chars
}
/**
* Validate a 7bit encoded message (RFC2045)
* @param message message to check
* @throws AssertionFailException if message does not conform
*/
private void checkOn7bit(String message)
throws AssertionFailException
{
String[] strs = split(message);
for (int i = 0; i < strs.length; i++)
{
String str = strs[i];
// check string length
if(str.length() > 998)
{
throw new AssertionFailException("The length (" + str.length() +
") of the line (" + (i+1) + ") greater than 998");
}
// No octets with decimal values greater than 127
// are allowed and neither are NULs (octets with decimal value 0). CR
//(decimal value 13) and LF (decimal value 10) octets only occur as
// part of CRLF line separation sequences.
char[] chars = str.toCharArray();
for (int j = 0; j < chars.length; j++)
{
if((chars[j] > 127) || (chars[j] == 0) ||
(chars[j] == 10) || (chars[j] == 13))
{
throw new AssertionFailException("The char (" + chars[j] +
")[code=" + (byte) chars[j] + " position=" + j +
"] does not allows in 7bit encoding content");
}
}
}
}
/**
* Validate an 8bit encoded message (RFC2045)
* @param message message to check
* @throws AssertionFailException if message does not conform
*/
private void checkOn8bit(String message)
throws AssertionFailException
{
String[] strs = split(message);
for (int i = 0; i < strs.length; i++)
{
String str = strs[i];
// check string length
if(str.length() > 998)
{
throw new AssertionFailException("The length (" + str.length() +
") of the line (" + (i+1) + ") greater than 998");
}
// octets with decimal values greater than 127
// may be used. As with "7bit data" CR and LF octets only occur as part
// of CRLF line separation sequences and no NULs are allowed.
char[] chars = str.toCharArray();
for (int j = 0; j < chars.length; j++)
{
if((chars[j] == 0) || (chars[j] == 10) || (chars[j] == 13))
{
throw new AssertionFailException("The char (" + chars[j] +
")[code=" + (byte) chars[j] + " position=" + j +
"] does not allows in 8bit encoding content");
}
}
}
}
/**
* Validate a quoted-printable encoded message (RFC2045)
* @param message message to check
* @throws AssertionFailException if message does not conform
*/
private void checkOnQuotedPrintable(String message)
throws AssertionFailException
{
String[] strs = split(message);
for (int i = 0; i < strs.length; i++)
{
// check length
// RFC2045
// (5) (Soft Line Breaks) The Quoted-Printable encoding
//REQUIRES that encoded lines be no more than 76
//characters long. If longer lines are to be encoded
//with the Quoted-Printable encoding, "soft" line breaks
//must be used. An equal sign as the last character on a
//encoded line indicates such a non-significant ("soft")
//line break in the encoded text.
if(((strs[i].indexOf("\t") != -1) || (strs[i].indexOf(" ") != -1)) &&
(strs[i].length() > 76))
{
throw new AssertionFailException("The length (" + strs[i].length() +
") of the line (" + (i+1) +
") greater than 76, \"soft\" line breaks must be used");
}
char[] chars = strs[i].toCharArray();
for (int j = 0; j < chars.length; j++)
{
//(1) (General 8bit representation) Any octet, except a CR or
//LF that is part of a CRLF line break of the canonical
//(standard) form of the data being encoded, may be
//represented by an "=" followed by a two digit
//hexadecimal representation of the octet's value. The
//digits of the hexadecimal alphabet, for this purpose,
//are "0123456789ABCDEF". Uppercase letters must be
//used; lowercase letters are not allowed. Thus, for
//example, the decimal value 12 (US-ASCII form feed) can
//be represented by "=0C", and the decimal value 61 (US-
//ASCII EQUAL SIGN) can be represented by "=3D". This
//rule must be followed except when the following rules
//allow an alternative encoding.
// (2) (Literal representation) Octets with decimal values of
//33 through 60 inclusive, and 62 through 126, inclusive,
//MAY be represented as the US-ASCII characters which
//correspond to those octets (EXCLAMATION POINT through
//LESS THAN, and GREATER THAN through TILDE,
//respectively).
if((chars[j] == 61) && (chars.length > j+2))
{
if(!isHex(chars[j+1]) || !isHex(chars[j+2]))
{
throw new AssertionFailException("the quoted char (" +
chars[j] + chars[j+1] + chars[j+2] + ") is incorrect");
} else {
j += 2;
}
}
// check for space and tab
else if((chars[j] != 9) && (chars[j] != 32))
{
// check invalid symbol
if((chars[j] == 0) || (chars[j] == 10) || (chars[j] == 13) ||
(chars[j] < 33) || (chars[j] > 126) || (chars[j] == 61))
{
throw new AssertionFailException("The char (" + chars[j] +
")[code=" + (byte) chars[j] + " position=" + j +
"] must be quoted");
}
}
}
}
}
/**
* Validate a base64 encoded message (RFC3548)
* @param message message to check
* @throws AssertionFailException if message does not conform
*/
private void checkOnBase64(String message)
throws AssertionFailException
{
String[] strs = split(message);
for (int i = 0; i < strs.length; i++)
{
String str = strs[i];
// check string length
if(str.length() > 76)
{
throw new AssertionFailException("The length (" + str.length() +
") of the line (" + (i+1) + ") greater than 998");
}
// check for "ABCDEFGHIJKLMNOPQRSTUVWXYZabcefghijklmnopqrstuvwxyz0123456789/+"
char[] chars = str.toCharArray();
for (int j = 0; j < chars.length; j++)
{
char c = chars[i];
if((c < 47) || (c > 122) || ((c > 57) && (c < 65)) ||
((c > 90) && (c < 97)))
{
throw new AssertionFailException("The char (" + chars[j] +
")[code=" + (byte) chars[j] + " position=" + j +
"] does not allows in base64 encoding content");
}
}
}
}
/**
* split string to array of strings and use as delimeter CRLF
* @param str original string
* @return array of strings
*/
private String[] split(String str)
{
ArrayList list = new ArrayList();
for(int idx = str.indexOf("\r\n"); idx != -1; idx = str.indexOf("\r\n"))
{
list.add(str.substring(0, idx));
str = str.substring(idx+2);
}
list.add(str);
return (String[]) list.toArray(new String[list.size()]);
}
/**
* Returns true if byte is "0123456789ABCDEF" range, false othewise
* @param c char
* @return true if byte is "0123456789ABCDEF" range, false othewise
*/
private boolean isHex(char c) {
return (((c >= 48) && (c <= 57)) || ((c >= 65) && (c <= 70)));
}
}