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