ByteArrayBuilder class to build byte sequences; BasicScheme and DigestScheme optimized to generate less intermediate garbage

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1695156 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2015-08-10 19:44:09 +00:00
parent e52e269dc8
commit d09ae9707d
5 changed files with 495 additions and 47 deletions

View File

@ -0,0 +1,212 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.http.auth.util;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import org.apache.http.Consts;
import org.apache.http.annotation.Immutable;
/**
* Builder class for sequences of bytes.
*
* @since 5.0
*/
@Immutable
public class ByteArrayBuilder {
private CharsetEncoder charsetEncoder;
private ByteBuffer buffer;
public ByteArrayBuilder() {
}
public ByteArrayBuilder(final int initialCapacity) {
this.buffer = ByteBuffer.allocate(initialCapacity);
}
public int capacity() {
return this.buffer != null ? this.buffer.capacity() : 0;
}
static ByteBuffer ensureFreeCapacity(final ByteBuffer buffer, final int capacity) {
if (buffer == null) {
return ByteBuffer.allocate(capacity);
}
if (buffer.remaining() < capacity) {
final ByteBuffer newBuffer = ByteBuffer.allocate(buffer.position() + capacity);
buffer.flip();
newBuffer.put(buffer);
return newBuffer;
} else {
return buffer;
}
}
static ByteBuffer encode(
final ByteBuffer buffer, final CharBuffer in, final CharsetEncoder encoder) throws CharacterCodingException {
final int capacity = (int) (in.remaining() * encoder.averageBytesPerChar());
ByteBuffer out = ensureFreeCapacity(buffer, capacity);
for (;;) {
CoderResult result = in.hasRemaining() ? encoder.encode(in, out, true) : CoderResult.UNDERFLOW;
if (result.isError()) {
result.throwException();
}
if (result.isUnderflow()) {
result = encoder.flush(out);
}
if (result.isUnderflow()) {
break;
}
if (result.isOverflow()) {
out = ensureFreeCapacity(out, capacity);
}
}
return out;
}
public void ensureFreeCapacity(final int freeCapacity) {
this.buffer = ensureFreeCapacity(this.buffer, freeCapacity);
}
private void doAppend(final CharBuffer charBuffer) {
if (this.charsetEncoder == null) {
this.charsetEncoder = Consts.ASCII.newEncoder()
.onMalformedInput(CodingErrorAction.IGNORE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
}
this.charsetEncoder.reset();
try {
this.buffer = encode(this.buffer, charBuffer, this.charsetEncoder);
} catch (CharacterCodingException ex) {
// Should never happen
throw new IllegalStateException("Unexpected character coding error", ex);
}
}
public ByteArrayBuilder charset(final Charset charset) {
if (charset == null) {
this.charsetEncoder = null;
} else {
this.charsetEncoder = charset.newEncoder()
.onMalformedInput(CodingErrorAction.IGNORE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
}
return this;
}
public ByteArrayBuilder append(final byte[] b, final int off, final int len) {
if (b == null) {
return this;
}
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) < 0) || ((off + len) > b.length)) {
throw new IndexOutOfBoundsException("off: " + off + " len: " + len + " b.length: " + b.length);
}
ensureFreeCapacity(len);
this.buffer.put(b, off, len);
return this;
}
public ByteArrayBuilder append(final byte[] b) {
if (b == null) {
return this;
}
return append(b, 0, b.length);
}
public ByteArrayBuilder append(final CharBuffer charBuffer) {
if (charBuffer == null) {
return this;
}
doAppend(charBuffer);
return this;
}
public ByteArrayBuilder append(final char[] b, final int off, final int len) {
if (b == null) {
return this;
}
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) < 0) || ((off + len) > b.length)) {
throw new IndexOutOfBoundsException("off: " + off + " len: " + len + " b.length: " + b.length);
}
return append(CharBuffer.wrap(b, off, len));
}
public ByteArrayBuilder append(final char[] b) {
if (b == null) {
return this;
}
return append(b, 0, b.length);
}
public ByteArrayBuilder append(final String s) {
if (s == null) {
return this;
}
return append(CharBuffer.wrap(s));
}
public ByteBuffer toByteBuffer() {
return this.buffer != null ? this.buffer.duplicate() : ByteBuffer.allocate(0);
}
public byte[] toByteArray() {
if (this.buffer == null) {
return new byte[] {};
} else {
this.buffer.flip();
final byte[] b = new byte[this.buffer.remaining()];
this.buffer.get(b);
this.buffer.clear();
return b;
}
}
public void reset() {
if (this.charsetEncoder != null) {
this.charsetEncoder.reset();
}
if (this.buffer != null) {
this.buffer.clear();
}
}
@Override
public String toString() {
return this.buffer != null ? this.buffer.toString() : "null";
}
}

