blob: 9bff50de2c27d81da13e45522dcc4e93bd0ea3d3 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import org.eclipse.jetty.http.AbstractGenerator;
import org.eclipse.jetty.util.ByteArrayOutputStream2;
import org.eclipse.jetty.util.StringUtil;
/** OutputWriter.
* A writer that can wrap a {@link HttpOutput} stream and provide
* character encodings.
*
* The UTF-8 encoding is done by this class and no additional
* buffers or Writers are used.
* The UTF-8 code was inspired by http://javolution.org
*/
public class HttpWriter extends Writer
{
public static final int MAX_OUTPUT_CHARS = 512;
private static final int WRITE_CONV = 0;
private static final int WRITE_ISO1 = 1;
private static final int WRITE_UTF8 = 2;
final HttpOutput _out;
final AbstractGenerator _generator;
int _writeMode;
int _surrogate;
/* ------------------------------------------------------------ */
public HttpWriter(HttpOutput out)
{
_out=out;
_generator=_out._generator;
_surrogate=0; // AS lastUTF16CodePoint
}
/* ------------------------------------------------------------ */
public void setCharacterEncoding(String encoding)
{
if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
{
_writeMode = WRITE_ISO1;
}
else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
{
_writeMode = WRITE_UTF8;
}
else
{
_writeMode = WRITE_CONV;
if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding))
_out._converter = null; // Set lazily in getConverter()
}
_out._characterEncoding = encoding;
if (_out._bytes==null)
_out._bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);
}
/* ------------------------------------------------------------ */
@Override
public void close() throws IOException
{
_out.close();
}
/* ------------------------------------------------------------ */
@Override
public void flush() throws IOException
{
_out.flush();
}
/* ------------------------------------------------------------ */
@Override
public void write (String s,int offset, int length) throws IOException
{
while (length > MAX_OUTPUT_CHARS)
{
write(s, offset, MAX_OUTPUT_CHARS);
offset += MAX_OUTPUT_CHARS;
length -= MAX_OUTPUT_CHARS;
}
if (_out._chars==null)
{
_out._chars = new char[MAX_OUTPUT_CHARS];
}
char[] chars = _out._chars;
s.getChars(offset, offset + length, chars, 0);
write(chars, 0, length);
}
/* ------------------------------------------------------------ */
@Override
public void write (char[] s,int offset, int length) throws IOException
{
HttpOutput out = _out;
while (length > 0)
{
out._bytes.reset();
int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
switch (_writeMode)
{
case WRITE_CONV:
{
Writer converter=getConverter();
converter.write(s, offset, chars);
converter.flush();
}
break;
case WRITE_ISO1:
{
byte[] buffer=out._bytes.getBuf();
int bytes=out._bytes.getCount();
if (chars>buffer.length-bytes)
chars=buffer.length-bytes;
for (int i = 0; i < chars; i++)
{
int c = s[offset+i];
buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255
}
if (bytes>=0)
out._bytes.setCount(bytes);
break;
}
case WRITE_UTF8:
{
byte[] buffer=out._bytes.getBuf();
int bytes=out._bytes.getCount();
if (bytes+chars>buffer.length)
chars=buffer.length-bytes;
for (int i = 0; i < chars; i++)
{
int code = s[offset+i];
// Do we already have a surrogate?
if(_surrogate==0)
{
// No - is this char code a surrogate?
if(Character.isHighSurrogate((char)code))
{
_surrogate=code; // UCS-?
continue;
}
}
// else handle a low surrogate
else if(Character.isLowSurrogate((char)code))
{
code = Character.toCodePoint((char)_surrogate, (char)code); // UCS-4
}
// else UCS-2
else
{
code=_surrogate; // UCS-2
_surrogate=0; // USED
i--;
}
if ((code & 0xffffff80) == 0)
{
// 1b
if (bytes>=buffer.length)
{
chars=i;
break;
}
buffer[bytes++]=(byte)(code);
}
else
{
if((code&0xfffff800)==0)
{
// 2b
if (bytes+2>buffer.length)
{
chars=i;
break;
}
buffer[bytes++]=(byte)(0xc0|(code>>6));
buffer[bytes++]=(byte)(0x80|(code&0x3f));
}
else if((code&0xffff0000)==0)
{
// 3b
if (bytes+3>buffer.length)
{
chars=i;
break;
}
buffer[bytes++]=(byte)(0xe0|(code>>12));
buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
buffer[bytes++]=(byte)(0x80|(code&0x3f));
}
else if((code&0xff200000)==0)
{
// 4b
if (bytes+4>buffer.length)
{
chars=i;
break;
}
buffer[bytes++]=(byte)(0xf0|(code>>18));
buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
buffer[bytes++]=(byte)(0x80|(code&0x3f));
}
else if((code&0xf4000000)==0)
{
// 5b
if (bytes+5>buffer.length)
{
chars=i;
break;
}
buffer[bytes++]=(byte)(0xf8|(code>>24));
buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
buffer[bytes++]=(byte)(0x80|(code&0x3f));
}
else if((code&0x80000000)==0)
{
// 6b
if (bytes+6>buffer.length)
{
chars=i;
break;
}
buffer[bytes++]=(byte)(0xfc|(code>>30));
buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
buffer[bytes++]=(byte)(0x80|(code&0x3f));
}
else
{
buffer[bytes++]=(byte)('?');
}
_surrogate=0; // USED
if (bytes==buffer.length)
{
chars=i+1;
break;
}
}
}
out._bytes.setCount(bytes);
break;
}
default:
throw new IllegalStateException();
}
out._bytes.writeTo(out);
length-=chars;
offset+=chars;
}
}
/* ------------------------------------------------------------ */
private Writer getConverter() throws IOException
{
if (_out._converter == null)
_out._converter = new OutputStreamWriter(_out._bytes, _out._characterEncoding);
return _out._converter;
}
}