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