1
2
3
4
5
6
7
8
9
10
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
93
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
117 new URI( textContent );
118 contentIds.put( textContent, textContent );
119 }
120 catch( RuntimeException e )
121 {
122
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
157 String textContent = cursor.getTextValue();
158 Attachment attachment = null;
159 boolean isXopAttachment = false;
160
161
162 if( textContent.startsWith( "file:" ))
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
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
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
217 if( isXopAttachment && container.isMtomEnabled() )
218 {
219 buildXopInclude(cursor, textContent);
220 isXop = true;
221 }
222
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
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
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
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
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
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
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
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
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 }