src/share/classes/com/sun/xml/internal/bind/v2/runtime/output/UTF8XmlOutput.java

Mon, 09 Mar 2009 15:32:10 -0700

author
ramap
date
Mon, 09 Mar 2009 15:32:10 -0700
changeset 42
99fc62f032a7
parent 1
0961a4a21176
child 46
a88ad84027a0
permissions
-rw-r--r--

6536193: Fix the flaw in UTF8XmlOutput
Reviewed-by: tbell

     1 /*
     2  * Copyright 2006 Sun Microsystems, Inc.  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.  Sun designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
    23  * have any questions.
    24  */
    25 package com.sun.xml.internal.bind.v2.runtime.output;
    27 import java.io.IOException;
    28 import java.io.OutputStream;
    30 import javax.xml.stream.XMLStreamException;
    32 import com.sun.xml.internal.bind.DatatypeConverterImpl;
    33 import com.sun.xml.internal.bind.v2.runtime.Name;
    34 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
    35 import com.sun.xml.internal.bind.v2.runtime.MarshallerImpl;
    37 import org.xml.sax.SAXException;
    39 /**
    40  * {@link XmlOutput} implementation specialized for UTF-8.
    41  *
    42  * @author Kohsuke Kawaguchi
    43  * @author Paul Sandoz
    44  */
    45 public class UTF8XmlOutput extends XmlOutputAbstractImpl {
    46     protected final OutputStream out;
    48     /** prefixes encoded. */
    49     private Encoded[] prefixes = new Encoded[8];
    51     /**
    52      * Of the {@link #prefixes}, number of filled entries.
    53      * This is almost the same as {@link NamespaceContextImpl#count()},
    54      * except that it allows us to handle contextual in-scope namespace bindings correctly.
    55      */
    56     private int prefixCount;
    58     /** local names encoded in UTF-8. All entries are pre-filled. */
    59     private final Encoded[] localNames;
    61     /** Temporary buffer used to encode text. */
    62     /*
    63      * TODO
    64      * The textBuffer could write directly to the _octetBuffer
    65      * when encoding a string if Encoder is modified.
    66      * This will avoid an additional memory copy.
    67      */
    68     private final Encoded textBuffer = new Encoded();
    70     /** Buffer of octets for writing. */
    71     // TODO: Obtain buffer size from property on the JAXB context
    72     protected final byte[] octetBuffer = new byte[1024];
    74     /** Index in buffer to write to. */
    75     protected int octetBufferIndex;
    77     /**
    78      * Set to true to indicate that we need to write '>'
    79      * to close a start tag. Deferring the write of this char
    80      * allows us to write "/>" for empty elements.
    81      */
    82     protected boolean closeStartTagPending = false;
    84     /**
    85      * @see MarshallerImpl#header
    86      */
    87     private String header;
    89     /**
    90      *
    91      * @param localNames
    92      *      local names encoded in UTF-8.
    93      */
    94     public UTF8XmlOutput(OutputStream out, Encoded[] localNames) {
    95         this.out = out;
    96         this.localNames = localNames;
    97         for( int i=0; i<prefixes.length; i++ )
    98             prefixes[i] = new Encoded();
    99     }
   101     public void setHeader(String header) {
   102         this.header = header;
   103     }
   105     @Override
   106     public void startDocument(XMLSerializer serializer, boolean fragment, int[] nsUriIndex2prefixIndex, NamespaceContextImpl nsContext) throws IOException, SAXException, XMLStreamException {
   107         super.startDocument(serializer, fragment,nsUriIndex2prefixIndex,nsContext);
   109         octetBufferIndex = 0;
   110         if(!fragment) {
   111             write(XML_DECL);
   112         }
   113         if(header!=null) {
   114             textBuffer.set(header);
   115             textBuffer.write(this);
   116         }
   117     }
   119     public void endDocument(boolean fragment) throws IOException, SAXException, XMLStreamException {
   120         flushBuffer();
   121         super.endDocument(fragment);
   122     }
   124     /**
   125      * Writes '>' to close the start tag, if necessary.
   126      */
   127     protected final void closeStartTag() throws IOException {
   128         if(closeStartTagPending) {
   129             write('>');
   130             closeStartTagPending = false;
   131         }
   132     }
   134     public void beginStartTag(int prefix, String localName) throws IOException {
   135         closeStartTag();
   136         int base= pushNsDecls();
   137         write('<');
   138         writeName(prefix,localName);
   139         writeNsDecls(base);
   140     }
   142     public void beginStartTag(Name name) throws IOException {
   143         closeStartTag();
   144         int base = pushNsDecls();
   145         write('<');
   146         writeName(name);
   147         writeNsDecls(base);
   148     }
   150     private int pushNsDecls() {
   151         int total = nsContext.count();
   152         NamespaceContextImpl.Element ns = nsContext.getCurrent();
   154         if(total > prefixes.length) {
   155             // reallocate
   156             int m = Math.max(total,prefixes.length*2);
   157             Encoded[] buf = new Encoded[m];
   158             System.arraycopy(prefixes,0,buf,0,prefixes.length);
   159             for( int i=prefixes.length; i<buf.length; i++ )
   160                 buf[i] = new Encoded();
   161             prefixes = buf;
   162         }
   164         int base = Math.min(prefixCount,ns.getBase());
   165         int size = nsContext.count();
   166         for( int i=base; i<size; i++ ) {
   167             String p = nsContext.getPrefix(i);
   169             Encoded e = prefixes[i];
   171             if(p.length()==0) {
   172                 e.buf = EMPTY_BYTE_ARRAY;
   173                 e.len = 0;
   174             } else {
   175                 e.set(p);
   176                 e.append(':');
   177             }
   178         }
   179         prefixCount = size;
   180         return base;
   181     }
   183     protected void writeNsDecls(int base) throws IOException {
   184         NamespaceContextImpl.Element ns = nsContext.getCurrent();
   185         int size = nsContext.count();
   187         for( int i=ns.getBase(); i<size; i++ )
   188             writeNsDecl(i);
   189     }
   191     /**
   192      * Writes a single namespace declaration for the specified prefix.
   193      */
   194     protected final void writeNsDecl(int prefixIndex) throws IOException {
   195         String p = nsContext.getPrefix(prefixIndex);
   197         if(p.length()==0) {
   198             if(nsContext.getCurrent().isRootElement()
   199             && nsContext.getNamespaceURI(prefixIndex).length()==0)
   200                 return;     // no point in declaring xmlns="" on the root element
   201             write(XMLNS_EQUALS);
   202         } else {
   203             Encoded e = prefixes[prefixIndex];
   204             write(XMLNS_COLON);
   205             write(e.buf,0,e.len-1); // skip the trailing ':'
   206             write(EQUALS);
   207         }
   208         doText(nsContext.getNamespaceURI(prefixIndex),true);
   209         write('\"');
   210     }
   212     private void writePrefix(int prefix) throws IOException {
   213         prefixes[prefix].write(this);
   214     }
   216     private void writeName(Name name) throws IOException {
   217         writePrefix(nsUriIndex2prefixIndex[name.nsUriIndex]);
   218         localNames[name.localNameIndex].write(this);
   219     }
   221     private void writeName(int prefix, String localName) throws IOException {
   222         writePrefix(prefix);
   223         textBuffer.set(localName);
   224         textBuffer.write(this);
   225     }
   227     @Override
   228     public void attribute(Name name, String value) throws IOException {
   229         write(' ');
   230         if(name.nsUriIndex==-1) {
   231             localNames[name.localNameIndex].write(this);
   232         } else
   233             writeName(name);
   234         write(EQUALS);
   235         doText(value,true);
   236         write('\"');
   237     }
   239     public void attribute(int prefix, String localName, String value) throws IOException {
   240         write(' ');
   241         if(prefix==-1) {
   242             textBuffer.set(localName);
   243             textBuffer.write(this);
   244         } else
   245             writeName(prefix,localName);
   246         write(EQUALS);
   247         doText(value,true);
   248         write('\"');
   249     }
   251     public void endStartTag() throws IOException {
   252         closeStartTagPending = true;
   253     }
   255     @Override
   256     public void endTag(Name name) throws IOException {
   257         if(closeStartTagPending) {
   258             write(EMPTY_TAG);
   259             closeStartTagPending = false;
   260         } else {
   261             write(CLOSE_TAG);
   262             writeName(name);
   263             write('>');
   264         }
   265     }
   267     public void endTag(int prefix, String localName) throws IOException {
   268         if(closeStartTagPending) {
   269             write(EMPTY_TAG);
   270             closeStartTagPending = false;
   271         } else {
   272             write(CLOSE_TAG);
   273             writeName(prefix,localName);
   274             write('>');
   275         }
   276     }
   278     public void text(String value, boolean needSP) throws IOException {
   279         closeStartTag();
   280         if(needSP)
   281             write(' ');
   282         doText(value,false);
   283     }
   285     public void text(Pcdata value, boolean needSP) throws IOException {
   286         closeStartTag();
   287         if(needSP)
   288             write(' ');
   289         value.writeTo(this);
   290     }
   292     private void doText(String value,boolean isAttribute) throws IOException {
   293         textBuffer.setEscape(value,isAttribute);
   294         textBuffer.write(this);
   295     }
   297     public final void text(int value) throws IOException {
   298         closeStartTag();
   299         /*
   300          * TODO
   301          * Change to use the octet buffer directly
   302          */
   304         // max is -2147483648 and 11 digits
   305         boolean minus = (value<0);
   306         textBuffer.ensureSize(11);
   307         byte[] buf = textBuffer.buf;
   308         int idx = 11;
   310         do {
   311             int r = value%10;
   312             if(r<0) r = -r;
   313             buf[--idx] = (byte)('0'|r);    // really measn 0x30+r but 0<=r<10, so bit-OR would do.
   314             value /= 10;
   315         } while(value!=0);
   317         if(minus)   buf[--idx] = (byte)'-';
   319         write(buf,idx,11-idx);
   320     }
   322     /**
   323      * Writes the given byte[] as base64 encoded binary to the output.
   324      *
   325      * <p>
   326      * Being defined on this class allows this method to access the buffer directly,
   327      * which translates to a better performance.
   328      */
   329     public void text(byte[] data, int dataLen) throws IOException {
   330         closeStartTag();
   332         int start = 0;
   334         while(dataLen>0) {
   335             // how many bytes (in data) can we write without overflowing the buffer?
   336             int batchSize = Math.min(((octetBuffer.length-octetBufferIndex)/4)*3,dataLen);
   338             // write the batch
   339             octetBufferIndex = DatatypeConverterImpl._printBase64Binary(data,start,batchSize,octetBuffer,octetBufferIndex);
   341             if(batchSize<dataLen)
   342                 flushBuffer();
   344             start += batchSize;
   345             dataLen -= batchSize;
   347         }
   348     }
   350 //
   351 //
   352 // series of the write method that places bytes to the output
   353 // (by doing some buffering internal to this class)
   354 //
   356     /**
   357      * Writes one byte directly into the buffer.
   358      *
   359      * <p>
   360      * This method can be used somewhat like the {@code text} method,
   361      * but it doesn't perform character escaping.
   362      */
   363     public final void write(int i) throws IOException {
   364         if (octetBufferIndex < octetBuffer.length) {
   365             octetBuffer[octetBufferIndex++] = (byte)i;
   366         } else {
   367             out.write(octetBuffer);
   368             octetBufferIndex = 1;
   369             octetBuffer[0] = (byte)i;
   370         }
   371     }
   373     protected final void write(byte[] b) throws IOException {
   374         write(b, 0,  b.length);
   375     }
   377     protected final void write(byte[] b, int start, int length) throws IOException {
   378         if ((octetBufferIndex + length) < octetBuffer.length) {
   379             System.arraycopy(b, start, octetBuffer, octetBufferIndex, length);
   380             octetBufferIndex += length;
   381         } else {
   382             out.write(octetBuffer, 0, octetBufferIndex);
   383             out.write(b, start, length);
   384             octetBufferIndex = 0;
   385         }
   386     }
   388     protected final void flushBuffer() throws IOException {
   389         out.write(octetBuffer, 0, octetBufferIndex);
   390         octetBufferIndex = 0;
   391     }
   393     static byte[] toBytes(String s) {
   394         byte[] buf = new byte[s.length()];
   395         for( int i=s.length()-1; i>=0; i-- )
   396             buf[i] = (byte)s.charAt(i);
   397         return buf;
   398     }
   400     // per instance copy to prevent an attack where malicious OutputStream
   401     // rewrites the byte array.
   402     private final byte[] XMLNS_EQUALS = _XMLNS_EQUALS.clone();
   403     private final byte[] XMLNS_COLON = _XMLNS_COLON.clone();
   404     private final byte[] EQUALS = _EQUALS.clone();
   405     private final byte[] CLOSE_TAG = _CLOSE_TAG.clone();
   406     private final byte[] EMPTY_TAG = _EMPTY_TAG.clone();
   407     private final byte[] XML_DECL = _XML_DECL.clone();
   409     // masters
   410     private static final byte[] _XMLNS_EQUALS = toBytes(" xmlns=\"");
   411     private static final byte[] _XMLNS_COLON = toBytes(" xmlns:");
   412     private static final byte[] _EQUALS = toBytes("=\"");
   413     private static final byte[] _CLOSE_TAG = toBytes("</");
   414     private static final byte[] _EMPTY_TAG = toBytes("/>");
   415     private static final byte[] _XML_DECL = toBytes("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
   417     // no need to copy
   418     private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
   419 }

mercurial