1 /*
   2  * Copyright (c) 1997, 2014, 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.org.jvnet.staxex;
  27 
  28 import javax.activation.DataHandler;
  29 import javax.activation.DataSource;
  30 import java.io.ByteArrayInputStream;
  31 import java.io.ByteArrayOutputStream;
  32 import java.io.File;
  33 import java.io.FileOutputStream;
  34 import java.io.IOException;
  35 import java.io.InputStream;
  36 import java.io.OutputStream;
  37 import javax.xml.stream.XMLStreamException;
  38 import javax.xml.stream.XMLStreamWriter;
  39 import java.util.logging.Level;
  40 import java.util.logging.Logger;
  41 
  42 // for testing method
  43 //import com.sun.xml.internal.stream.writers.XMLStreamWriterImpl;
  44 //import java.io.FileNotFoundException;
  45 //import java.io.FileWriter;
  46 //import javax.activation.FileDataSource;
  47 
  48 /**
  49  * Binary data represented as base64-encoded string
  50  * in XML.
  51  *
  52  * <p>
  53  * Used in conjunction with {@link XMLStreamReaderEx}
  54  * and {@link XMLStreamWriterEx}.
  55  *
  56  * @author Kohsuke Kawaguchi, Martin Grebac
  57  */
  58 public class Base64Data implements CharSequence, Cloneable {
  59 
  60     // either dataHandler or (data,dataLen,mimeType?) must be present
  61     // (note that having both is allowed)
  62 
  63     private DataHandler dataHandler;
  64     private byte[] data;
  65     private String hrefCid;
  66 
  67     /**
  68      * Length of the valid data in {@link #data}.
  69      */
  70     private int dataLen;
  71     /**
  72      * True if {@link #data} can be cloned by reference
  73      * if Base64Data instance is cloned.
  74      */
  75     private boolean dataCloneByRef;
  76     /**
  77      * Optional MIME type of {@link #data}.
  78      *
  79      * Unused when {@link #dataHandler} is set.
  80      * Use {@link DataHandler#getContentType()} in that case.
  81      */
  82     private String mimeType;
  83 
  84     /**
  85      * Default constructor
  86      */
  87     public Base64Data() {
  88     }
  89 
  90     private static final Logger logger = Logger.getLogger(Base64Data.class.getName());
  91 
  92     /**
  93      * Clone constructor
  94      * @param that needs to be cloned
  95      */
  96     public Base64Data(Base64Data that) {
  97         that.get();
  98         if (that.dataCloneByRef) {
  99             this.data = that.data;
 100         } else {
 101             this.data = new byte[that.dataLen];
 102             System.arraycopy(that.data, 0, this.data, 0, that.dataLen);
 103         }
 104 
 105         this.dataCloneByRef = true;
 106         this.dataLen = that.dataLen;
 107         this.dataHandler = null;
 108         this.mimeType = that.mimeType;
 109     }
 110 
 111     /**
 112      * Fills in the data object by a portion of the byte[].
 113      *
 114      * @param data actual data
 115      * @param len
 116      *      data[0] to data[len-1] are treated as the data.
 117      * @param mimeType MIME type
 118      * @param cloneByRef
 119      *      true if data[] can be cloned by reference
 120      */
 121     public void set(byte[] data, int len, String mimeType, boolean cloneByRef) {
 122         this.data = data;
 123         this.dataLen = len;
 124         this.dataCloneByRef = cloneByRef;
 125         this.dataHandler = null;
 126         this.mimeType = mimeType;
 127     }
 128 
 129     /**
 130      * Fills in the data object by a portion of the byte[].
 131      *
 132      * @param data actual data bytes
 133      * @param len
 134      *      data[0] to data[len-1] are treated as the data.
 135      * @param mimeType MIME type
 136      */
 137     public void set(byte[] data, int len, String mimeType) {
 138         set(data,len,mimeType,false);
 139     }
 140 
 141     /**
 142      * Fills in the data object by the byte[] of the exact length.
 143      *
 144      * @param data
 145      *      this buffer may be owned directly by the unmarshaleld JAXB object.
 146      * @param mimeType MIME type
 147      */
 148     public void set(byte[] data,String mimeType) {
 149         set(data,data.length,mimeType,false);
 150     }
 151 
 152     /**
 153      * Fills in the data object by a {@link DataHandler}.
 154      *
 155      * @param data DataHandler for the data
 156      */
 157     public void set(DataHandler data) {
 158         assert data!=null;
 159         this.dataHandler = data;
 160         this.data = null;
 161     }
 162 
 163     /**
 164      * Gets the raw data. If the returned DataHandler is {@link StreamingDataHandler},
 165      * callees may need to downcast to take advantage of its capabilities.
 166      *
 167      * @see StreamingDataHandler
 168      * @return DataHandler for the data
 169      */
 170     public DataHandler getDataHandler() {
 171         if(dataHandler==null){
 172             dataHandler = new Base64StreamingDataHandler(new Base64DataSource());
 173         } else if (!(dataHandler instanceof StreamingDataHandler)) {
 174             dataHandler = new FilterDataHandler(dataHandler);
 175         }
 176         return dataHandler;
 177     }
 178 
 179     private final class Base64DataSource implements DataSource {
 180         public String getContentType() {
 181             return getMimeType();
 182         }
 183 
 184         public InputStream getInputStream() {
 185             return new ByteArrayInputStream(data,0,dataLen);
 186         }
 187 
 188         public String getName() {
 189             return null;
 190         }
 191 
 192         public OutputStream getOutputStream() {
 193             throw new UnsupportedOperationException();
 194         }
 195 
 196     }
 197 
 198     private final class Base64StreamingDataHandler extends StreamingDataHandler {
 199 
 200         Base64StreamingDataHandler(DataSource source) {
 201             super(source);
 202         }
 203 
 204         public InputStream readOnce() throws IOException {
 205             return getDataSource().getInputStream();
 206         }
 207 
 208         public void moveTo(File dst) throws IOException {
 209             FileOutputStream fout = new FileOutputStream(dst);
 210             try {
 211                 fout.write(data, 0, dataLen);
 212             } finally {
 213                 fout.close();
 214             }
 215         }
 216 
 217         public void close() throws IOException {
 218             // nothing to do
 219         }
 220     }
 221 
 222     private static final class FilterDataHandler extends StreamingDataHandler {
 223 
 224         FilterDataHandler(DataHandler dh) {
 225             super(dh.getDataSource());
 226         }
 227 
 228         public InputStream readOnce() throws IOException {
 229             return getDataSource().getInputStream();
 230         }
 231 
 232         public void moveTo(File dst) throws IOException {
 233             byte[] buf = new byte[8192];
 234             InputStream in = null;
 235             OutputStream out = null;
 236             try {
 237                 in = getDataSource().getInputStream();
 238                 out = new FileOutputStream(dst);
 239                 while (true) {
 240                     int amountRead = in.read(buf);
 241                     if (amountRead == -1) {
 242                         break;
 243                     }
 244                     out.write(buf, 0, amountRead);
 245                 }
 246             } finally {
 247                 if (in != null) {
 248                     try {
 249                         in.close();
 250                     } catch(IOException ioe) {
 251                         // nothing to do
 252                     }
 253                 }
 254                 if (out != null) {
 255                     try {
 256                         out.close();
 257                     } catch(IOException ioe) {
 258                         // nothing to do
 259                     }
 260                 }
 261             }
 262         }
 263 
 264         public void close() throws IOException {
 265             // nothing to do
 266         }
 267     }
 268 
 269     /**
 270      * Gets the byte[] of the exact length.
 271      *
 272      * @return byte[] for data
 273      */
 274     public byte[] getExact() {
 275         get();
 276         if(dataLen!=data.length) {
 277             byte[] buf = new byte[dataLen];
 278             System.arraycopy(data,0,buf,0,dataLen);
 279             data = buf;
 280         }
 281         return data;
 282     }
 283 
 284     /**
 285      * Gets the data as an {@link InputStream}.
 286      *
 287      * @return data as InputStream
 288      * @throws IOException if i/o error occurs
 289      */
 290     public InputStream getInputStream() throws IOException {
 291         if(dataHandler!=null) {
 292             return dataHandler.getInputStream();
 293         } else {
 294             return new ByteArrayInputStream(data,0,dataLen);
 295         }
 296     }
 297 
 298     /**
 299      * Returns false if this object only has {@link DataHandler} and therefore
 300      * {@link #get()} operation is likely going to be expensive.
 301      *
 302      * @return false if it has only DataHandler
 303      */
 304     public boolean hasData() {
 305         return data!=null;
 306     }
 307 
 308     /**
 309      * Gets the raw data. The size of the byte array maybe larger than the actual length.
 310      *
 311      * @return data as byte[], the array may be larger
 312      */
 313     public byte[] get() {
 314         if(data==null) {
 315             try {
 316                 ByteArrayOutputStreamEx baos = new ByteArrayOutputStreamEx(1024);
 317                 InputStream is = dataHandler.getDataSource().getInputStream();
 318                 baos.readFrom(is);
 319                 is.close();
 320                 data = baos.getBuffer();
 321                 dataLen = baos.size();
 322                 dataCloneByRef = true;
 323             } catch (IOException e) {
 324                 // TODO: report the error to the unmarshaller
 325                 dataLen = 0;    // recover by assuming length-0 data
 326             }
 327         }
 328         return data;
 329     }
 330 
 331     /**
 332      * Gets the length of the binary data counted in bytes.
 333      *
 334      * Note that if this object encapsulates {@link DataHandler},
 335      * this method would have to read the whole thing into {@code byte[]}
 336      * just to count the length, because {@link DataHandler}
 337      * doesn't easily expose the length.
 338      *
 339      * @return no of bytes
 340      */
 341     public int getDataLen() {
 342         get();
 343         return dataLen;
 344     }
 345 
 346     public String getMimeType() {
 347         if (mimeType==null) {
 348             return "application/octet-stream";
 349         }
 350         return mimeType;
 351     }
 352 
 353     /**
 354      * Gets the number of characters needed to represent
 355      * this binary data in the base64 encoding.
 356      */
 357     public int length() {
 358         // for each 3 bytes you use 4 chars
 359         // if the remainder is 1 or 2 there will be 4 more
 360         get();  // fill in the buffer if necessary
 361         return ((dataLen+2)/3)*4;
 362     }
 363 
 364     /**
 365      * Encode this binary data in the base64 encoding
 366      * and returns the character at the specified position.
 367      */
 368     public char charAt(int index) {
 369         // we assume that the length() method is called before this method
 370         // (otherwise how would the caller know that the index is valid?)
 371         // so we assume that the byte[] is already populated
 372 
 373         int offset = index%4;
 374         int base = (index/4)*3;
 375 
 376         byte b1,b2;
 377 
 378         switch(offset) {
 379         case 0:
 380             return Base64Encoder.encode(data[base]>>2);
 381         case 1:
 382             if (base+1<dataLen) {
 383                 b1 = data[base+1];
 384             } else {
 385                 b1 = 0;
 386             }
 387             return Base64Encoder.encode(
 388                         ((data[base]&0x3)<<4) |
 389                         ((b1>>4)&0xF));
 390         case 2:
 391             if (base+1<dataLen) {
 392                 b1 = data[base+1];
 393                 if (base+2<dataLen) {
 394                     b2 = data[base+2];
 395                 } else {
 396                     b2 = 0;
 397                 }
 398 
 399                 return Base64Encoder.encode(
 400                             ((b1&0xF)<<2)|
 401                             ((b2>>6)&0x3));
 402             } else {
 403                 return '=';
 404             }
 405         case 3:
 406             if(base+2<dataLen) {
 407                 return Base64Encoder.encode(data[base+2]&0x3F);
 408             } else {
 409                 return '=';
 410             }
 411         }
 412 
 413         throw new IllegalStateException();
 414     }
 415 
 416     /**
 417      * Internally this is only used to split a text to a list,
 418      * which doesn't happen that much for base64.
 419      * So this method should be smaller than faster.
 420      */
 421     public CharSequence subSequence(int start, int end) {
 422         StringBuilder buf = new StringBuilder();
 423         get();  // fill in the buffer if we haven't done so
 424         for (int i=start; i<end; i++ ) {
 425             buf.append(charAt(i));
 426         }
 427         return buf;
 428     }
 429 
 430     /**
 431      * Returns the base64 encoded string of this data.
 432      */
 433     @Override
 434     public String toString() {
 435         get();  // fill in the buffer
 436         return Base64Encoder.print(data, 0, dataLen);
 437     }
 438 
 439     public void writeTo(char[] buf, int start) {
 440         get();
 441         Base64Encoder.print(data, 0, dataLen, buf, start);
 442     }
 443 
 444     private static final int CHUNK_SIZE;
 445     static {
 446         int bufSize = 1024;
 447         try {
 448             String bufSizeStr = getProperty("com.sun.xml.internal.org.jvnet.staxex.Base64DataStreamWriteBufferSize");
 449             if (bufSizeStr != null) {
 450                 bufSize = Integer.parseInt(bufSizeStr);
 451             }
 452         } catch (Exception e) {
 453             logger.log(Level.INFO, "Error reading com.sun.xml.internal.org.jvnet.staxex.Base64DataStreamWriteBufferSize property", e);
 454         }
 455         CHUNK_SIZE = bufSize;
 456     }
 457 
 458     public void writeTo(XMLStreamWriter output) throws IOException, XMLStreamException {
 459         if (data==null) {
 460             try {
 461                 InputStream is = dataHandler.getDataSource().getInputStream();
 462                 ByteArrayOutputStream outStream = new ByteArrayOutputStream(); // dev-null stream
 463                 Base64EncoderStream encWriter = new Base64EncoderStream(output, outStream);
 464                 int b;
 465                 byte[] buffer = new byte[CHUNK_SIZE];
 466                 while ((b = is.read(buffer)) != -1) {
 467                     encWriter.write(buffer, 0, b);
 468                 }
 469                 outStream.close();
 470                 encWriter.close();
 471             } catch (IOException e) {
 472                 dataLen = 0;    // recover by assuming length-0 data
 473                 throw e;
 474             }
 475         } else {
 476             // the data is already in memory and not streamed
 477             String s = Base64Encoder.print(data, 0, dataLen);
 478             output.writeCharacters(s);
 479         }
 480     }
 481 
 482     @Override
 483     public Base64Data clone() {
 484         try {
 485             Base64Data clone = (Base64Data) super.clone();
 486             clone.get();
 487             if (clone.dataCloneByRef) {
 488                 this.data = clone.data;
 489             } else {
 490                 this.data = new byte[clone.dataLen];
 491                 System.arraycopy(clone.data, 0, this.data, 0, clone.dataLen);
 492             }
 493 
 494             this.dataCloneByRef = true;
 495             this.dataLen = clone.dataLen;
 496             this.dataHandler = null;
 497             this.mimeType = clone.mimeType;
 498             return clone;
 499         } catch (CloneNotSupportedException ex) {
 500             Logger.getLogger(Base64Data.class.getName()).log(Level.SEVERE, null, ex);
 501             return null;
 502         }
 503     }
 504 
 505     static String getProperty(final String propName) {
 506         if (System.getSecurityManager() == null) {
 507             return System.getProperty(propName);
 508         } else {
 509             return (String) java.security.AccessController.doPrivileged(
 510                     new java.security.PrivilegedAction() {
 511                         public java.lang.Object run() {
 512                             return System.getProperty(propName);
 513                         }
 514                     });
 515         }
 516     }
 517 
 518 //    public static void main(String[] args) throws FileNotFoundException, IOException, XMLStreamException {
 519 //        Base64Data data = new Base64Data();
 520 //
 521 //        File f = new File("/Users/snajper/work/builds/weblogic/wls1211_dev.zip");
 522 //        FileDataSource fds = new FileDataSource(f);
 523 //        DataHandler dh = new DataHandler(fds);
 524 //        data.set(dh);
 525 //
 526 //        FileWriter fw = new FileWriter(new File("/Users/snajper/Desktop/b.txt"));
 527 //        XMLStreamWriterImpl wi = new XMLStreamWriterImpl(fw, null);
 528 //
 529 //        data.writeTo(wi);
 530 //        wi.flush();fw.flush();
 531 //        //System.out.println("SW: " + sw.toString());
 532 //
 533 //    }
 534 
 535     public String getHrefCid() {
 536         if (hrefCid == null && dataHandler != null && dataHandler instanceof StreamingDataHandler) {
 537             hrefCid = ((StreamingDataHandler)dataHandler).getHrefCid();
 538         }
 539         return hrefCid;
 540     }
 541 
 542     public void setHrefCid(final String cid) {
 543         this.hrefCid = cid;
 544         if (dataHandler != null && dataHandler instanceof StreamingDataHandler) ((StreamingDataHandler)dataHandler).setHrefCid(cid);
 545     }
 546 
 547 }