View Javadoc

    1   /*
    2    *  soapUI, copyright (C) 2004-2007 eviware.com
    3    *
    4    *  soapUI is free software; you can redistribute it and/or modify it under the
    5    *  terms of version 2.1 of the GNU Lesser General Public License as published by
    6    *  the Free Software Foundation.
    7    *
    8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
    9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    10   *  See the GNU Lesser General Public License for more details at gnu.org.
    11   */
    12
    13  package com.eviware.soapui.impl.wsdl.submit.transports.http;
    14
    15  import java.io.File;
    16  import java.io.FileInputStream;
    17  import java.io.IOException;
    18  import java.io.InputStream;
    19  import java.net.URI;
    20  import java.util.ArrayList;
    21  import java.util.HashMap;
    22  import java.util.Iterator;
    23  import java.util.List;
    24  import java.util.Map;
    25  import java.util.Properties;
    26
    27  import javax.activation.DataHandler;
    28  import javax.mail.MessagingException;
    29  import javax.mail.Session;
    30  import javax.mail.internet.MimeBodyPart;
    31  import javax.mail.internet.MimeMultipart;
    32  import javax.mail.internet.PreencodedMimeBodyPart;
    33  import javax.wsdl.Input;
    34  import javax.wsdl.Output;
    35  import javax.xml.namespace.QName;
    36
    37  import org.apache.commons.codec.binary.Base64;
    38  import org.apache.commons.codec.binary.Hex;
    39  import org.apache.log4j.Logger;
    40  import org.apache.xmlbeans.SchemaType;
    41  import org.apache.xmlbeans.XmlBase64Binary;
    42  import org.apache.xmlbeans.XmlCursor;
    43  import org.apache.xmlbeans.XmlHexBinary;
    44  import org.apache.xmlbeans.XmlObject;
    45
    46  import com.eviware.soapui.config.PartsConfig;
    47  import com.eviware.soapui.config.PartsConfig.Part;
    48  import com.eviware.soapui.impl.wsdl.AttachmentContainer;
    49  import com.eviware.soapui.impl.wsdl.WsdlAttachmentPart;
    50  import com.eviware.soapui.impl.wsdl.WsdlInterface;
    51  import com.eviware.soapui.impl.wsdl.WsdlOperation;
    52  import com.eviware.soapui.impl.wsdl.support.MessageXmlPart;
    53  import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
    54  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlContext;
    55  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlValidator;
    56  import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils;
    57  import com.eviware.soapui.model.iface.Attachment;
    58  import com.eviware.soapui.model.iface.Attachment.AttachmentEncoding;
    59  import com.eviware.soapui.support.Tools;
    60  import com.eviware.soapui.support.types.StringToStringMap;
    61
    62  /***
    63   * Attachment-related utility classes
    64   *
    65   * @author ole.matzura
    66   */
    67
    68  public class AttachmentUtils
    69  {
    70  	private final static Logger log = Logger.getLogger( AttachmentUtils.class );
    71  	private static final QName XMLMIME_CONTENTTYPE_200505 = new QName( "http://www.w3.org/2005/05/xmlmime", "contentType");
    72  	private static final QName XMLMIME_CONTENTTYPE_200411 = new QName( "http://www.w3.org/2004/11/xmlmime", "contentType");
    73  	private static final QName SWAREF_QNAME = new QName("http://ws-i.org/profiles/basic/1.1/xsd","swaRef");
    74  	public static final QName XOP_HREF_QNAME = new QName( "href" );
    75  	private static final QName XOP_INCLUDE_QNAME = new QName("http://www.w3.org/2004/08/xop/include", "Include" );
    76  	public static final String ROOTPART_SOAPUI_ORG = "<rootpart@soapui.org>";
    77
    78  	public static boolean prepareMessagePart(AttachmentContainer container, MimeMultipart mp, MessageXmlPart messagePart, StringToStringMap contentIds) throws Exception, MessagingException
    79  	{
    80  		boolean isXop = false;
    81
    82  		XmlCursor cursor = messagePart.newCursor();
    83
    84  		try
    85  		{
    86  			while( !cursor.isEnddoc() )
    87  			{
    88  				if( cursor.isContainer() )
    89  				{
    90  					// could be an attachment part (as of "old" SwA specs which specify a content
    91  					// element referring to the attachment)
    92  					if( messagePart.isAttachmentPart() )
    93  					{
    94  						String href = cursor.getAttributeText( XOP_HREF_QNAME);
    95  						if( href != null && href.length() > 0 )
    96  						{
    97  							contentIds.put( messagePart.getPart().getName(), href );
    98  						}
    99
    100 						break;
    101 					}
    102
    103 					SchemaType schemaType = cursor.getObject().schemaType();
    104
    105 					if( AttachmentUtils.isSwaRefType( schemaType ))
    106 					{
    107 						String textContent = cursor.getTextValue();
    108 						if( textContent.startsWith( "cid:" ))
    109 						{
    110 							textContent = textContent.substring( 4 );
    111
    112 							try
    113 							{
    114 					   		// is the textcontent already a URI?
    115 								new URI( textContent );
    116 								contentIds.put( textContent, textContent );
    117 							}
    118 							catch( RuntimeException e )
    119 							{
    120 								// not a URI.. try to create one..
    121 								String contentId = textContent + "@soapui.org";
    122 								cursor.setTextValue( "cid:" + contentId );
    123 								contentIds.put( textContent, contentId );
    124 							}
    125 						}
    126 					}
    127 					else if( AttachmentUtils.isXopInclude( schemaType ))
    128 					{
    129 						String contentId = cursor.getAttributeText( new QName("href"));
    130 						if( contentId != null && contentId.length() > 0 )
    131 						{
    132 							contentIds.put( contentId, contentId );
    133 							isXop = true;
    134
    135 							Attachment[] attachments = container.getAttachmentsForPart( contentId );
    136 						   if( attachments.length == 1 )
    137 						   {
    138 						   	XmlCursor cur = cursor.newCursor();
    139 						   	if( cur.toParent())
    140 						   	{
    141 						   		String contentType = getXmlMimeContentType( cur );
    142 						   		if( contentType != null && contentType.length() > 0 )
    143 						   			attachments[0].setContentType( contentType );
    144 						   	}
    145
    146 						   	cur.dispose();
    147 						   }
    148 						}
    149 					}
    150 					else if( SchemaUtils.isBinaryType(schemaType) )
    151 					{
    152 						String xmimeContentType = getXmlMimeContentType( cursor );
    153
    154 						// extract contentId
    155 						String textContent = cursor.getTextValue();
    156 						Attachment attachment = null;
    157 						boolean isXopAttachment = false;
    158
    159 						// is content a reference to a file?
    160 						if( textContent.startsWith( "file:" ))
    161 						{
    162 							String filename = textContent.substring( 5 );
    163 							if( xmimeContentType == null )
    164 							{
    165 								inlineData(cursor, schemaType, new FileInputStream( filename));
    166 							}
    167 							else if( container.isMtomEnabled() )
    168 							{
    169 								MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
    170
    171 								part.setDataHandler( new DataHandler( new XOPPartDataSource( new File(filename ), xmimeContentType, schemaType ) ));
    172 								part.setContentID( "<" + filename + ">" );
    173 								mp.addBodyPart( part );
    174
    175 								isXopAttachment = true;
    176 							}
    177 						}
    178 						// is content a reference to an attachment?
    179 						else if( textContent.startsWith( "cid:" ))
    180 						{
    181 						   textContent = textContent.substring( 4 );
    182
    183 						   Attachment[] attachments = container.getAttachmentsForPart( textContent );
    184 						   if( attachments.length == 1 )
    185 						   {
    186 						   	attachment = attachments[0];
    187 						   }
    188 						   else if( attachments.length > 1 )
    189 						   {
    190 						   	attachment = buildMulitpartAttachment( attachments );
    191 						   }
    192
    193 						   isXopAttachment = xmimeContentType != null;
    194 					   	contentIds.put( textContent, textContent );
    195 						}
    196 						// content should be binary data; is this an XOP element which should be serialized with MTOM?
    197 						else if( container.isMtomEnabled() && xmimeContentType != null )
    198 						{
    199 							MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
    200
    201 							part.setDataHandler( new DataHandler( new XOPPartDataSource( textContent, xmimeContentType, schemaType ) ));
    202
    203 							textContent = "http://www.soapui.org/" + System.nanoTime();
    204
    205 							part.setContentID( "<" + textContent + ">" );
    206 							mp.addBodyPart( part );
    207
    208 							isXopAttachment = true;
    209 						}
    210
    211 						// add XOP include?
    212 						if( isXopAttachment && container.isMtomEnabled() )
    213 						{
    214 							buildXopInclude(cursor, textContent);
    215 							isXop = true;
    216 						}
    217 						// inline?
    218 						else if( attachment != null )
    219 						{
    220 							inlineAttachment(cursor, schemaType, attachment);
    221 						}
    222 					}
    223 				}
    224
    225 				cursor.toNextToken();
    226 			}
    227 		}
    228 		finally
    229 		{
    230 			cursor.dispose();
    231 		}
    232
    233 		return isXop;
    234 	}
    235
    236 	private static void inlineAttachment(XmlCursor cursor, SchemaType schemaType, Attachment attachment) throws Exception
    237 	{
    238 		inlineData( cursor, schemaType, attachment.getInputStream() );
    239 	}
    240
    241 	private static void inlineData(XmlCursor cursor, SchemaType schemaType, InputStream in ) throws IOException
    242 	{
    243 		String content = null;
    244 		byte [] data = Tools.readAll( in, -1 ).toByteArray();
    245
    246 		if (SchemaUtils.isInstanceOf( schemaType, XmlHexBinary.type ))
    247 		{
    248 			content = new String( Hex.encodeHex( data ));
    249 		}
    250 		else if (SchemaUtils.isInstanceOf( schemaType, XmlBase64Binary.type ))
    251 		{
    252 			content = new String( Base64.encodeBase64( data ));
    253 		}
    254
    255 		XmlCursor c = cursor.newCursor();
    256 		c.setTextValue( content );
    257 		c.dispose();
    258 	}
    259
    260 	private static void buildXopInclude(XmlCursor cursor, String contentId)
    261 	{
    262 		// build xop:Include
    263 		XmlCursor c = cursor.newCursor();
    264 		c.removeXmlContents();
    265 		c.toFirstContentToken();
    266 		c.beginElement( XOP_INCLUDE_QNAME );
    267 		c.insertAttributeWithValue( XOP_HREF_QNAME, "cid:" + contentId );
    268 		c.toNextSibling();
    269 		c.removeXml();
    270 		c.dispose();
    271 	}
    272
    273 	private static Attachment buildMulitpartAttachment(Attachment[] attachments)
    274 	{
    275 		System.out.println( "buildMulitpartAttachment(Attachment[] attachments) not implemented!" );
    276 		return null;
    277 	}
    278
    279 	public static String buildRootPartContentType( String action, SoapVersion soapVersion )
    280 	{
    281 		return "application/xop+xml; charset=UTF-8; type=\"" + soapVersion.getContentType() + "; action=//\"" +
    282 			action + "//\"\"";
    283 	}
    284
    285 	public static String buildMTOMContentType(String header, String action, SoapVersion soapVersion )
    286 	{
    287 		if( action == null )
    288 			action = "";
    289
    290 		int ix = header.indexOf( "boundary" );
    291 		return "multipart/related; type=\"application/xop+xml\"; start=\"" + ROOTPART_SOAPUI_ORG + "\"; " +
    292 		    "start-info=\"" + soapVersion.getContentType() + "; action=//\"" + action  + "//\"\"; " + header.substring( ix );
    293 	}
    294
    295 	public static boolean isSwaRefType(SchemaType schemaType)
    296 	{
    297 		return schemaType.getName() != null && schemaType.getName().equals( SWAREF_QNAME);
    298 	}
    299
    300 	public static String getXmlMimeContentType(XmlCursor cursor)
    301 	{
    302 		String attributeText = cursor.getAttributeText( XMLMIME_CONTENTTYPE_200411);
    303 		if( attributeText == null )
    304 			attributeText = cursor.getAttributeText( XMLMIME_CONTENTTYPE_200505);
    305 		return attributeText;
    306 	}
    307
    308 	public static AttachmentEncoding getAttachmentEncoding( WsdlOperation operation, String partName, boolean isResponse )
    309 	{
    310 		// make sure we have access
    311 		if( operation == null || operation.getBindingOperation() == null ||
    312 			 operation.getBindingOperation().getOperation() == null )
    313 		{
    314 			return AttachmentEncoding.NONE;
    315 		}
    316
    317 		javax.wsdl.Part part = null;
    318
    319 		if( isResponse )
    320 		{
    321 			Output output = operation.getBindingOperation().getOperation().getOutput();
    322 			if( output == null || output.getMessage() == null )
    323 			{
    324 				return AttachmentEncoding.NONE;
    325 			}
    326 			else
    327 			{
    328 				part = output.getMessage().getPart( partName );
    329 			}
    330 		}
    331 		else
    332 		{
    333 			Input input = operation.getBindingOperation().getOperation().getInput();
    334 			if( input == null || input.getMessage() == null )
    335 			{
    336 				return AttachmentEncoding.NONE;
    337 			}
    338 			else
    339 			{
    340 				part = input.getMessage().getPart( partName );
    341 			}
    342 		}
    343
    344 		if( part != null )
    345 		{
    346 			QName typeName = part.getTypeName();
    347 			if( typeName.getNamespaceURI().equals( "http://www.w3.org/2001/XMLSchema" ))
    348 			{
    349 				if( typeName.getLocalPart().equals( "base64Binary" ))
    350 				{
    351 					return AttachmentEncoding.BASE64;
    352 				}
    353 				else if( typeName.getLocalPart().equals( "hexBinary" ))
    354 				{
    355 					return AttachmentEncoding.HEX;
    356 				}
    357 			}
    358 		}
    359
    360 		return AttachmentEncoding.NONE;
    361 	}
    362
    363 	public static boolean isXopInclude( SchemaType schemaType )
    364 	{
    365 		return XOP_INCLUDE_QNAME.equals( schemaType.getName() );
    366 	}
    367
    368 	public static List<WsdlAttachmentPart> extractAttachmentParts( WsdlOperation operation, String messageContent, boolean addAnonymous, boolean isResponse )
    369 	{
    370 		List<WsdlAttachmentPart> result = new ArrayList<WsdlAttachmentPart>();
    371
    372 		PartsConfig messageParts = isResponse ? operation.getConfig().getResponseParts() : operation.getConfig().getRequestParts();
    373 		if( messageParts != null )
    374 		{
    375 			for( Part part : messageParts.getPartList() )
    376 			{
    377 				WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( part.getName(), part.getContentTypeList() );
    378 				attachmentPart.setType( Attachment.AttachmentType.MIME );
    379 				result.add( attachmentPart );
    380 			}
    381 		}
    382
    383 		if( messageContent.length() > 0 )
    384 		{
    385 			WsdlContext wsdlContext = ((WsdlInterface)operation.getInterface()).getWsdlContext();
    386 			WsdlValidator validator = new WsdlValidator( wsdlContext );
    387 			try
    388 			{
    389 				XmlObject[] requestDocuments = validator.getMessageParts( messageContent, operation.getName(), isResponse );
    390
    391 				for( XmlObject partDoc : requestDocuments )
    392 				{
    393 					XmlCursor cursor = partDoc.newCursor();
    394 					while( !cursor.isEnddoc() )
    395 					{
    396 						if( cursor.isContainer() )
    397 						{
    398 							SchemaType schemaType = cursor.getObject().schemaType();
    399 							if( schemaType != null )
    400 							{
    401 								String attributeText = AttachmentUtils.getXmlMimeContentType(cursor);
    402
    403 								// xop?
    404 								if( SchemaUtils.isBinaryType( schemaType ))
    405 								{
    406 									String contentId = cursor.getTextValue();
    407 									if( contentId.startsWith( "cid:" ))
    408 									{
    409 										WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( contentId.substring( 4 ), attributeText);
    410 										attachmentPart.setType( attributeText == null ? Attachment.AttachmentType.CONTENT : Attachment.AttachmentType.XOP );
    411 										result.add( attachmentPart );
    412 									}
    413 								}
    414 								else if( AttachmentUtils.isXopInclude( schemaType ))
    415 								{
    416 									String contentId = cursor.getAttributeText( new QName("href"));
    417 									if( contentId != null && contentId.length() > 0 )
    418 									{
    419 										WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( contentId, attributeText);
    420 										attachmentPart.setType( Attachment.AttachmentType.XOP );
    421 										result.add( attachmentPart );
    422 									}
    423 								}
    424 								// swaref?
    425 								else if( AttachmentUtils.isSwaRefType(schemaType) )
    426 								{
    427 									String contentId = cursor.getTextValue();
    428 									if( contentId.startsWith( "cid:" ))
    429 									{
    430 										WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( contentId.substring( 4 ), attributeText);
    431 										attachmentPart.setType( Attachment.AttachmentType.SWAREF );
    432 										result.add( attachmentPart );
    433 									}
    434 								}
    435 							}
    436 						}
    437
    438 						cursor.toNextToken();
    439 					}
    440 				}
    441 			}
    442 			catch( Exception e )
    443 			{
    444 				log.warn( e.getMessage() );
    445 			}
    446 		}
    447
    448 		if( addAnonymous )
    449 			result.add( new WsdlAttachmentPart() );
    450
    451 		return result;
    452 	}
    453
    454 	/***
    455 	 * Adds defined attachments as mimeparts
    456 	 */
    457
    458 	public static void addMimeParts(AttachmentContainer container, MimeMultipart mp, StringToStringMap contentIds) throws MessagingException
    459 	{
    460 		// no multipart handling?
    461 		if( !container.isMultipartEnabled())
    462 		{
    463 			for( int c = 0; c < container.getAttachmentCount(); c++ )
    464 			{
    465 				Attachment att = container.getAttachmentAt( c );
    466 				addSingleAttachment(mp, contentIds, att);
    467 			}
    468 		}
    469 		else
    470 		{
    471 			// first identify if any part has more than one attachments
    472 			Map<String,List<Attachment> > attachmentsMap = new HashMap<String, List<Attachment> >();
    473 			for( int c = 0; c < container.getAttachmentCount(); c++ )
    474 			{
    475 				Attachment att = container.getAttachmentAt( c );
    476 				String partName = att.getPart();
    477
    478 				if( !attachmentsMap.containsKey( partName ))
    479 				{
    480 					attachmentsMap.put( partName, new ArrayList<Attachment>() );
    481 				}
    482
    483 				attachmentsMap.get( partName ).add( att );
    484 			}
    485
    486 			// add attachments
    487 			for( Iterator<String> i = attachmentsMap.keySet().iterator(); i.hasNext(); )
    488 			{
    489 				List<Attachment> attachments = attachmentsMap.get( i.next() );
    490 				if( attachments.size() == 1 )
    491 				{
    492 					Attachment att = attachments.get( 0 );
    493 					addSingleAttachment(mp, contentIds, att);
    494 				}
    495 				// more than one attachment with the same part -> create multipart attachment
    496 				else if( attachments.size() > 1 )
    497 				{
    498 					addMultipartAttachment(mp, contentIds, attachments);
    499 				}
    500 			}
    501 		}
    502 	}
    503
    504 	/***
    505 	 * Adds a mulitpart MimeBodyPart from an array of attachments
    506 	 */
    507
    508 	public static void addMultipartAttachment(MimeMultipart mp, StringToStringMap contentIds, List<Attachment> attachments) throws MessagingException
    509 	{
    510 		MimeMultipart multipart = new MimeMultipart("mixed");
    511 		for( int c = 0; c < attachments.size(); c++ )
    512 		{
    513 			Attachment att = attachments.get( c );
    514 			String contentType = att.getContentType();
    515
    516 			MimeBodyPart part = contentType.startsWith( "text/" ) ? new MimeBodyPart() : new PreencodedMimeBodyPart( "binary" );
    517
    518 			part.setDataHandler( new DataHandler( new AttachmentDataSource( att ) ));
    519 			initPartContentId( contentIds, part, att, false );
    520 			multipart.addBodyPart( part );
    521 		}
    522
    523 		MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
    524 		part.setDataHandler( new DataHandler( new MultipartAttachmentDataSource( multipart ) ));
    525
    526 		Attachment attachment = attachments.get( 0 );
    527 		initPartContentId( contentIds, part, attachment, true );
    528
    529 		mp.addBodyPart( part );
    530 	}
    531
    532 	public static void initPartContentId( StringToStringMap contentIds, MimeBodyPart part, Attachment attachment, boolean isMultipart ) throws MessagingException
    533 	{
    534 		String partName = attachment.getPart();
    535
    536 		String contentID = attachment.getContentID();
    537 		if( contentID != null )
    538 		{
    539 			int ix = contentID.indexOf( ' ' );
    540 			if( ix != -1 )
    541 				part.setContentID( "<" + (isMultipart ? contentID.substring( ix+1 ) : contentID.substring( 0, ix )) + ">" );
    542 			else
    543 				part.setContentID( contentID );
    544
    545 		}
    546 		else if( partName != null && !partName.equals( WsdlAttachmentPart.ANONYMOUS_NAME ))
    547 		{
    548 			if( contentIds.containsKey( partName ))
    549 			{
    550 				part.setContentID( "<" + contentIds.get( partName )+ ">" );
    551 			}
    552 			else
    553 			{
    554 				part.setContentID( "<" + partName + "=" + System.nanoTime() + "@soapui.org>" );
    555 			}
    556 		}
    557 	}
    558
    559 	/***
    560 	 * Adds a simple MimeBodyPart from an attachment
    561 	 */
    562
    563 	public static void addSingleAttachment(MimeMultipart mp, StringToStringMap contentIds, Attachment att) throws MessagingException
    564 	{
    565 		String contentType = att.getContentType();
    566 		MimeBodyPart part = contentType.startsWith( "text/" ) ? new MimeBodyPart() : new PreencodedMimeBodyPart( "binary" );
    567
    568 		part.setDataHandler( new DataHandler( new AttachmentDataSource( att ) ));
    569 		initPartContentId( contentIds, part, att, false );
    570
    571 		mp.addBodyPart( part );
    572 	}
    573
    574 	public static final Session JAVAMAIL_SESSION = Session.getDefaultInstance( new Properties() );
    575 }