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