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