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