1 /*
   2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.ws.encoding;
  27 
  28 import com.sun.istack.internal.NotNull;
  29 import com.sun.xml.internal.bind.DatatypeConverterImpl;
  30 import com.sun.xml.internal.ws.api.SOAPVersion;
  31 import com.sun.xml.internal.ws.api.WSFeatureList;
  32 import com.sun.xml.internal.ws.api.message.Attachment;
  33 import com.sun.xml.internal.ws.api.message.AttachmentSet;
  34 import com.sun.xml.internal.ws.api.message.Packet;
  35 import com.sun.xml.internal.ws.api.pipe.ContentType;
  36 import com.sun.xml.internal.ws.api.pipe.StreamSOAPCodec;
  37 import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
  38 import com.sun.xml.internal.ws.api.streaming.XMLStreamWriterFactory;
  39 import com.sun.xml.internal.ws.developer.SerializationFeature;
  40 import com.sun.xml.internal.ws.developer.StreamingDataHandler;
  41 import com.sun.xml.internal.ws.message.MimeAttachmentSet;
  42 import com.sun.xml.internal.ws.streaming.XMLStreamWriterUtil;
  43 import com.sun.xml.internal.ws.util.ByteArrayDataSource;
  44 import com.sun.xml.internal.ws.util.xml.XMLStreamReaderFilter;
  45 import com.sun.xml.internal.ws.util.xml.XMLStreamWriterFilter;
  46 import com.sun.xml.internal.ws.streaming.MtomStreamWriter;
  47 import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
  48 import com.sun.xml.internal.ws.server.UnsupportedMediaException;
  49 import com.sun.xml.internal.org.jvnet.staxex.Base64Data;
  50 import com.sun.xml.internal.org.jvnet.staxex.NamespaceContextEx;
  51 import com.sun.xml.internal.org.jvnet.staxex.XMLStreamReaderEx;
  52 import com.sun.xml.internal.org.jvnet.staxex.XMLStreamWriterEx;
  53 
  54 import javax.activation.DataHandler;
  55 import javax.xml.namespace.NamespaceContext;
  56 import javax.xml.stream.XMLStreamConstants;
  57 import javax.xml.stream.XMLStreamException;
  58 import javax.xml.stream.XMLStreamReader;
  59 import javax.xml.stream.XMLStreamWriter;
  60 import javax.xml.ws.WebServiceException;
  61 import javax.xml.ws.soap.MTOMFeature;
  62 import javax.xml.bind.attachment.AttachmentMarshaller;
  63 import java.io.IOException;
  64 import java.io.OutputStream;
  65 import java.io.UnsupportedEncodingException;
  66 import java.net.URLDecoder;
  67 import java.nio.channels.WritableByteChannel;
  68 import java.nio.charset.Charset;
  69 import java.util.ArrayList;
  70 import java.util.Iterator;
  71 import java.util.List;
  72 import java.util.Map;
  73 import java.util.UUID;
  74 
  75 /**
  76  * Mtom message Codec. It can be used even for non-soap message's mtom encoding.
  77  *
  78  * @author Vivek Pandey
  79  * @author Jitendra Kotamraju
  80  */
  81 public class MtomCodec extends MimeCodec {
  82 
  83     public static final String XOP_XML_MIME_TYPE = "application/xop+xml";
  84     public static final String XOP_LOCALNAME = "Include";
  85     public static final String XOP_NAMESPACEURI = "http://www.w3.org/2004/08/xop/include";
  86 
  87     private final StreamSOAPCodec codec;
  88     private final MTOMFeature mtomFeature;
  89     private final SerializationFeature sf;
  90     private final static String DECODED_MESSAGE_CHARSET = "decodedMessageCharset";
  91 
  92     MtomCodec(SOAPVersion version, StreamSOAPCodec codec, WSFeatureList features){
  93         super(version, features);
  94         this.codec = codec;
  95         sf = features.get(SerializationFeature.class);
  96         MTOMFeature mtom = features.get(MTOMFeature.class);
  97         if(mtom == null)
  98             this.mtomFeature = new MTOMFeature();
  99         else
 100             this.mtomFeature = mtom;
 101     }
 102 
 103     /**
 104      * Return the soap 1.1 and soap 1.2 specific XOP packaged ContentType
 105      *
 106      * @return A non-null content type for soap11 or soap 1.2 content type
 107      */
 108     @Override
 109     public ContentType getStaticContentType(Packet packet) {
 110         return getStaticContentTypeStatic(packet, version);
 111     }
 112 
 113     public static ContentType getStaticContentTypeStatic(Packet packet, SOAPVersion version) {
 114         ContentType ct = (ContentType) packet.getInternalContentType();
 115         if ( ct != null ) return ct;
 116 
 117         String uuid = UUID.randomUUID().toString();
 118         String boundary = "uuid:" + uuid;
 119         String rootId = "<rootpart*"+uuid+"@example.jaxws.sun.com>";
 120         String soapActionParameter = SOAPVersion.SOAP_11.equals(version) ?  null : createActionParameter(packet);
 121 
 122         String boundaryParameter = "boundary=\"" + boundary +"\"";
 123         String messageContentType = MULTIPART_RELATED_MIME_TYPE +
 124                 ";start=\""+rootId +"\"" +
 125                 ";type=\"" + XOP_XML_MIME_TYPE + "\";" +
 126                 boundaryParameter +
 127                 ";start-info=\"" + version.contentType +
 128                 (soapActionParameter == null? "" : soapActionParameter) +
 129                 "\"";
 130 
 131         ContentTypeImpl ctImpl = SOAPVersion.SOAP_11.equals(version) ?
 132                 new ContentTypeImpl(messageContentType, (packet.soapAction == null)?"":packet.soapAction, null) :
 133                 new ContentTypeImpl(messageContentType, null, null);
 134         ctImpl.setBoundary(boundary);
 135         ctImpl.setRootId(rootId);
 136         packet.setContentType(ctImpl);
 137         return ctImpl;
 138     }
 139 
 140     private static String createActionParameter(Packet packet) {
 141         return packet.soapAction != null? ";action=\\\""+packet.soapAction+"\\\"" : "";
 142     }
 143 
 144     @Override
 145     public ContentType encode(Packet packet, OutputStream out) throws IOException {
 146         ContentTypeImpl ctImpl = (ContentTypeImpl) this.getStaticContentType(packet);
 147         String boundary = ctImpl.getBoundary();
 148         String rootId = ctImpl.getRootId();
 149 
 150         if(packet.getMessage() != null){
 151             try {
 152                 String encoding = getPacketEncoding(packet);
 153                 packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET);
 154 
 155                 String actionParameter = getActionParameter(packet, version);
 156                 String soapXopContentType = getSOAPXopContentType(encoding, version, actionParameter);
 157 
 158                 writeln("--"+boundary, out);
 159                 writeMimeHeaders(soapXopContentType, rootId, out);
 160 
 161                 //mtom attachments that need to be written after the root part
 162                 List<ByteArrayBuffer> mtomAttachments = new ArrayList<ByteArrayBuffer>();
 163                 MtomStreamWriterImpl writer = new MtomStreamWriterImpl(
 164                         XMLStreamWriterFactory.create(out, encoding), mtomAttachments, boundary, mtomFeature);
 165 
 166                 packet.getMessage().writeTo(writer);
 167                 XMLStreamWriterFactory.recycle(writer);
 168                 writeln(out);
 169 
 170                 for(ByteArrayBuffer bos : mtomAttachments){
 171                     bos.write(out);
 172                 }
 173 
 174                 // now write out the attachments in the message that weren't
 175                 // previously written
 176                 writeNonMtomAttachments(packet.getMessage().getAttachments(),
 177                         out, boundary);
 178 
 179                 //write out the end boundary
 180                 writeAsAscii("--"+boundary, out);
 181                 writeAsAscii("--", out);
 182 
 183             } catch (XMLStreamException e) {
 184                 throw new WebServiceException(e);
 185             }
 186         }
 187         //now create the boundary for next encode() call
 188 //        createConteTypeHeader();
 189         return ctImpl;
 190     }
 191 
 192     public static String getSOAPXopContentType(String encoding, SOAPVersion version,
 193             String actionParameter) {
 194         return XOP_XML_MIME_TYPE +";charset="+encoding+";type=\""+version.contentType+ actionParameter + "\"";
 195     }
 196 
 197     public static String getActionParameter(Packet packet, SOAPVersion version) {
 198         return (version == SOAPVersion.SOAP_11) ? "" : createActionParameter(packet);
 199     }
 200 
 201     public static class ByteArrayBuffer{
 202         final String contentId;
 203 
 204         private final DataHandler dh;
 205         private final String boundary;
 206 
 207         ByteArrayBuffer(@NotNull DataHandler dh, String b) {
 208             this.dh = dh;
 209             String cid = null;
 210             if (dh instanceof StreamingDataHandler) {
 211                 StreamingDataHandler sdh = (StreamingDataHandler) dh;
 212                 if (sdh.getHrefCid() != null)
 213                     cid = sdh.getHrefCid();
 214             }
 215             this.contentId = cid != null ? cid : encodeCid();
 216             boundary = b;
 217         }
 218 
 219         public void write(OutputStream os) throws IOException {
 220             //build attachment frame
 221             writeln("--"+boundary, os);
 222             writeMimeHeaders(dh.getContentType(), contentId, os);
 223             dh.writeTo(os);
 224             writeln(os);
 225         }
 226     }
 227 
 228     public static void writeMimeHeaders(String contentType, String contentId, OutputStream out) throws IOException {
 229         String cid = contentId;
 230         if(cid != null && cid.length() >0 && cid.charAt(0) != '<')
 231             cid = '<' + cid + '>';
 232         writeln("Content-Id: " + cid, out);
 233         writeln("Content-Type: " + contentType, out);
 234         writeln("Content-Transfer-Encoding: binary", out);
 235         writeln(out);
 236     }
 237 
 238     // Compiler warning for not calling close, but cannot call close,
 239     // will consume attachment bytes.
 240         @SuppressWarnings("resource")
 241     private void writeNonMtomAttachments(AttachmentSet attachments,
 242             OutputStream out, String boundary) throws IOException {
 243 
 244         for (Attachment att : attachments) {
 245 
 246             DataHandler dh = att.asDataHandler();
 247             if (dh instanceof StreamingDataHandler) {
 248                 StreamingDataHandler sdh = (StreamingDataHandler) dh;
 249                 // If DataHandler has href Content-ID, it is MTOM, so skip.
 250                 if (sdh.getHrefCid() != null)
 251                     continue;
 252             }
 253 
 254             // build attachment frame
 255             writeln("--" + boundary, out);
 256             writeMimeHeaders(att.getContentType(), att.getContentId(), out);
 257             att.writeTo(out);
 258             writeln(out); // write \r\n
 259         }
 260     }
 261 
 262     @Override
 263     public ContentType encode(Packet packet, WritableByteChannel buffer) {
 264         throw new UnsupportedOperationException();
 265     }
 266 
 267     @Override
 268     public MtomCodec copy() {
 269         return new MtomCodec(version, (StreamSOAPCodec)codec.copy(), features);
 270     }
 271 
 272     private static String encodeCid(){
 273         String cid="example.jaxws.sun.com";
 274         String name = UUID.randomUUID()+"@";
 275         return name + cid;
 276     }
 277 
 278     @Override
 279     protected void decode(MimeMultipartParser mpp, Packet packet) throws IOException {
 280         //TODO shouldn't we check for SOAP1.1/SOAP1.2 and throw
 281         //TODO UnsupportedMediaException like StreamSOAPCodec
 282         String charset = null;
 283         String ct = mpp.getRootPart().getContentType();
 284         if (ct != null) {
 285             charset = new ContentTypeImpl(ct).getCharSet();
 286         }
 287         if (charset != null && !Charset.isSupported(charset)) {
 288             throw new UnsupportedMediaException(charset);
 289         }
 290 
 291         if (charset != null) {
 292             packet.invocationProperties.put(DECODED_MESSAGE_CHARSET, charset);
 293         } else {
 294             packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET);
 295         }
 296 
 297         // we'd like to reuse those reader objects but unfortunately decoder may be reused
 298         // before the decoded message is completely used.
 299         XMLStreamReader mtomReader = new MtomXMLStreamReaderEx( mpp,
 300             XMLStreamReaderFactory.create(null, mpp.getRootPart().asInputStream(), charset, true)
 301         );
 302 
 303         packet.setMessage(codec.decode(mtomReader, new MimeAttachmentSet(mpp)));
 304         packet.setMtomFeature(mtomFeature);
 305         packet.setContentType(mpp.getContentType());
 306     }
 307 
 308     private String getPacketEncoding(Packet packet) {
 309         // If SerializationFeature is set, just use that encoding
 310         if (sf != null && sf.getEncoding() != null) {
 311             return sf.getEncoding().equals("") ? SOAPBindingCodec.DEFAULT_ENCODING : sf.getEncoding();
 312         }
 313         return determinePacketEncoding(packet);
 314     }
 315 
 316     public static String determinePacketEncoding(Packet packet) {
 317         if (packet != null && packet.endpoint != null) {
 318             // Use request message's encoding for Server-side response messages
 319             String charset = (String)packet.invocationProperties.get(DECODED_MESSAGE_CHARSET);
 320             return charset == null
 321                     ? SOAPBindingCodec.DEFAULT_ENCODING : charset;
 322         }
 323 
 324         // Use default encoding for client-side request messages
 325         return SOAPBindingCodec.DEFAULT_ENCODING;
 326     }
 327 
 328     public static class MtomStreamWriterImpl extends XMLStreamWriterFilter implements XMLStreamWriterEx,
 329             MtomStreamWriter, HasEncoding {
 330         private final List<ByteArrayBuffer> mtomAttachments;
 331         private final String boundary;
 332         private final MTOMFeature myMtomFeature;
 333         public MtomStreamWriterImpl(XMLStreamWriter w, List<ByteArrayBuffer> mtomAttachments, String b, MTOMFeature myMtomFeature) {
 334             super(w);
 335             this.mtomAttachments = mtomAttachments;
 336             this.boundary = b;
 337             this.myMtomFeature = myMtomFeature;
 338         }
 339 
 340         @Override
 341         public void writeBinary(byte[] data, int start, int len, String contentType) throws XMLStreamException {
 342             //check threshold and if less write as base64encoded value
 343             if(myMtomFeature.getThreshold() > len){
 344                 writeCharacters(DatatypeConverterImpl._printBase64Binary(data, start, len));
 345                 return;
 346             }
 347             ByteArrayBuffer bab = new ByteArrayBuffer(new DataHandler(new ByteArrayDataSource(data, start, len, contentType)), boundary);
 348             writeBinary(bab);
 349         }
 350 
 351         @Override
 352         public void writeBinary(DataHandler dataHandler) throws XMLStreamException {
 353             // TODO how do we check threshold and if less inline the data
 354             writeBinary(new ByteArrayBuffer(dataHandler, boundary));
 355         }
 356 
 357         @Override
 358         public OutputStream writeBinary(String contentType) throws XMLStreamException {
 359             throw new UnsupportedOperationException();
 360         }
 361 
 362         @Override
 363         public void writePCDATA(CharSequence data) throws XMLStreamException {
 364             if(data == null)
 365                 return;
 366             if(data instanceof Base64Data){
 367                 Base64Data binaryData = (Base64Data)data;
 368                 writeBinary(binaryData.getDataHandler());
 369                 return;
 370             }
 371             writeCharacters(data.toString());
 372         }
 373 
 374         private void writeBinary(ByteArrayBuffer bab) {
 375             try {
 376                 mtomAttachments.add(bab);
 377                 writer.setPrefix("xop", XOP_NAMESPACEURI);
 378                 writer.writeNamespace("xop", XOP_NAMESPACEURI);
 379                 writer.writeStartElement(XOP_NAMESPACEURI, XOP_LOCALNAME);
 380                 writer.writeAttribute("href", "cid:"+bab.contentId);
 381                 writer.writeEndElement();
 382                 writer.flush();
 383             } catch (XMLStreamException e) {
 384                 throw new WebServiceException(e);
 385             }
 386         }
 387 
 388         @Override
 389         public Object getProperty(String name) throws IllegalArgumentException {
 390             // Hack for JDK6's SJSXP
 391             if (name.equals("sjsxp-outputstream") && writer instanceof Map) {
 392                 Object obj = ((Map) writer).get("sjsxp-outputstream");
 393                 if (obj != null) {
 394                     return obj;
 395                 }
 396             }
 397             return super.getProperty(name);
 398         }
 399 
 400         /**
 401          * JAXBMessage writes envelope directly to the OutputStream(for SJSXP, woodstox).
 402          * While writing, it calls the AttachmentMarshaller methods for adding attachments.
 403          * JAXB writes xop:Include in this case.
 404          */
 405         @Override
 406         public AttachmentMarshaller getAttachmentMarshaller() {
 407             return new AttachmentMarshaller() {
 408 
 409                 @Override
 410                 public String addMtomAttachment(DataHandler data, String elementNamespace, String elementLocalName) {
 411                     // Should we do the threshold processing on DataHandler ? But that would be
 412                     // expensive as DataHolder need to read the data again from its source
 413                     ByteArrayBuffer bab = new ByteArrayBuffer(data, boundary);
 414                     mtomAttachments.add(bab);
 415                     return "cid:"+bab.contentId;
 416                 }
 417 
 418                 @Override
 419                 public String addMtomAttachment(byte[] data, int offset, int length, String mimeType, String elementNamespace, String elementLocalName) {
 420                     // inline the data based on the threshold
 421                     if (myMtomFeature.getThreshold() > length) {
 422                         return null;                // JAXB inlines the attachment data
 423                     }
 424                     ByteArrayBuffer bab = new ByteArrayBuffer(new DataHandler(new ByteArrayDataSource(data, offset, length, mimeType)), boundary);
 425                     mtomAttachments.add(bab);
 426                     return "cid:"+bab.contentId;
 427                 }
 428 
 429                 @Override
 430                 public String addSwaRefAttachment(DataHandler data) {
 431                     ByteArrayBuffer bab = new ByteArrayBuffer(data, boundary);
 432                     mtomAttachments.add(bab);
 433                     return "cid:"+bab.contentId;
 434                 }
 435 
 436                 @Override
 437                 public boolean isXOPPackage() {
 438                     return true;
 439                 }
 440             };
 441         }
 442 
 443         public List<ByteArrayBuffer> getMtomAttachments() {
 444             return this.mtomAttachments;
 445         }
 446 
 447         @Override
 448         public String getEncoding() {
 449             return XMLStreamWriterUtil.getEncoding(writer);
 450         }
 451 
 452         private static class MtomNamespaceContextEx implements NamespaceContextEx {
 453             private final NamespaceContext nsContext;
 454 
 455             public MtomNamespaceContextEx(NamespaceContext nsContext) {
 456                 this.nsContext = nsContext;
 457             }
 458 
 459             @Override
 460             public Iterator<Binding> iterator() {
 461                 throw new UnsupportedOperationException();
 462             }
 463 
 464             @Override
 465             public String getNamespaceURI(String prefix) {
 466                 return nsContext.getNamespaceURI(prefix);
 467             }
 468 
 469             @Override
 470             public String getPrefix(String namespaceURI) {
 471                 return nsContext.getPrefix(namespaceURI);
 472             }
 473 
 474             @Override
 475             public Iterator getPrefixes(String namespaceURI) {
 476                 return nsContext.getPrefixes(namespaceURI);
 477             }
 478         }
 479 
 480         @Override
 481         public NamespaceContextEx getNamespaceContext() {
 482             NamespaceContext nsContext = writer.getNamespaceContext();
 483             return new MtomNamespaceContextEx(nsContext);
 484         }
 485     }
 486 
 487     public static class MtomXMLStreamReaderEx extends XMLStreamReaderFilter implements XMLStreamReaderEx {
 488         /**
 489          * The parser for the outer MIME 'shell'.
 490          */
 491         private final MimeMultipartParser mimeMP;
 492 
 493         private boolean xopReferencePresent = false;
 494         private Base64Data base64AttData;
 495 
 496         //To be used with #getTextCharacters
 497         private char[] base64EncodedText;
 498 
 499         private String xopHref;
 500 
 501         public MtomXMLStreamReaderEx(MimeMultipartParser mimeMP, XMLStreamReader reader) {
 502             super(reader);
 503             this.mimeMP = mimeMP;
 504         }
 505 
 506         @Override
 507         public CharSequence getPCDATA() throws XMLStreamException {
 508             if(xopReferencePresent){
 509                 return base64AttData;
 510             }
 511             return reader.getText();
 512         }
 513 
 514         @Override
 515         public NamespaceContextEx getNamespaceContext() {
 516             NamespaceContext nsContext = reader.getNamespaceContext();
 517             return new MtomNamespaceContextEx(nsContext);
 518         }
 519 
 520         @Override
 521         public String getElementTextTrim() throws XMLStreamException {
 522             throw new UnsupportedOperationException();
 523         }
 524 
 525         private static class MtomNamespaceContextEx implements NamespaceContextEx {
 526             private final NamespaceContext nsContext;
 527 
 528             public MtomNamespaceContextEx(NamespaceContext nsContext) {
 529                 this.nsContext = nsContext;
 530             }
 531 
 532             @Override
 533             public Iterator<Binding> iterator() {
 534                 throw new UnsupportedOperationException();
 535             }
 536 
 537             @Override
 538             public String getNamespaceURI(String prefix) {
 539                 return nsContext.getNamespaceURI(prefix);
 540             }
 541 
 542             @Override
 543             public String getPrefix(String namespaceURI) {
 544                 return nsContext.getPrefix(namespaceURI);
 545             }
 546 
 547             @Override
 548             public Iterator getPrefixes(String namespaceURI) {
 549                 return nsContext.getPrefixes(namespaceURI);
 550             }
 551 
 552         }
 553 
 554         @Override
 555         public int getTextLength() {
 556             if (xopReferencePresent) {
 557                 return base64AttData.length();
 558             }
 559             return reader.getTextLength();
 560         }
 561 
 562         @Override
 563         public int getTextStart() {
 564             if (xopReferencePresent) {
 565                 return 0;
 566             }
 567             return reader.getTextStart();
 568         }
 569 
 570         @Override
 571         public int getEventType() {
 572             if(xopReferencePresent)
 573                 return XMLStreamConstants.CHARACTERS;
 574             return super.getEventType();
 575         }
 576 
 577         @Override
 578         public int next() throws XMLStreamException {
 579             int event = reader.next();
 580             if (event == XMLStreamConstants.START_ELEMENT && reader.getLocalName().equals(XOP_LOCALNAME) && reader.getNamespaceURI().equals(XOP_NAMESPACEURI)) {
 581                 //its xop reference, take the URI reference
 582                 String href = reader.getAttributeValue(null, "href");
 583                 try {
 584                     xopHref = href;
 585                     Attachment att = getAttachment(href);
 586                     if(att != null){
 587                         DataHandler dh = att.asDataHandler();
 588                         if (dh instanceof StreamingDataHandler) {
 589                             ((StreamingDataHandler)dh).setHrefCid(att.getContentId());
 590                         }
 591                         base64AttData = new Base64Data();
 592                         base64AttData.set(dh);
 593                     }
 594                     xopReferencePresent = true;
 595                 } catch (IOException e) {
 596                     throw new WebServiceException(e);
 597                 }
 598                 //move to the </xop:Include>
 599                 XMLStreamReaderUtil.nextElementContent(reader);
 600                 return XMLStreamConstants.CHARACTERS;
 601             }
 602             if(xopReferencePresent){
 603                 xopReferencePresent = false;
 604                 base64EncodedText = null;
 605                 xopHref = null;
 606             }
 607             return event;
 608         }
 609 
 610         private String decodeCid(String cid) {
 611             try {
 612                 cid = URLDecoder.decode(cid, "utf-8");
 613             } catch (UnsupportedEncodingException e) {
 614                 //on recceiving side lets not fail now, try to look for it
 615             }
 616             return cid;
 617         }
 618 
 619         private Attachment getAttachment(String cid) throws IOException {
 620             if (cid.startsWith("cid:"))
 621                 cid = cid.substring(4, cid.length());
 622             if (cid.indexOf('%') != -1) {
 623                 cid = decodeCid(cid);
 624                 return mimeMP.getAttachmentPart(cid);
 625             }
 626             return mimeMP.getAttachmentPart(cid);
 627         }
 628 
 629         @Override
 630         public char[] getTextCharacters() {
 631             if (xopReferencePresent) {
 632                 char[] chars = new char[base64AttData.length()];
 633                 base64AttData.writeTo(chars, 0);
 634                 return chars;
 635             }
 636             return reader.getTextCharacters();
 637         }
 638 
 639         @Override
 640         public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) throws XMLStreamException {
 641             if(xopReferencePresent){
 642                 if(target == null){
 643                     throw new NullPointerException("target char array can't be null") ;
 644                 }
 645 
 646                 if(targetStart < 0 || length < 0 || sourceStart < 0 || targetStart >= target.length ||
 647                         (targetStart + length ) > target.length) {
 648                     throw new IndexOutOfBoundsException();
 649                 }
 650 
 651                 int textLength = base64AttData.length();
 652                 if(sourceStart > textLength)
 653                     throw new IndexOutOfBoundsException();
 654 
 655                 if(base64EncodedText == null){
 656                     base64EncodedText = new char[base64AttData.length()];
 657                     base64AttData.writeTo(base64EncodedText, 0);
 658                 }
 659 
 660                 int copiedLength = Math.min(textLength - sourceStart, length);
 661                 System.arraycopy(base64EncodedText, sourceStart , target, targetStart, copiedLength);
 662                 return copiedLength;
 663             }
 664             return reader.getTextCharacters(sourceStart, target, targetStart, length);
 665         }
 666 
 667         @Override
 668         public String getText() {
 669             if (xopReferencePresent) {
 670                 return base64AttData.toString();
 671             }
 672             return reader.getText();
 673         }
 674 
 675         protected boolean isXopReference() throws XMLStreamException {
 676             return xopReferencePresent;
 677         }
 678 
 679         protected String getXopHref() {
 680             return xopHref;
 681         }
 682 
 683         public MimeMultipartParser getMimeMultipartParser() {
 684             return mimeMP;
 685         }
 686     }
 687 
 688 }