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