View File

@ -51,10 +51,10 @@
import org.apache.http.auth.Credentials; import org.apache.http.auth.Credentials;
import org.apache.http.auth.CredentialsProvider; import org.apache.http.auth.CredentialsProvider;
import org.apache.http.auth.MalformedChallengeException; import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.auth.util.ByteArrayBuilder;
import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Args; import org.apache.http.util.Args;
import org.apache.http.util.CharsetUtils; import org.apache.http.util.CharsetUtils;
import org.apache.http.util.EncodingUtils;
/** /**
* Basic authentication scheme as defined in RFC 2617. * Basic authentication scheme as defined in RFC 2617.
@ -68,7 +68,10 @@ public class BasicScheme implements AuthScheme, Serializable {
private final Map<String, String> paramMap; private final Map<String, String> paramMap;
private transient Charset charset; private transient Charset charset;
private transient ByteArrayBuilder buffer;
private transient Base64 base64codec;
private boolean complete; private boolean complete;
private String username; private String username;
private String password; private String password;
@ -160,12 +163,17 @@ public String generateAuthResponse(
final HttpHost host, final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpContext context) throws AuthenticationException { final HttpContext context) throws AuthenticationException {
final StringBuilder buffer = new StringBuilder(); if (this.buffer == null) {
buffer.append(this.username); this.buffer = new ByteArrayBuilder(64).charset(this.charset);
buffer.append(":"); } else {
buffer.append(this.password); this.buffer.reset();
final Base64 base64codec = new Base64(0); }
final byte[] encodedCreds = base64codec.encode(EncodingUtils.getBytes(buffer.toString(), charset.name())); this.buffer.append(this.username).append(":").append(this.password);
if (this.base64codec == null) {
this.base64codec = new Base64(0);
}
final byte[] encodedCreds = this.base64codec.encode(this.buffer.toByteArray());
this.buffer.reset();
return "Basic " + new String(encodedCreds, 0, encodedCreds.length, Consts.ASCII); return "Basic " + new String(encodedCreds, 0, encodedCreds.length, Consts.ASCII);
} }

View File

@ -28,6 +28,7 @@
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.nio.charset.Charset;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.Principal; import java.security.Principal;
import java.security.SecureRandom; import java.security.SecureRandom;
@ -41,6 +42,7 @@
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import org.apache.http.Consts;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
@ -54,12 +56,13 @@
import org.apache.http.auth.Credentials; import org.apache.http.auth.Credentials;
import org.apache.http.auth.CredentialsProvider; import org.apache.http.auth.CredentialsProvider;
import org.apache.http.auth.MalformedChallengeException; import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.auth.util.ByteArrayBuilder;
import org.apache.http.message.BasicHeaderValueFormatter; import org.apache.http.message.BasicHeaderValueFormatter;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Args; import org.apache.http.util.Args;
import org.apache.http.util.CharArrayBuffer; import org.apache.http.util.CharArrayBuffer;
import org.apache.http.util.EncodingUtils; import org.apache.http.util.CharsetUtils;
/** /**
* Digest authentication scheme as defined in RFC 2617. * Digest authentication scheme as defined in RFC 2617.
@ -84,7 +87,7 @@ public class DigestScheme implements AuthScheme, Serializable {
* Hexa values used when creating 32 character long digest in HTTP DigestScheme * Hexa values used when creating 32 character long digest in HTTP DigestScheme
* in case of authentication. * in case of authentication.
* *
* @see #encode(byte[]) * @see #formatHex(byte[])
*/ */
private static final char[] HEXADECIMAL = { private static final char[] HEXADECIMAL = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
@ -98,11 +101,13 @@ public class DigestScheme implements AuthScheme, Serializable {
private final Map<String, String> paramMap; private final Map<String, String> paramMap;
private boolean complete; private boolean complete;
private transient ByteArrayBuilder buffer;
private String lastNonce; private String lastNonce;
private long nounceCount; private long nounceCount;
private String cnonce; private String cnonce;
private String a1; private byte[] a1;
private String a2; private byte[] a2;
private String username; private String username;
private String password; private String password;
@ -251,9 +256,10 @@ private String createDigestResponse(final HttpRequest request) throws Authentica
throw new AuthenticationException("None of the qop methods is supported: " + qoplist); throw new AuthenticationException("None of the qop methods is supported: " + qoplist);
} }
String charset = this.paramMap.get("charset"); final String charsetName = this.paramMap.get("charset");
Charset charset = charsetName != null ? CharsetUtils.lookup(charsetName) : null;
if (charset == null) { if (charset == null) {
charset = "ISO-8859-1"; charset = Consts.ISO_8859_1;
} }
String digAlg = algorithm; String digAlg = algorithm;
@ -275,16 +281,24 @@ private String createDigestResponse(final HttpRequest request) throws Authentica
cnonce = null; cnonce = null;
lastNonce = nonce; lastNonce = nonce;
} }
final StringBuilder sb = new StringBuilder(256);
final StringBuilder sb = new StringBuilder(8);
final Formatter formatter = new Formatter(sb, Locale.US); final Formatter formatter = new Formatter(sb, Locale.US);
formatter.format("%08x", Long.valueOf(nounceCount)); formatter.format("%08x", nounceCount);
formatter.close(); formatter.close();
final String nc = sb.toString(); final String nc = sb.toString();
if (cnonce == null) { if (cnonce == null) {
cnonce = createCnonce(); cnonce = formatHex(createCnonce());
} }
if (buffer == null) {
buffer = new ByteArrayBuilder();
} else {
buffer.reset();
}
buffer.charset(charset);
a1 = null; a1 = null;
a2 = null; a2 = null;
// 3.2.2.2: Calculating digest // 3.2.2.2: Calculating digest
@ -294,24 +308,23 @@ private String createDigestResponse(final HttpRequest request) throws Authentica
// ":" unq(cnonce-value) // ":" unq(cnonce-value)
// calculated one per session // calculated one per session
sb.setLength(0); buffer.append(username).append(":").append(realm).append(":").append(password);
sb.append(username).append(':').append(realm).append(':').append(password); final String checksum = formatHex(digester.digest(this.buffer.toByteArray()));
final String checksum = encode(digester.digest(EncodingUtils.getBytes(sb.toString(), charset))); buffer.reset();
sb.setLength(0); buffer.append(checksum).append(":").append(nonce).append(":").append(cnonce);
sb.append(checksum).append(':').append(nonce).append(':').append(cnonce); a1 = buffer.toByteArray();
a1 = sb.toString();
} else { } else {
// unq(username-value) ":" unq(realm-value) ":" passwd // unq(username-value) ":" unq(realm-value) ":" passwd
sb.setLength(0); buffer.append(username).append(":").append(realm).append(":").append(password);
sb.append(username).append(':').append(realm).append(':').append(password); a1 = buffer.toByteArray();
a1 = sb.toString();
} }
final String hasha1 = encode(digester.digest(EncodingUtils.getBytes(a1, charset))); final String hasha1 = formatHex(digester.digest(a1));
buffer.reset();
if (qop == QOP_AUTH) { if (qop == QOP_AUTH) {
// Method ":" digest-uri-value // Method ":" digest-uri-value
a2 = method + ':' + uri; a2 = buffer.append(method).append(":").append(uri).toByteArray();
} else if (qop == QOP_AUTH_INT) { } else if (qop == QOP_AUTH_INT) {
// Method ":" digest-uri-value ":" H(entity-body) // Method ":" digest-uri-value ":" H(entity-body)
HttpEntity entity = null; HttpEntity entity = null;
@ -322,7 +335,7 @@ private String createDigestResponse(final HttpRequest request) throws Authentica
// If the entity is not repeatable, try falling back onto QOP_AUTH // If the entity is not repeatable, try falling back onto QOP_AUTH
if (qopset.contains("auth")) { if (qopset.contains("auth")) {
qop = QOP_AUTH; qop = QOP_AUTH;
a2 = method + ':' + uri; a2 = buffer.append(method).append(":").append(uri).toByteArray();
} else { } else {
throw new AuthenticationException("Qop auth-int cannot be used with " + throw new AuthenticationException("Qop auth-int cannot be used with " +
"a non-repeatable entity"); "a non-repeatable entity");
@ -337,30 +350,31 @@ private String createDigestResponse(final HttpRequest request) throws Authentica
} catch (final IOException ex) { } catch (final IOException ex) {
throw new AuthenticationException("I/O error reading entity content", ex); throw new AuthenticationException("I/O error reading entity content", ex);
} }
a2 = method + ':' + uri + ':' + encode(entityDigester.getDigest()); a2 = buffer.append(method).append(":").append(uri)
.append(":").append(formatHex(entityDigester.getDigest())).toByteArray();
} }
} else { } else {
a2 = method + ':' + uri; a2 = buffer.append(method).append(":").append(uri).toByteArray();
} }
final String hasha2 = encode(digester.digest(EncodingUtils.getBytes(a2, charset))); final String hasha2 = formatHex(digester.digest(a2));
buffer.reset();
// 3.2.2.1 // 3.2.2.1
final String digestValue; final byte[] digestInput;
if (qop == QOP_MISSING) { if (qop == QOP_MISSING) {
sb.setLength(0); buffer.append(hasha1).append(":").append(nonce).append(":").append(hasha2);
sb.append(hasha1).append(':').append(nonce).append(':').append(hasha2); digestInput = buffer.toByteArray();
digestValue = sb.toString();
} else { } else {
sb.setLength(0); buffer.append(hasha1).append(":").append(nonce).append(":").append(nc).append(":")
sb.append(hasha1).append(':').append(nonce).append(':').append(nc).append(':') .append(cnonce).append(":").append(qop == QOP_AUTH_INT ? "auth-int" : "auth")
.append(cnonce).append(':').append(qop == QOP_AUTH_INT ? "auth-int" : "auth") .append(":").append(hasha2);
.append(':').append(hasha2); digestInput = buffer.toByteArray();
digestValue = sb.toString();
} }
buffer.reset();
final String digest = encode(digester.digest(EncodingUtils.getAsciiBytes(digestValue))); final String digest = formatHex(digester.digest(digestInput));
final CharArrayBuffer buffer = new CharArrayBuffer(128); final CharArrayBuffer buffer = new CharArrayBuffer(128);
buffer.append("Digest "); buffer.append("Digest ");
@ -401,11 +415,11 @@ String getCnonce() {
} }
String getA1() { String getA1() {
return a1; return a1 != null ? new String(a1, Consts.ASCII) : null;
} }
String getA2() { String getA2() {
return a2; return a2 != null ? new String(a2, Consts.ASCII) : null;
} }
/** /**
@ -415,7 +429,7 @@ String getA2() {
* @param binaryData array containing the digest * @param binaryData array containing the digest
* @return encoded MD5, or <CODE>null</CODE> if encoding failed * @return encoded MD5, or <CODE>null</CODE> if encoding failed
*/ */
static String encode(final byte[] binaryData) { static String formatHex(final byte[] binaryData) {
final int n = binaryData.length; final int n = binaryData.length;
final char[] buffer = new char[n * 2]; final char[] buffer = new char[n * 2];
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
@ -433,11 +447,11 @@ static String encode(final byte[] binaryData) {
* *
* @return The cnonce value as String. * @return The cnonce value as String.
*/ */
public static String createCnonce() { static byte[] createCnonce() {
final SecureRandom rnd = new SecureRandom(); final SecureRandom rnd = new SecureRandom();
final byte[] tmp = new byte[8]; final byte[] tmp = new byte[8];
rnd.nextBytes(tmp); rnd.nextBytes(tmp);
return encode(tmp); return tmp;
} }
@Override @Override

View File

@ -0,0 +1,214 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.http.auth.util;
import java.nio.ByteBuffer;
import org.apache.http.Consts;
import org.junit.Assert;
import org.junit.Test;
/**
* {@link ByteArrayBuilder} test cases.
*/
public class TestByteArrayBuilder {
@Test
public void testEmptyBuffer() throws Exception {
final ByteArrayBuilder buffer = new ByteArrayBuilder();
final ByteBuffer byteBuffer = buffer.toByteBuffer();
Assert.assertNotNull(byteBuffer);
Assert.assertEquals(0, byteBuffer.capacity());
final byte[] bytes = buffer.toByteArray();
Assert.assertNotNull(bytes);
Assert.assertEquals(0, bytes.length);
}
@Test
public void testAppendBytes() throws Exception {
final ByteArrayBuilder buffer = new ByteArrayBuilder();
buffer.append(new byte[]{1, 2, 3, 4, 5});
buffer.append(new byte[]{3, 4, 5, 6, 7, 8, 9, 10, 11}, 3, 5);
buffer.append((byte[]) null);
final byte[] bytes = buffer.toByteArray();
Assert.assertNotNull(bytes);
Assert.assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, bytes);
}
@Test
public void testInvalidAppendBytes() throws Exception {
final ByteArrayBuilder buffer = new ByteArrayBuilder();
buffer.append((byte[])null, 0, 0);
final byte[] tmp = new byte[] { 1, 2, 3, 4};
try {
buffer.append(tmp, -1, 0);
Assert.fail("IndexOutOfBoundsException should have been thrown");
} catch (final IndexOutOfBoundsException ex) {
// expected
}
try {
buffer.append(tmp, 0, -1);
Assert.fail("IndexOutOfBoundsException should have been thrown");
} catch (final IndexOutOfBoundsException ex) {
// expected
}
try {
buffer.append(tmp, 0, 8);
Assert.fail("IndexOutOfBoundsException should have been thrown");
} catch (final IndexOutOfBoundsException ex) {
// expected
}
try {
buffer.append(tmp, 10, Integer.MAX_VALUE);
Assert.fail("IndexOutOfBoundsException should have been thrown");
} catch (final IndexOutOfBoundsException ex) {
// expected
}
try {
buffer.append(tmp, 2, 4);
Assert.fail("IndexOutOfBoundsException should have been thrown");
} catch (final IndexOutOfBoundsException ex) {
// expected
}
}
@Test
public void testEnsureCapacity() throws Exception {
final ByteArrayBuilder buffer = new ByteArrayBuilder();
buffer.ensureFreeCapacity(10);
Assert.assertEquals(10, buffer.capacity());
buffer.ensureFreeCapacity(5);
Assert.assertEquals(10, buffer.capacity());
buffer.append(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
buffer.ensureFreeCapacity(5);
Assert.assertEquals(13, buffer.capacity());
buffer.ensureFreeCapacity(15);
Assert.assertEquals(23, buffer.capacity());
}
@Test
public void testAppendText() throws Exception {
final ByteArrayBuilder buffer = new ByteArrayBuilder();
buffer.append(new char[]{'1', '2', '3', '4', '5'});
buffer.append(new char[]{'3', '4', '5', '6', '7', '8', '9', 'a', 'b'}, 3, 5);
buffer.append("bcd");
buffer.append("e");
buffer.append("f");
buffer.append((String) null);
buffer.append((char[]) null);
final byte[] bytes = buffer.toByteArray();
Assert.assertNotNull(bytes);
Assert.assertEquals("123456789abcdef", new String(bytes, Consts.ASCII));
}
@Test
public void testInvalidAppendChars() throws Exception {
final ByteArrayBuilder buffer = new ByteArrayBuilder();
buffer.append((char[])null, 0, 0);
final char[] tmp = new char[] { 1, 2, 3, 4};
try {
buffer.append(tmp, -1, 0);
Assert.fail("IndexOutOfBoundsException should have been thrown");
} catch (final IndexOutOfBoundsException ex) {
// expected
}
try {
buffer.append(tmp, 0, -1);
Assert.fail("IndexOutOfBoundsException should have been thrown");
} catch (final IndexOutOfBoundsException ex) {
// expected
}
try {
buffer.append(tmp, 0, 8);
Assert.fail("IndexOutOfBoundsException should have been thrown");
} catch (final IndexOutOfBoundsException ex) {
// expected
}
try {
buffer.append(tmp, 10, Integer.MAX_VALUE);
Assert.fail("IndexOutOfBoundsException should have been thrown");
} catch (final IndexOutOfBoundsException ex) {
// expected
}
try {
buffer.append(tmp, 2, 4);
Assert.fail("IndexOutOfBoundsException should have been thrown");
} catch (final IndexOutOfBoundsException ex) {
// expected
}
}
@Test
public void testReset() throws Exception {
final ByteArrayBuilder buffer = new ByteArrayBuilder();
buffer.append("abcd");
buffer.append("e");
buffer.append("f");
final byte[] bytes1 = buffer.toByteArray();
Assert.assertNotNull(bytes1);
Assert.assertEquals("abcdef", new String(bytes1, Consts.ASCII));
buffer.reset();
final byte[] bytes2 = buffer.toByteArray();
Assert.assertNotNull(bytes2);
Assert.assertEquals("", new String(bytes2, Consts.ASCII));
}
@Test
public void testNonAsciiCharset() throws Exception {
final int[] germanChars = { 0xE4, 0x2D, 0xF6, 0x2D, 0xFc };
final StringBuilder tmp = new StringBuilder();
for (final int germanChar : germanChars) {
tmp.append((char) germanChar);
}
final String umlauts = tmp.toString();
final ByteArrayBuilder buffer = new ByteArrayBuilder();
buffer.append(umlauts);
final byte[] bytes1 = buffer.toByteArray();
Assert.assertNotNull(bytes1);
Assert.assertEquals("?-?-?", new String(bytes1, Consts.ASCII));
buffer.reset();
buffer.charset(Consts.UTF_8);
buffer.append(umlauts);
final byte[] bytes2 = buffer.toByteArray();
Assert.assertNotNull(bytes2);
Assert.assertEquals(umlauts, new String(bytes2, Consts.UTF_8));
}
}

View File

@ -567,7 +567,7 @@ public void testHttpEntityDigest() throws Exception {
digester.write(new byte[] { 'a', 'b', 'c'}); digester.write(new byte[] { 'a', 'b', 'c'});
Assert.assertNull(digester.getDigest()); Assert.assertNull(digester.getDigest());
digester.close(); digester.close();
Assert.assertEquals("acd2b59cd01c7737d8069015584c6cac", DigestScheme.encode(digester.getDigest())); Assert.assertEquals("acd2b59cd01c7737d8069015584c6cac", DigestScheme.formatHex(digester.getDigest()));
try { try {
digester.write('a'); digester.write('a');
Assert.fail("IOException should have been thrown"); Assert.fail("IOException should have been thrown");