This commit is contained in:
jinyu 2018-02-04 14:48:10 +08:00
parent f0dfffe9bb
commit 3779c33046
37 changed files with 2469 additions and 1151 deletions

View File

@ -2,10 +2,17 @@ package com.foxinmy.weixin4j.http;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import com.foxinmy.weixin4j.util.CharArrayBuffer;
import com.foxinmy.weixin4j.util.Consts;
import com.foxinmy.weixin4j.util.NameValue;
import com.foxinmy.weixin4j.util.StringUtil;
/**
* reference of apache pivot
@ -22,6 +29,7 @@ public final class ContentType implements Serializable {
private final MimeType mimeType;
private final Charset charset;
private final NameValue[] params;
private static final Charset DEFAULT_CHARSET = Consts.UTF_8;
public static final ContentType APPLICATION_JSON;
@ -32,7 +40,8 @@ public final class ContentType implements Serializable {
static {
APPLICATION_JSON = new ContentType(MimeType.APPLICATION_JSON);
APPLICATION_FORM_URLENCODED = new ContentType(MimeType.APPLICATION_FORM_URLENCODED);
APPLICATION_FORM_URLENCODED = new ContentType(
MimeType.APPLICATION_FORM_URLENCODED);
MULTIPART_FORM_DATA = new ContentType(MimeType.MULTIPART_FORM_DATA);
DEFAULT_BINARY = new ContentType(MimeType.APPLICATION_OCTET_STREAM);
DEFAULT_TEXT = new ContentType(MimeType.TEXT_PLAIN);
@ -43,8 +52,14 @@ public final class ContentType implements Serializable {
}
ContentType(final MimeType mimeType, final Charset charset) {
this(mimeType, charset, null);
}
ContentType(final MimeType mimeType, final Charset charset,
final NameValue[] params) {
this.mimeType = mimeType;
this.charset = charset;
this.params = params;
}
public MimeType getMimeType() {
@ -55,11 +70,34 @@ public final class ContentType implements Serializable {
return this.charset;
}
/**
* @since 4.3
*/
public String getParameter(final String name) {
if (this.params == null) {
return null;
}
for (final NameValue param : this.params) {
if (param.getName().equalsIgnoreCase(name)) {
return param.getValue();
}
}
return null;
}
/**
* Generates textual representation of this content type which can be used
* as the value of a {@code Content-Type} header.
*/
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(this.mimeType.toString());
if (this.charset != null) {
final CharArrayBuffer buf = new CharArrayBuffer(64);
buf.append(this.mimeType);
if (this.params != null) {
buf.append("; ");
HeaderValueFormatter.INSTANCE.formatParameters(buf,
this.params, false);
} else if (this.charset != null) {
buf.append("; charset=");
buf.append(this.charset.name());
}
@ -87,7 +125,8 @@ public final class ContentType implements Serializable {
return true;
}
public static ContentType create(final MimeType mimeType, final Charset charset) {
public static ContentType create(final MimeType mimeType,
final Charset charset) {
if (mimeType == null) {
throw new IllegalArgumentException("MIME type may not be null");
}
@ -98,7 +137,16 @@ public final class ContentType implements Serializable {
return create(MimeType.valueOf(mimeType), (Charset) null);
}
public static ContentType create(final String mimeType, final Charset charset) {
public static ContentType create(final String mimeType, final String charset)
throws UnsupportedCharsetException {
return create(
mimeType,
(charset != null && charset.length() > 0) ? Charset
.forName(charset) : null);
}
public static ContentType create(final String mimeType,
final Charset charset) {
if (mimeType == null) {
throw new IllegalArgumentException("MIME type may not be null");
}
@ -107,8 +155,84 @@ public final class ContentType implements Serializable {
throw new IllegalArgumentException("MIME type may not be empty");
}
if (!valid(type)) {
throw new IllegalArgumentException("MIME type may not contain reserved characters");
throw new IllegalArgumentException(
"MIME type may not contain reserved characters");
}
return new ContentType(MimeType.valueOf(type), charset);
}
private static ContentType create(final MimeType mimeType,
final NameValue[] params, final boolean strict) {
Charset charset = null;
for (final NameValue param : params) {
if (param.getName().equalsIgnoreCase("charset")) {
final String s = param.getValue();
if (StringUtil.isNotBlank(s)) {
try {
charset = Charset.forName(s);
} catch (final UnsupportedCharsetException ex) {
if (strict) {
throw ex;
}
}
}
break;
}
}
return new ContentType(mimeType, charset, params != null
&& params.length > 0 ? params : null);
}
/**
* Creates a new instance of {@link ContentType} with the given parameters.
*
* @param mimeType MIME type. It may not be {@code null} or empty. It may not contain
* characters {@code <">, <;>, <,>} reserved by the HTTP specification.
* @param params parameters.
* @return content type
*
* @since 4.4
*/
public static ContentType create(final String mimeType,
final NameValue... params) throws UnsupportedCharsetException {
final String type = mimeType.toLowerCase(Locale.ROOT);
if (!valid(type)) {
throw new IllegalArgumentException(
"MIME type may not contain reserved characters");
}
return create(MimeType.valueOf(mimeType), params, true);
}
/**
* Creates a new instance with this MIME type and the given parameters.
*
* @param params
* @return a new instance with this MIME type and the given parameters.
* @since 4.4
*/
public ContentType withParameters(final NameValue... params)
throws UnsupportedCharsetException {
if (params.length == 0) {
return this;
}
final Map<String, String> paramMap = new LinkedHashMap<String, String>();
if (this.params != null) {
for (final NameValue param : this.params) {
paramMap.put(param.getName(), param.getValue());
}
}
for (final NameValue param : params) {
paramMap.put(param.getName(), param.getValue());
}
final List<NameValue> newParams = new ArrayList<NameValue>(
paramMap.size() + 1);
if (this.charset != null && !paramMap.containsKey("charset")) {
newParams.add(new NameValue("charset", this.charset.name()));
}
for (final Map.Entry<String, String> entry : paramMap.entrySet()) {
newParams.add(new NameValue(entry.getKey(), entry.getValue()));
}
return create(this.getMimeType(),
newParams.toArray(new NameValue[newParams.size()]), true);
}
}

View File

@ -25,8 +25,11 @@
*
*/
package com.foxinmy.weixin4j.http.apache;
package com.foxinmy.weixin4j.http;
import java.nio.charset.Charset;
import org.apache.http.Consts;
/**
* Constants and static helpers related to the HTTP protocol.
@ -63,11 +66,52 @@ public final class HTTP {
public static final String CHUNK_CODING = "chunked";
public static final String IDENTITY_CODING = "identity";
public static boolean isWhitespace(char ch) {
public static final Charset DEF_CONTENT_CHARSET = Consts.ISO_8859_1;
public static final Charset DEF_PROTOCOL_CHARSET = Consts.ASCII;
/**
*/
public static final String UTF_8 = "UTF-8";
/**
*/
public static final String UTF_16 = "UTF-16";
/**
*/
public static final String US_ASCII = "US-ASCII";
/**
*/
public static final String ASCII = "ASCII";
/**
*/
public static final String DEFAULT_CONTENT_CHARSET = UTF_8;
/**
*/
public static final String DEFAULT_PROTOCOL_CHARSET = US_ASCII;
/**
*/
public final static String OCTET_STREAM_TYPE = "application/octet-stream";
/**
*/
public final static String PLAIN_TEXT_TYPE = "text/plain";
/**
*/
public final static String CHARSET_PARAM = "; charset=";
/**
*/
public final static String DEFAULT_CONTENT_TYPE = OCTET_STREAM_TYPE;
public static boolean isWhitespace(final char ch) {
return ch == SP || ch == HT || ch == CR || ch == LF;
}
private HTTP() {
}
}

View File

@ -0,0 +1,253 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http;
import com.foxinmy.weixin4j.util.CharArrayBuffer;
import com.foxinmy.weixin4j.util.NameValue;
/**
* Basic implementation for formatting header value elements.
* Instances of this class are stateless and thread-safe.
* Derived classes are expected to maintain these properties.
*
* @since 4.0
*/
public class HeaderValueFormatter {
public final static HeaderValueFormatter INSTANCE = new HeaderValueFormatter();
/**
* Special characters that can be used as separators in HTTP parameters.
* These special characters MUST be in a quoted string to be used within
* a parameter value .
*/
public final static String SEPARATORS = " ;,:@()<>\\\"/[]?={}\t";
/**
* Unsafe special characters that must be escaped using the backslash
* character
*/
public final static String UNSAFE_CHARS = "\"\\";
public HeaderValueFormatter() {
super();
}
/**
* Formats a set of parameters.
*
* @param nvps the parameters to format
* @param quote <code>true</code> to always format with quoted values,
* <code>false</code> to use quotes only when necessary
* @param formatter the formatter to use, or <code>null</code>
* for the {@link #INSTANCE default}
*
* @return the formatted parameters
*/
public static
String formatParameters(final NameValue[] nvps,
final boolean quote,
final HeaderValueFormatter formatter) {
return (formatter != null ? formatter : HeaderValueFormatter.INSTANCE)
.formatParameters(null, nvps, quote).toString();
}
// non-javadoc, see interface HeaderValueFormatter
public CharArrayBuffer formatParameters(final CharArrayBuffer charBuffer,
final NameValue[] nvps,
final boolean quote) {
final int len = estimateParametersLen(nvps);
CharArrayBuffer buffer = charBuffer;
if (buffer == null) {
buffer = new CharArrayBuffer(len);
} else {
buffer.ensureCapacity(len);
}
for (int i = 0; i < nvps.length; i++) {
if (i > 0) {
buffer.append("; ");
}
formatNameValuePair(buffer, nvps[i], quote);
}
return buffer;
}
/**
* Estimates the length of formatted parameters.
*
* @param nvps the parameters to format, or <code>null</code>
*
* @return a length estimate, in number of characters
*/
protected int estimateParametersLen(final NameValue[] nvps) {
if ((nvps == null) || (nvps.length < 1)) {
return 0;
}
int result = (nvps.length-1) * 2; // "; " between the parameters
for (final NameValue nvp : nvps) {
result += estimateNameValuePairLen(nvp);
}
return result;
}
/**
* Formats a name-value pair.
*
* @param nvp the name-value pair to format
* @param quote <code>true</code> to always format with a quoted value,
* <code>false</code> to use quotes only when necessary
* @param formatter the formatter to use, or <code>null</code>
* for the {@link #INSTANCE default}
*
* @return the formatted name-value pair
*/
public static
String formatNameValuePair(final NameValue nvp,
final boolean quote,
final HeaderValueFormatter formatter) {
return (formatter != null ? formatter : HeaderValueFormatter.INSTANCE)
.formatNameValuePair(null, nvp, quote).toString();
}
// non-javadoc, see interface HeaderValueFormatter
public CharArrayBuffer formatNameValuePair(final CharArrayBuffer charBuffer,
final NameValue nvp,
final boolean quote) {
final int len = estimateNameValuePairLen(nvp);
CharArrayBuffer buffer = charBuffer;
if (buffer == null) {
buffer = new CharArrayBuffer(len);
} else {
buffer.ensureCapacity(len);
}
buffer.append(nvp.getName());
final String value = nvp.getValue();
if (value != null) {
buffer.append('=');
doFormatValue(buffer, value, quote);
}
return buffer;
}
/**
* Estimates the length of a formatted name-value pair.
*
* @param nvp the name-value pair to format, or <code>null</code>
*
* @return a length estimate, in number of characters
*/
protected int estimateNameValuePairLen(final NameValue nvp) {
if (nvp == null) {
return 0;
}
int result = nvp.getName().length(); // name
final String value = nvp.getValue();
if (value != null) {
// assume quotes, but no escaped characters
result += 3 + value.length(); // ="value"
}
return result;
}
/**
* Actually formats the value of a name-value pair.
* This does not include a leading = character.
* Called from {@link #formatNameValuePair formatNameValuePair}.
*
* @param buffer the buffer to append to, never <code>null</code>
* @param value the value to append, never <code>null</code>
* @param quote <code>true</code> to always format with quotes,
* <code>false</code> to use quotes only when necessary
*/
protected void doFormatValue(final CharArrayBuffer buffer,
final String value,
final boolean quote) {
boolean quoteFlag = quote;
if (!quoteFlag) {
for (int i = 0; (i < value.length()) && !quoteFlag; i++) {
quoteFlag = isSeparator(value.charAt(i));
}
}
if (quoteFlag) {
buffer.append('"');
}
for (int i = 0; i < value.length(); i++) {
final char ch = value.charAt(i);
if (isUnsafe(ch)) {
buffer.append('\\');
}
buffer.append(ch);
}
if (quoteFlag) {
buffer.append('"');
}
}
/**
* Checks whether a character is a {@link #SEPARATORS separator}.
*
* @param ch the character to check
*
* @return <code>true</code> if the character is a separator,
* <code>false</code> otherwise
*/
protected boolean isSeparator(final char ch) {
return SEPARATORS.indexOf(ch) >= 0;
}
/**
* Checks whether a character is {@link #UNSAFE_CHARS unsafe}.
*
* @param ch the character to check
*
* @return <code>true</code> if the character is unsafe,
* <code>false</code> otherwise
*/
protected boolean isUnsafe(final char ch) {
return UNSAFE_CHARS.indexOf(ch) >= 0;
}
} // class BasicHeaderValueFormatter

View File

@ -1,89 +0,0 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache;
/**
* Represents common content properties.
*/
public interface ContentDescriptor {
/**
* Returns the body descriptors MIME type.
* @see #getMediaType()
* @see #getSubType()
* @return The MIME type, which has been parsed from the
* content-type definition. Must not be null, but
* "text/plain", if no content-type was specified.
*/
String getMimeType();
/**
* Gets the defaulted MIME media type for this content.
* For example <code>TEXT</code>, <code>IMAGE</code>, <code>MULTIPART</code>
* @see #getMimeType()
* @return the MIME media type when content-type specified,
* otherwise the correct default (<code>TEXT</code>)
*/
String getMediaType();
/**
* Gets the defaulted MIME sub type for this content.
* @see #getMimeType()
* @return the MIME media type when content-type is specified,
* otherwise the correct default (<code>PLAIN</code>)
*/
String getSubType();
/**
* <p>The body descriptors character set, defaulted appropriately for the MIME type.</p>
* <p>
* For <code>TEXT</code> types, this will be defaulted to <code>us-ascii</code>.
* For other types, when the charset parameter is missing this property will be null.
* </p>
* @return Character set, which has been parsed from the
* content-type definition. Not null for <code>TEXT</code> types, when unset will
* be set to default <code>us-ascii</code>. For other types, when unset,
* null will be returned.
*/
String getCharset();
/**
* Returns the body descriptors transfer encoding.
* @return The transfer encoding. Must not be null, but "7bit",
* if no transfer-encoding was specified.
*/
String getTransferEncoding();
/**
* Returns the body descriptors content-length.
* @return Content length, if known, or -1, to indicate the absence of a
* content-length header.
*/
long getContentLength();
}

View File

@ -1,146 +0,0 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import com.foxinmy.weixin4j.util.NameValue;
/**
* The header of an entity (see RFC 2045).
*/
public class Header implements Iterable<NameValue> {
private final List<NameValue> fields;
private final Map<String, List<NameValue>> fieldMap;
public Header() {
super();
this.fields = new LinkedList<NameValue>();
this.fieldMap = new HashMap<String, List<NameValue>>();
}
public void addField(final NameValue field) {
if (field == null) {
return;
}
String key = field.getName().toLowerCase(Locale.US);
List<NameValue> values = this.fieldMap.get(key);
if (values == null) {
values = new LinkedList<NameValue>();
this.fieldMap.put(key, values);
}
values.add(field);
this.fields.add(field);
}
public List<NameValue> getFields() {
return new ArrayList<NameValue>(this.fields);
}
public NameValue getField(final String name) {
if (name == null) {
return null;
}
String key = name.toLowerCase(Locale.US);
List<NameValue> list = this.fieldMap.get(key);
if (list != null && !list.isEmpty()) {
return list.get(0);
}
return null;
}
public List<NameValue> getFields(final String name) {
if (name == null) {
return null;
}
String key = name.toLowerCase(Locale.US);
List<NameValue> list = this.fieldMap.get(key);
if (list == null || list.isEmpty()) {
return Collections.emptyList();
} else {
return new ArrayList<NameValue>(list);
}
}
public int removeFields(final String name) {
if (name == null) {
return 0;
}
String key = name.toLowerCase(Locale.US);
List<NameValue> removed = fieldMap.remove(key);
if (removed == null || removed.isEmpty()) {
return 0;
}
this.fields.removeAll(removed);
return removed.size();
}
public void setField(final NameValue field) {
if (field == null) {
return;
}
String key = field.getName().toLowerCase(Locale.US);
List<NameValue> list = fieldMap.get(key);
if (list == null || list.isEmpty()) {
addField(field);
return;
}
list.clear();
list.add(field);
int firstOccurrence = -1;
int index = 0;
for (Iterator<NameValue> it = this.fields.iterator(); it.hasNext(); index++) {
NameValue f = it.next();
if (f.getName().equalsIgnoreCase(field.getName())) {
it.remove();
if (firstOccurrence == -1) {
firstOccurrence = index;
}
}
}
this.fields.add(firstOccurrence, field);
}
public Iterator<NameValue> iterator() {
return Collections.unmodifiableList(fields).iterator();
}
@Override
public String toString() {
return this.fields.toString();
}
}

View File

@ -1,278 +0,0 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import com.foxinmy.weixin4j.util.NameValue;
/**
* HttpMultipart represents a collection of MIME multipart encoded content
* bodies. This class is capable of operating either in the strict (RFC 822, RFC
* 2045, RFC 2046 compliant) or the browser compatible modes.
*
* @since 4.0
*/
public class HttpMultipart {
private static ByteArrayBuffer encode(final Charset charset,
final String string) {
ByteBuffer encoded = charset.encode(CharBuffer.wrap(string));
ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining());
bab.append(encoded.array(), encoded.position(), encoded.remaining());
return bab;
}
private static void writeBytes(final ByteArrayBuffer b,
final OutputStream out) throws IOException {
out.write(b.buffer(), 0, b.length());
}
private static void writeBytes(final String s, final Charset charset,
final OutputStream out) throws IOException {
ByteArrayBuffer b = encode(charset, s);
writeBytes(b, out);
}
private static void writeBytes(final String s, final OutputStream out)
throws IOException {
ByteArrayBuffer b = encode(MIME.DEFAULT_CHARSET, s);
writeBytes(b, out);
}
private static void writeField(final NameValue field, final OutputStream out)
throws IOException {
writeBytes(field.getName(), out);
writeBytes(FIELD_SEP, out);
writeBytes(field.getValue(), out);
writeBytes(CR_LF, out);
}
private static void writeField(final NameValue field, final Charset charset,
final OutputStream out) throws IOException {
writeBytes(field.getName(), charset, out);
writeBytes(FIELD_SEP, out);
writeBytes(field.getValue(), charset, out);
writeBytes(CR_LF, out);
}
private static final ByteArrayBuffer FIELD_SEP = encode(
MIME.DEFAULT_CHARSET, ": ");
private static final ByteArrayBuffer CR_LF = encode(MIME.DEFAULT_CHARSET,
"\r\n");
private static final ByteArrayBuffer TWO_DASHES = encode(
MIME.DEFAULT_CHARSET, "--");
private final String subType;
private final Charset charset;
private final String boundary;
private final List<FormBodyPart> parts;
private final HttpMultipartMode mode;
/**
* Creates an instance with the specified settings.
*
* @param subType
* mime subtype - must not be {@code null}
* @param charset
* the character set to use. May be {@code null}, in which case
* {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
* @param boundary
* to use - must not be {@code null}
* @param mode
* the mode to use
* @throws IllegalArgumentException
* if charset is null or boundary is null
*/
public HttpMultipart(final String subType, final Charset charset,
final String boundary, HttpMultipartMode mode) {
super();
if (subType == null) {
throw new IllegalArgumentException(
"Multipart subtype may not be null");
}
if (boundary == null) {
throw new IllegalArgumentException(
"Multipart boundary may not be null");
}
this.subType = subType;
this.charset = charset != null ? charset : MIME.DEFAULT_CHARSET;
this.boundary = boundary;
this.parts = new ArrayList<FormBodyPart>();
this.mode = mode;
}
/**
* Creates an instance with the specified settings. Mode is set to
* {@link HttpMultipartMode#STRICT}
*
* @param subType
* mime subtype - must not be {@code null}
* @param charset
* the character set to use. May be {@code null}, in which case
* {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
* @param boundary
* to use - must not be {@code null}
* @throws IllegalArgumentException
* if charset is null or boundary is null
*/
public HttpMultipart(final String subType, final Charset charset,
final String boundary) {
this(subType, charset, boundary, HttpMultipartMode.STRICT);
}
public HttpMultipart(final String subType, final String boundary) {
this(subType, null, boundary);
}
public String getSubType() {
return this.subType;
}
public Charset getCharset() {
return this.charset;
}
public HttpMultipartMode getMode() {
return this.mode;
}
public List<FormBodyPart> getBodyParts() {
return this.parts;
}
public void addBodyPart(final FormBodyPart part) {
if (part == null) {
return;
}
this.parts.add(part);
}
public String getBoundary() {
return this.boundary;
}
private void doWriteTo(final HttpMultipartMode mode,
final OutputStream out, boolean writeContent) throws IOException {
ByteArrayBuffer boundary = encode(this.charset, getBoundary());
for (FormBodyPart part : this.parts) {
writeBytes(TWO_DASHES, out);
writeBytes(boundary, out);
writeBytes(CR_LF, out);
Header header = part.getHeader();
switch (mode) {
case STRICT:
for (NameValue field : header) {
writeField(field, out);
}
break;
case BROWSER_COMPATIBLE:
// Only write Content-Disposition
// Use content charset
NameValue cd = part.getHeader().getField(
MIME.CONTENT_DISPOSITION);
writeField(cd, this.charset, out);
String filename = part.getBody().getFilename();
if (filename != null) {
NameValue ct = part.getHeader().getField(MIME.CONTENT_TYPE);
writeField(ct, this.charset, out);
}
break;
}
writeBytes(CR_LF, out);
if (writeContent) {
part.getBody().writeTo(out);
}
writeBytes(CR_LF, out);
}
writeBytes(TWO_DASHES, out);
writeBytes(boundary, out);
writeBytes(TWO_DASHES, out);
writeBytes(CR_LF, out);
}
/**
* Writes out the content in the multipart/form encoding. This method
* produces slightly different formatting depending on its compatibility
* mode.
*
* @see #getMode()
*/
public void writeTo(final OutputStream out) throws IOException {
doWriteTo(this.mode, out, true);
}
/**
* Determines the total length of the multipart content (content length of
* individual parts plus that of extra elements required to delimit the
* parts from one another). If any of the @{link BodyPart}s contained in
* this object is of a streaming entity of unknown length the total length
* is also unknown.
* <p/>
* This method buffers only a small amount of data in order to determine the
* total length of the entire entity. The content of individual parts is not
* buffered.
*
* @return total length of the multipart entity if known, <code>-1</code>
* otherwise.
*/
public long getTotalLength() {
long contentLen = 0;
for (FormBodyPart part : this.parts) {
ContentBody body = part.getBody();
long len = body.getContentLength();
if (len >= 0) {
contentLen += len;
} else {
return -1;
}
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
doWriteTo(this.mode, out, false);
byte[] extra = out.toByteArray();
return contentLen + extra.length;
} catch (IOException ex) {
// Should never happen
return -1;
}
}
}

View File

@ -1,12 +0,0 @@
package com.foxinmy.weixin4j.http.apache;
public enum HttpMultipartMode {
/** RFC 822, RFC 2045, RFC 2046 compliant */
STRICT,
/**
* browser-compatible mode, i.e. only write Content-Disposition; use content
* charset
*/
BROWSER_COMPATIBLE
}

View File

@ -1,99 +0,0 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
*
* @since 4.0
*/
public class InputStreamBody extends AbstractContentBody {
private final InputStream in;
private final String filename;
private int contentLength;
public InputStreamBody(final InputStream in, final String mimeType,
final String filename) {
super(mimeType);
if (in == null) {
throw new IllegalArgumentException("Input stream may not be null");
}
this.in = in;
this.filename = filename;
}
public InputStreamBody(final InputStream in, final String filename) {
this(in, "application/octet-stream", filename);
}
public InputStream getInputStream() {
return this.in;
}
public void writeTo(final OutputStream out) throws IOException {
if (out == null) {
throw new IllegalArgumentException("Output stream may not be null");
}
try {
byte[] tmp = new byte[4096];
int l;
while ((l = this.in.read(tmp)) != -1) {
out.write(tmp, 0, l);
contentLength += l;
}
out.flush();
} finally {
this.in.close();
}
}
public String getTransferEncoding() {
return MIME.ENC_BINARY;
}
public String getCharset() {
return null;
}
public long getContentLength() {
try {
return Math.max(contentLength, this.in.available());
} catch (IOException e) {
return -1;
}
}
public String getFilename() {
return this.filename;
}
}

View File

@ -1,174 +0,0 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Random;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.entity.HttpEntity;
/**
* Multipart/form coded HTTP entity consisting of multiple body parts.
*
* @since 4.0
*/
public class MultipartEntity implements HttpEntity {
/**
* The pool of ASCII chars to be used for generating a multipart boundary.
*/
private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.toCharArray();
private final HttpMultipart multipart;
// @GuardedBy("dirty") // we always read dirty before accessing length
private long length;
private volatile boolean dirty; // used to decide whether to recalculate
// length
/**
* Creates an instance using the specified parameters
*
* @param mode
* the mode to use, may be {@code null}, in which case
* {@link HttpMultipartMode#STRICT} is used
* @param boundary
* the boundary string, may be {@code null}, in which case
* {@link #generateBoundary()} is invoked to create the string
* @param charset
* the character set to use, may be {@code null}, in which case
* {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
*/
public MultipartEntity(HttpMultipartMode mode, String boundary,
Charset charset) {
super();
if (boundary == null) {
boundary = generateBoundary();
}
if (mode == null) {
mode = HttpMultipartMode.STRICT;
}
this.multipart = new HttpMultipart("form-data", charset, boundary, mode);
this.dirty = true;
}
/**
* Creates an instance using the specified {@link HttpMultipartMode} mode.
* Boundary and charset are set to {@code null}.
*
* @param mode
* the desired mode
*/
public MultipartEntity(final HttpMultipartMode mode) {
this(mode, null, null);
}
/**
* Creates an instance using mode {@link HttpMultipartMode#STRICT}
*/
public MultipartEntity() {
this(HttpMultipartMode.STRICT, null, null);
}
protected String generateBoundary() {
StringBuilder buffer = new StringBuilder();
Random rand = new Random();
int count = rand.nextInt(11) + 30; // a random size from 30 to 40
for (int i = 0; i < count; i++) {
buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
}
return buffer.toString();
}
public void addPart(final FormBodyPart bodyPart) {
this.multipart.addBodyPart(bodyPart);
this.dirty = true;
}
public void addPart(final String name, final ContentBody contentBody) {
addPart(new FormBodyPart(name, contentBody));
}
public boolean isRepeatable() {
for (FormBodyPart part : this.multipart.getBodyParts()) {
ContentBody body = part.getBody();
if (body.getContentLength() < 0) {
return false;
}
}
return true;
}
public boolean isChunked() {
return !isRepeatable();
}
public boolean isStreaming() {
return !isRepeatable();
}
@Override
public long getContentLength() {
if (this.dirty) {
this.length = this.multipart.getTotalLength();
this.dirty = false;
}
return this.length;
}
@Override
public ContentType getContentType() {
return ContentType.MULTIPART_FORM_DATA;
}
public void consumeContent() throws IOException,
UnsupportedOperationException {
if (isStreaming()) {
throw new UnsupportedOperationException(
"Streaming entity does not implement #consumeContent()");
}
}
@Override
public InputStream getContent() throws IOException,
UnsupportedOperationException {
throw new UnsupportedOperationException(
"Multipart form entity does not implement #getContent()");
}
@Override
public void writeTo(final OutputStream outstream) throws IOException {
this.multipart.writeTo(outstream);
outstream.flush();
}
}

View File

@ -25,7 +25,12 @@
*
*/
package com.foxinmy.weixin4j.http.apache;
package com.foxinmy.weixin4j.http.apache.content;
import java.nio.charset.Charset;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.MimeType;
/**
*
@ -33,36 +38,31 @@ package com.foxinmy.weixin4j.http.apache;
*/
public abstract class AbstractContentBody implements ContentBody {
private final String mimeType;
private final String mediaType;
private final String subType;
private final ContentType contentType;
public AbstractContentBody(final String mimeType) {
/**
* @since 4.3
*/
public AbstractContentBody(final ContentType contentType) {
super();
if (mimeType == null) {
throw new IllegalArgumentException("MIME type may not be null");
}
this.mimeType = mimeType;
int i = mimeType.indexOf('/');
if (i != -1) {
this.mediaType = mimeType.substring(0, i);
this.subType = mimeType.substring(i + 1);
} else {
this.mediaType = mimeType;
this.subType = null;
}
this.contentType = contentType;
}
public String getMimeType() {
return this.mimeType;
/**
* @since 4.3
*/
public ContentType getContentType() {
return this.contentType;
}
public String getMediaType() {
return this.mediaType;
@Override
public MimeType getMimeType() {
return this.contentType.getMimeType();
}
public String getSubType() {
return this.subType;
@Override
public String getCharset() {
final Charset charset = this.contentType.getCharset();
return charset != null ? charset.name() : null;
}
}

View File

@ -24,13 +24,18 @@
* <http://www.apache.org/>.
*
*/
package com.foxinmy.weixin4j.http.apache;
package com.foxinmy.weixin4j.http.apache.content;
import java.io.IOException;
import java.io.OutputStream;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.apache.mime.MIME;
/**
* Body part that is built using a byte array containing a file.
* Binary body part backed by a byte array.
*
* @see org.apache.http.entity.mime.MultipartEntityBuilder
*
* @since 4.1
*/
@ -50,14 +55,19 @@ public class ByteArrayBody extends AbstractContentBody {
* Creates a new ByteArrayBody.
*
* @param data The contents of the file contained in this part.
* @param mimeType The mime type of the file contained in this part.
* @param mimeType The MIME type of the file contained in this part.
* @param filename The name of the file contained in this part.
*
*/
public ByteArrayBody(final byte[] data, final String mimeType, final String filename) {
super(mimeType);
if (data == null) {
throw new IllegalArgumentException("byte[] may not be null");
this(data, ContentType.create(mimeType), filename);
}
/**
* @since 4.3
*/
public ByteArrayBody(final byte[] data, final ContentType contentType, final String filename) {
super(contentType);
this.data = data;
this.filename = filename;
}
@ -72,22 +82,27 @@ public class ByteArrayBody extends AbstractContentBody {
this(data, "application/octet-stream", filename);
}
@Override
public String getFilename() {
return filename;
}
@Override
public void writeTo(final OutputStream out) throws IOException {
out.write(data);
}
@Override
public String getCharset() {
return null;
}
@Override
public String getTransferEncoding() {
return MIME.ENC_BINARY;
}
@Override
public long getContentLength() {
return data.length;
}

View File

@ -0,0 +1,86 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.content;
import java.io.IOException;
import java.io.OutputStream;
import com.foxinmy.weixin4j.http.MimeType;
/**
*
* @since 4.0
*/
public interface ContentBody {
/**
* Returns the body descriptors MIME type.
*
* @return The MIME type, which has been parsed from the content-type
* definition. Must not be null, but "text/plain", if no
* content-type was specified.
*/
MimeType getMimeType();
/**
* <p>
* The body descriptors character set, defaulted appropriately for the MIME
* type.
* </p>
* <p>
* For {@code TEXT} types, this will be defaulted to {@code us-ascii}. For
* other types, when the charset parameter is missing this property will be
* null.
* </p>
*
* @return Character set, which has been parsed from the content-type
* definition. Not null for {@code TEXT} types, when unset will be
* set to default {@code us-ascii}. For other types, when unset,
* null will be returned.
*/
String getCharset();
/**
* Returns the body descriptors transfer encoding.
*
* @return The transfer encoding. Must not be null, but "7bit", if no
* transfer-encoding was specified.
*/
String getTransferEncoding();
/**
* Returns the body descriptors content-length.
*
* @return Content length, if known, or -1, to indicate the absence of a
* content-length header.
*/
long getContentLength();
String getFilename();
void writeTo(OutputStream out) throws IOException;
}

View File

@ -0,0 +1,134 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.content;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.apache.mime.MIME;
/**
* Binary body part backed by a file.
*
* @see org.apache.http.entity.mime.MultipartEntityBuilder
*
* @since 4.0
*/
public class FileBody extends AbstractContentBody {
private final File file;
private final String filename;
/**
* @since 4.1
*
*/
public FileBody(final File file, final String filename,
final String mimeType, final String charset) {
this(file, ContentType.create(mimeType, charset), filename);
}
/**
* @since 4.1
*
*/
public FileBody(final File file, final String mimeType, final String charset) {
this(file, null, mimeType, charset);
}
/**
*/
public FileBody(final File file, final String mimeType) {
this(file, ContentType.create(mimeType), null);
}
public FileBody(final File file) {
this(file, ContentType.DEFAULT_BINARY, file != null ? file.getName()
: null);
}
/**
* @since 4.3
*/
public FileBody(final File file, final ContentType contentType,
final String filename) {
super(contentType);
this.file = file;
this.filename = filename == null ? file.getName() : filename;
}
/**
* @since 4.3
*/
public FileBody(final File file, final ContentType contentType) {
this(file, contentType, file != null ? file.getName() : null);
}
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
}
@Override
public void writeTo(final OutputStream out) throws IOException {
final InputStream in = new FileInputStream(this.file);
try {
final byte[] tmp = new byte[4096];
int l;
while ((l = in.read(tmp)) != -1) {
out.write(tmp, 0, l);
}
out.flush();
} finally {
in.close();
}
}
@Override
public String getTransferEncoding() {
return MIME.ENC_BINARY;
}
@Override
public long getContentLength() {
return this.file.length();
}
@Override
public String getFilename() {
return filename;
}
public File getFile() {
return this.file;
}
}

View File

@ -25,99 +25,86 @@
*
*/
package com.foxinmy.weixin4j.http.apache;
package com.foxinmy.weixin4j.http.apache.content;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.apache.mime.MIME;
/**
* Binary body part backed by an input stream.
*
* @see org.apache.http.entity.mime.MultipartEntityBuilder
*
* @since 4.0
*/
public class FileBody extends AbstractContentBody {
public class InputStreamBody extends AbstractContentBody {
private final File file;
private final InputStream in;
private final String filename;
private final String charset;
/**
* @since 4.1
*
*/
public FileBody(final File file,
final String filename,
final String mimeType,
final String charset) {
super(mimeType);
if (file == null) {
throw new IllegalArgumentException("File may not be null");
public InputStreamBody(final InputStream in, final String mimeType, final String filename) {
this(in, ContentType.create(mimeType), filename);
}
this.file = file;
if (filename != null)
public InputStreamBody(final InputStream in, final String filename) {
this(in, ContentType.DEFAULT_BINARY, filename);
}
/**
* @since 4.3
*/
public InputStreamBody(final InputStream in, final ContentType contentType, final String filename) {
super(contentType);
this.in = in;
this.filename = filename;
else
this.filename = file.getName();
this.charset = charset;
}
/**
* @since 4.1
* @since 4.3
*/
public FileBody(final File file,
final String mimeType,
final String charset) {
this(file, null, mimeType, charset);
public InputStreamBody(final InputStream in, final ContentType contentType) {
this(in, contentType, null);
}
public FileBody(final File file, final String mimeType) {
this(file, mimeType, null);
}
public FileBody(final File file) {
this(file, "application/octet-stream");
}
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
public InputStream getInputStream() {
return this.in;
}
@Override
public void writeTo(final OutputStream out) throws IOException {
if (out == null) {
throw new IllegalArgumentException("Output stream may not be null");
}
InputStream in = new FileInputStream(this.file);
try {
byte[] tmp = new byte[4096];
final byte[] tmp = new byte[4096];
int l;
while ((l = in.read(tmp)) != -1) {
while ((l = this.in.read(tmp)) != -1) {
out.write(tmp, 0, l);
}
out.flush();
} finally {
in.close();
this.in.close();
}
}
@Override
public String getTransferEncoding() {
return MIME.ENC_BINARY;
}
public String getCharset() {
return charset;
}
@Override
public long getContentLength() {
return this.file.length();
return -1;
}
@Override
public String getFilename() {
return filename;
}
public File getFile() {
return this.file;
return this.filename;
}
}

View File

@ -25,7 +25,7 @@
*
*/
package com.foxinmy.weixin4j.http.apache;
package com.foxinmy.weixin4j.http.apache.content;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -36,32 +36,46 @@ import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.apache.mime.MIME;
import com.foxinmy.weixin4j.util.Consts;
/**
* Text body part backed by a byte array.
*
* @see org.apache.http.entity.mime.MultipartEntityBuilder
*
* @since 4.0
*/
public class StringBody extends AbstractContentBody {
private final byte[] content;
private final Charset charset;
/**
* @since 4.1
*
* @deprecated (4.3) use {@link StringBody#StringBody(String, ContentType)}
* or {@link org.apache.http.entity.mime.MultipartEntityBuilder}
*/
@Deprecated
public static StringBody create(
final String text,
final String mimeType,
final Charset charset) throws IllegalArgumentException {
try {
return new StringBody(text, mimeType, charset);
} catch (UnsupportedEncodingException ex) {
} catch (final UnsupportedEncodingException ex) {
throw new IllegalArgumentException("Charset " + charset + " is not supported", ex);
}
}
/**
* @since 4.1
*
* @deprecated (4.3) use {@link StringBody#StringBody(String, ContentType)}
* or {@link org.apache.http.entity.mime.MultipartEntityBuilder}
*/
@Deprecated
public static StringBody create(
final String text, final Charset charset) throws IllegalArgumentException {
return create(text, null, charset);
@ -69,43 +83,41 @@ public class StringBody extends AbstractContentBody {
/**
* @since 4.1
*
* @deprecated (4.3) use {@link StringBody#StringBody(String, ContentType)}
* or {@link org.apache.http.entity.mime.MultipartEntityBuilder}
*/
@Deprecated
public static StringBody create(final String text) throws IllegalArgumentException {
return create(text, null, null);
}
/**
* Create a StringBody from the specified text, mime type and character set.
* Create a StringBody from the specified text, MIME type and character set.
*
* @param text to be used for the body, not {@code null}
* @param mimeType the mime type, not {@code null}
* @param mimeType the MIME type, not {@code null}
* @param charset the character set, may be {@code null}, in which case the US-ASCII charset is used
* @throws UnsupportedEncodingException
* @throws IllegalArgumentException if the {@code text} parameter is null
*
*/
public StringBody(
final String text,
final String mimeType,
Charset charset) throws UnsupportedEncodingException {
super(mimeType);
if (text == null) {
throw new IllegalArgumentException("Text may not be null");
}
if (charset == null) {
charset = Charset.forName("US-ASCII");
}
this.content = text.getBytes(charset.name());
this.charset = charset;
final Charset charset) throws UnsupportedEncodingException {
this(text, ContentType.create(mimeType, charset != null ? charset : Consts.UTF_8));
}
/**
* Create a StringBody from the specified text and character set.
* The mime type is set to "text/plain".
* The MIME type is set to "text/plain".
*
* @param text to be used for the body, not {@code null}
* @param charset the character set, may be {@code null}, in which case the US-ASCII charset is used
* @throws UnsupportedEncodingException
* @throws IllegalArgumentException if the {@code text} parameter is null
*
*/
public StringBody(final String text, final Charset charset) throws UnsupportedEncodingException {
this(text, "text/plain", charset);
@ -113,29 +125,38 @@ public class StringBody extends AbstractContentBody {
/**
* Create a StringBody from the specified text.
* The mime type is set to "text/plain".
* The hosts default charset is used.
* The MIME type is set to "text/plain".
* The {@linkplain Consts#ASCII ASCII} charset is used.
*
* @param text to be used for the body, not {@code null}
* @throws UnsupportedEncodingException
* @throws IllegalArgumentException if the {@code text} parameter is null
*
*/
public StringBody(final String text) throws UnsupportedEncodingException {
this(text, "text/plain", null);
this(text, "text/plain", Consts.UTF_8);
}
/**
* @since 4.3
*/
public StringBody(final String text, final ContentType contentType) {
super(contentType);
final Charset charset = contentType.getCharset();
this.content = text.getBytes(charset != null ? charset : Consts.UTF_8);
}
public Reader getReader() {
final Charset charset = getContentType().getCharset();
return new InputStreamReader(
new ByteArrayInputStream(this.content),
this.charset);
charset != null ? charset : Consts.UTF_8);
}
@Override
public void writeTo(final OutputStream out) throws IOException {
if (out == null) {
throw new IllegalArgumentException("Output stream may not be null");
}
InputStream in = new ByteArrayInputStream(this.content);
byte[] tmp = new byte[4096];
final InputStream in = new ByteArrayInputStream(this.content);
final byte[] tmp = new byte[4096];
int l;
while ((l = in.read(tmp)) != -1) {
out.write(tmp, 0, l);
@ -143,20 +164,18 @@ public class StringBody extends AbstractContentBody {
out.flush();
}
@Override
public String getTransferEncoding() {
return MIME.ENC_8BIT;
}
public String getCharset() {
return this.charset.name();
}
@Override
public long getContentLength() {
return this.content.length;
}
@Override
public String getFilename() {
return null;
}
}

View File

@ -0,0 +1,194 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.mime;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.List;
import com.foxinmy.weixin4j.http.apache.content.ContentBody;
import com.foxinmy.weixin4j.util.ByteArrayBuffer;
/**
* HttpMultipart represents a collection of MIME multipart encoded content bodies. This class is
* capable of operating either in the strict (RFC 822, RFC 2045, RFC 2046 compliant) or
* the browser compatible modes.
*
* @since 4.3
*/
abstract class AbstractMultipartForm {
private static ByteArrayBuffer encode(
final Charset charset, final String string) {
final ByteBuffer encoded = charset.encode(CharBuffer.wrap(string));
final ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining());
bab.append(encoded.array(), encoded.position(), encoded.remaining());
return bab;
}
private static void writeBytes(
final ByteArrayBuffer b, final OutputStream out) throws IOException {
out.write(b.buffer(), 0, b.length());
}
private static void writeBytes(
final String s, final Charset charset, final OutputStream out) throws IOException {
final ByteArrayBuffer b = encode(charset, s);
writeBytes(b, out);
}
private static void writeBytes(
final String s, final OutputStream out) throws IOException {
final ByteArrayBuffer b = encode(MIME.DEFAULT_CHARSET, s);
writeBytes(b, out);
}
protected static void writeField(
final MinimalField field, final OutputStream out) throws IOException {
writeBytes(field.getName(), out);
writeBytes(FIELD_SEP, out);
writeBytes(field.getBody(), out);
writeBytes(CR_LF, out);
}
protected static void writeField(
final MinimalField field, final Charset charset, final OutputStream out) throws IOException {
writeBytes(field.getName(), charset, out);
writeBytes(FIELD_SEP, out);
writeBytes(field.getBody(), charset, out);
writeBytes(CR_LF, out);
}
private static final ByteArrayBuffer FIELD_SEP = encode(MIME.DEFAULT_CHARSET, ": ");
private static final ByteArrayBuffer CR_LF = encode(MIME.DEFAULT_CHARSET, "\r\n");
private static final ByteArrayBuffer TWO_DASHES = encode(MIME.DEFAULT_CHARSET, "--");
final Charset charset;
final String boundary;
/**
* Creates an instance with the specified settings.
*
* @param charset the character set to use. May be {@code null}, in which case {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
* @param boundary to use - must not be {@code null}
* @throws IllegalArgumentException if charset is null or boundary is null
*/
public AbstractMultipartForm(final Charset charset, final String boundary) {
super();
this.charset = charset != null ? charset : MIME.DEFAULT_CHARSET;
this.boundary = boundary;
}
public AbstractMultipartForm(final String boundary) {
this(null, boundary);
}
public abstract List<FormBodyPart> getBodyParts();
void doWriteTo(
final OutputStream out,
final boolean writeContent) throws IOException {
final ByteArrayBuffer boundaryEncoded = encode(this.charset, this.boundary);
for (final FormBodyPart part: getBodyParts()) {
writeBytes(TWO_DASHES, out);
writeBytes(boundaryEncoded, out);
writeBytes(CR_LF, out);
formatMultipartHeader(part, out);
writeBytes(CR_LF, out);
if (writeContent) {
part.getBody().writeTo(out);
}
writeBytes(CR_LF, out);
}
writeBytes(TWO_DASHES, out);
writeBytes(boundaryEncoded, out);
writeBytes(TWO_DASHES, out);
writeBytes(CR_LF, out);
}
/**
* Write the multipart header fields; depends on the style.
*/
protected abstract void formatMultipartHeader(
final FormBodyPart part,
final OutputStream out) throws IOException;
/**
* Writes out the content in the multipart/form encoding. This method
* produces slightly different formatting depending on its compatibility
* mode.
*/
public void writeTo(final OutputStream out) throws IOException {
doWriteTo(out, true);
}
/**
* Determines the total length of the multipart content (content length of
* individual parts plus that of extra elements required to delimit the parts
* from one another). If any of the @{link BodyPart}s contained in this object
* is of a streaming entity of unknown length the total length is also unknown.
* <p>
* This method buffers only a small amount of data in order to determine the
* total length of the entire entity. The content of individual parts is not
* buffered.
* </p>
*
* @return total length of the multipart entity if known, {@code -1}
* otherwise.
*/
public long getTotalLength() {
long contentLen = 0;
for (final FormBodyPart part: getBodyParts()) {
final ContentBody body = part.getBody();
final long len = body.getContentLength();
if (len >= 0) {
contentLen += len;
} else {
return -1;
}
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
doWriteTo(out, false);
final byte[] extra = out.toByteArray();
return contentLen + extra.length;
} catch (final IOException ex) {
// Should never happen
return -1;
}
}
}

View File

@ -25,10 +25,11 @@
*
*/
package com.foxinmy.weixin4j.http.apache;
import com.foxinmy.weixin4j.util.NameValue;
package com.foxinmy.weixin4j.http.apache.mime;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.apache.content.AbstractContentBody;
import com.foxinmy.weixin4j.http.apache.content.ContentBody;
/**
* FormBodyPart class represents a content body that can be used as a part of multipart encoded
@ -41,17 +42,20 @@ public class FormBodyPart {
private final String name;
private final Header header;
private final ContentBody body;
FormBodyPart(final String name, final ContentBody body, final Header header) {
super();
this.name = name;
this.body = body;
this.header = header != null ? header : new Header();
}
/**
* (4.4) use {@link com.foxinmy.weixin4j.http.apache.mime.FormBodyPartBuilder}.
*/
public FormBodyPart(final String name, final ContentBody body) {
super();
if (name == null) {
throw new IllegalArgumentException("Name may not be null");
}
if (body == null) {
throw new IllegalArgumentException("Body may not be null");
}
this.name = name;
this.body = body;
this.header = new Header();
@ -74,14 +78,14 @@ public class FormBodyPart {
}
public void addField(final String name, final String value) {
if (name == null) {
throw new IllegalArgumentException("Field name may not be null");
}
this.header.addField(new NameValue(name, value));
this.header.addField(new MinimalField(name, value));
}
/**
* (4.4) use {@link com.foxinmy.weixin4j.http.apache.mime.FormBodyPartBuilder}.
*/
protected void generateContentDisp(final ContentBody body) {
StringBuilder buffer = new StringBuilder();
final StringBuilder buffer = new StringBuilder();
buffer.append("form-data; name=\"");
buffer.append(getName());
buffer.append("\"");
@ -93,8 +97,20 @@ public class FormBodyPart {
addField(MIME.CONTENT_DISPOSITION, buffer.toString());
}
/**
* (4.4) use {@link com.foxinmy.weixin4j.http.apache.mime.FormBodyPartBuilder}.
*/
protected void generateContentType(final ContentBody body) {
StringBuilder buffer = new StringBuilder();
final ContentType contentType;
if (body instanceof AbstractContentBody) {
contentType = ((AbstractContentBody) body).getContentType();
} else {
contentType = null;
}
if (contentType != null) {
addField(MIME.CONTENT_TYPE, contentType.toString());
} else {
final StringBuilder buffer = new StringBuilder();
buffer.append(body.getMimeType()); // MimeType cannot be null
if (body.getCharset() != null) { // charset may legitimately be null
buffer.append("; charset=");
@ -102,9 +118,12 @@ public class FormBodyPart {
}
addField(MIME.CONTENT_TYPE, buffer.toString());
}
}
/**
* (4.4) use {@link com.foxinmy.weixin4j.http.apache.mime.FormBodyPartBuilder}.
*/
protected void generateTransferEncoding(final ContentBody body) {
addField(MIME.CONTENT_TRANSFER_ENC, body.getTransferEncoding()); // TE cannot be null
}
}

View File

@ -0,0 +1,149 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.mime;
import java.util.List;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.apache.content.AbstractContentBody;
import com.foxinmy.weixin4j.http.apache.content.ContentBody;
/**
* Builder for individual {@link com.foxinmy.weixin4j.http.apache.mime.FormBodyPart}s.
*
* @since 4.4
*/
public class FormBodyPartBuilder {
private String name;
private ContentBody body;
private final Header header;
public static FormBodyPartBuilder create(final String name, final ContentBody body) {
return new FormBodyPartBuilder(name, body);
}
public static FormBodyPartBuilder create() {
return new FormBodyPartBuilder();
}
FormBodyPartBuilder(final String name, final ContentBody body) {
this();
this.name = name;
this.body = body;
}
FormBodyPartBuilder() {
this.header = new Header();
}
public FormBodyPartBuilder setName(final String name) {
this.name = name;
return this;
}
public FormBodyPartBuilder setBody(final ContentBody body) {
this.body = body;
return this;
}
public FormBodyPartBuilder addField(final String name, final String value) {
this.header.addField(new MinimalField(name, value));
return this;
}
public FormBodyPartBuilder setField(final String name, final String value) {
this.header.setField(new MinimalField(name, value));
return this;
}
public FormBodyPartBuilder removeFields(final String name) {
this.header.removeFields(name);
return this;
}
public FormBodyPart build() {
final Header headerCopy = new Header();
final List<MinimalField> fields = this.header.getFields();
for (final MinimalField field: fields) {
headerCopy.addField(field);
}
if (headerCopy.getField(MIME.CONTENT_DISPOSITION) == null) {
final StringBuilder buffer = new StringBuilder();
buffer.append("form-data; name=\"");
buffer.append(encodeForHeader(this.name));
buffer.append("\"");
if (this.body.getFilename() != null) {
buffer.append("; filename=\"");
buffer.append(encodeForHeader(this.body.getFilename()));
buffer.append("\"");
}
headerCopy.addField(new MinimalField(MIME.CONTENT_DISPOSITION, buffer.toString()));
}
if (headerCopy.getField(MIME.CONTENT_TYPE) == null) {
final ContentType contentType;
if (body instanceof AbstractContentBody) {
contentType = ((AbstractContentBody) body).getContentType();
} else {
contentType = null;
}
if (contentType != null) {
headerCopy.addField(new MinimalField(MIME.CONTENT_TYPE, contentType.toString()));
} else {
final StringBuilder buffer = new StringBuilder();
buffer.append(this.body.getMimeType()); // MimeType cannot be null
if (this.body.getCharset() != null) { // charset may legitimately be null
buffer.append("; charset=");
buffer.append(this.body.getCharset());
}
headerCopy.addField(new MinimalField(MIME.CONTENT_TYPE, buffer.toString()));
}
}
if (headerCopy.getField(MIME.CONTENT_TRANSFER_ENC) == null) {
// TE cannot be null
headerCopy.addField(new MinimalField(MIME.CONTENT_TRANSFER_ENC, body.getTransferEncoding()));
}
return new FormBodyPart(this.name, this.body, headerCopy);
}
private static String encodeForHeader(final String headerName) {
if (headerName == null) {
return null;
}
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < headerName.length(); i++) {
final char x = headerName.charAt(i);
if (x == '"' || x == '\\' || x == '\r') {
sb.append("\\");
}
sb.append(x);
}
return sb.toString();
}
}

View File

@ -0,0 +1,144 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.mime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* The header of an entity (see RFC 2045).
*/
public class Header implements Iterable<MinimalField> {
private final List<MinimalField> fields;
private final Map<String, List<MinimalField>> fieldMap;
public Header() {
super();
this.fields = new LinkedList<MinimalField>();
this.fieldMap = new HashMap<String, List<MinimalField>>();
}
public void addField(final MinimalField field) {
if (field == null) {
return;
}
final String key = field.getName().toLowerCase(Locale.ROOT);
List<MinimalField> values = this.fieldMap.get(key);
if (values == null) {
values = new LinkedList<MinimalField>();
this.fieldMap.put(key, values);
}
values.add(field);
this.fields.add(field);
}
public List<MinimalField> getFields() {
return new ArrayList<MinimalField>(this.fields);
}
public MinimalField getField(final String name) {
if (name == null) {
return null;
}
final String key = name.toLowerCase(Locale.ROOT);
final List<MinimalField> list = this.fieldMap.get(key);
if (list != null && !list.isEmpty()) {
return list.get(0);
}
return null;
}
public List<MinimalField> getFields(final String name) {
if (name == null) {
return null;
}
final String key = name.toLowerCase(Locale.ROOT);
final List<MinimalField> list = this.fieldMap.get(key);
if (list == null || list.isEmpty()) {
return Collections.emptyList();
} else {
return new ArrayList<MinimalField>(list);
}
}
public int removeFields(final String name) {
if (name == null) {
return 0;
}
final String key = name.toLowerCase(Locale.ROOT);
final List<MinimalField> removed = fieldMap.remove(key);
if (removed == null || removed.isEmpty()) {
return 0;
}
this.fields.removeAll(removed);
return removed.size();
}
public void setField(final MinimalField field) {
if (field == null) {
return;
}
final String key = field.getName().toLowerCase(Locale.ROOT);
final List<MinimalField> list = fieldMap.get(key);
if (list == null || list.isEmpty()) {
addField(field);
return;
}
list.clear();
list.add(field);
int firstOccurrence = -1;
int index = 0;
for (final Iterator<MinimalField> it = this.fields.iterator(); it.hasNext(); index++) {
final MinimalField f = it.next();
if (f.getName().equalsIgnoreCase(field.getName())) {
it.remove();
if (firstOccurrence == -1) {
firstOccurrence = index;
}
}
}
this.fields.add(firstOccurrence, field);
}
@Override
public Iterator<MinimalField> iterator() {
return Collections.unmodifiableList(fields).iterator();
}
@Override
public String toString() {
return this.fields.toString();
}
}

View File

@ -0,0 +1,78 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.mime;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.List;
/**
* HttpBrowserCompatibleMultipart represents a collection of MIME multipart encoded
* content bodies. This class is emulates browser compatibility, e.g. IE 5 or earlier.
*
* @since 4.3
*/
class HttpBrowserCompatibleMultipart extends AbstractMultipartForm {
private final List<FormBodyPart> parts;
public HttpBrowserCompatibleMultipart(
final Charset charset,
final String boundary,
final List<FormBodyPart> parts) {
super(charset, boundary);
this.parts = parts;
}
@Override
public List<FormBodyPart> getBodyParts() {
return this.parts;
}
/**
* Write the multipart header fields; depends on the style.
*/
@Override
protected void formatMultipartHeader(
final FormBodyPart part,
final OutputStream out) throws IOException {
// For browser-compatible, only write Content-Disposition
// Use content charset
final Header header = part.getHeader();
final MinimalField cd = header.getField(MIME.CONTENT_DISPOSITION);
writeField(cd, this.charset, out);
final String filename = part.getBody().getFilename();
if (filename != null) {
final MinimalField ct = header.getField(MIME.CONTENT_TYPE);
writeField(ct, this.charset, out);
}
}
}

View File

@ -0,0 +1,141 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.mime;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/**
* HttpMultipart represents a collection of MIME multipart encoded content bodies. This class is
* capable of operating either in the strict (RFC 822, RFC 2045, RFC 2046 compliant) or
* the browser compatible modes.
*
* @since 4.0
*
* @deprecated (4.3) Use {@link MultipartEntityBuilder}.
*/
@Deprecated
public class HttpMultipart extends AbstractMultipartForm {
private final HttpMultipartMode mode;
private final List<FormBodyPart> parts;
private final String subType;
/**
* Creates an instance with the specified settings.
*
* @param subType MIME subtype - must not be {@code null}
* @param charset the character set to use. May be {@code null},
* in which case {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
* @param boundary to use - must not be {@code null}
* @param mode the mode to use
* @throws IllegalArgumentException if charset is null or boundary is null
*/
public HttpMultipart(
final String subType, final Charset charset, final String boundary,
final HttpMultipartMode mode) {
super(charset, boundary);
this.subType = subType;
this.mode = mode;
this.parts = new ArrayList<FormBodyPart>();
}
/**
* Creates an instance with the specified settings.
* Mode is set to {@link HttpMultipartMode#STRICT}
*
* @param subType MIME subtype - must not be {@code null}
* @param charset the character set to use. May be {@code null},
* in which case {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
* @param boundary to use - must not be {@code null}
* @throws IllegalArgumentException if charset is null or boundary is null
*/
public HttpMultipart(final String subType, final Charset charset, final String boundary) {
this(subType, charset, boundary, HttpMultipartMode.STRICT);
}
public HttpMultipart(final String subType, final String boundary) {
this(subType, null, boundary);
}
public HttpMultipartMode getMode() {
return this.mode;
}
@Override
protected void formatMultipartHeader(
final FormBodyPart part, final OutputStream out) throws IOException {
final Header header = part.getHeader();
switch (this.mode) {
case BROWSER_COMPATIBLE:
// For browser-compatible, only write Content-Disposition
// Use content charset
final MinimalField cd = header.getField(MIME.CONTENT_DISPOSITION);
writeField(cd, this.charset, out);
final String filename = part.getBody().getFilename();
if (filename != null) {
final MinimalField ct = header.getField(MIME.CONTENT_TYPE);
writeField(ct, this.charset, out);
}
break;
default:
for (final MinimalField field: header) {
writeField(field, out);
}
}
}
@Override
public List<FormBodyPart> getBodyParts() {
return this.parts;
}
public void addBodyPart(final FormBodyPart part) {
if (part == null) {
return;
}
this.parts.add(part);
}
public String getSubType() {
return this.subType;
}
public Charset getCharset() {
return this.charset;
}
public String getBoundary() {
return this.boundary;
}
}

View File

@ -25,19 +25,19 @@
*
*/
package com.foxinmy.weixin4j.http.apache;
import java.io.IOException;
import java.io.OutputStream;
package com.foxinmy.weixin4j.http.apache.mime;
/**
*
* @since 4.0
*/
public interface ContentBody extends ContentDescriptor {
public enum HttpMultipartMode {
String getFilename();
void writeTo(OutputStream out) throws IOException;
/** RFC 822, RFC 2045, RFC 2046 compliant */
STRICT,
/** browser-compatible mode, i.e. only write Content-Disposition; use content charset */
BROWSER_COMPATIBLE,
/** RFC 6532 compliant */
RFC6532
}

View File

@ -0,0 +1,71 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.mime;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.List;
/**
* HttpRFC6532Multipart represents a collection of MIME multipart encoded content bodies,
* implementing the strict (RFC 822, RFC 2045, RFC 2046 compliant) interpretation
* of the spec, with the exception of allowing UTF-8 headers, as per RFC6532.
*
* @since 4.3
*/
class HttpRFC6532Multipart extends AbstractMultipartForm {
private final List<FormBodyPart> parts;
public HttpRFC6532Multipart(
final Charset charset,
final String boundary,
final List<FormBodyPart> parts) {
super(charset, boundary);
this.parts = parts;
}
@Override
public List<FormBodyPart> getBodyParts() {
return this.parts;
}
@Override
protected void formatMultipartHeader(
final FormBodyPart part,
final OutputStream out) throws IOException {
// For RFC6532, we output all fields with UTF-8 encoding.
final Header header = part.getHeader();
for (final MinimalField field: header) {
writeField(field, MIME.UTF8_CHARSET, out);
}
}
}

View File

@ -0,0 +1,71 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.mime;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.List;
/**
* HttpStrictMultipart represents a collection of MIME multipart encoded content bodies,
* implementing the strict (RFC 822, RFC 2045, RFC 2046 compliant) interpretation
* of the spec.
*
* @since 4.3
*/
class HttpStrictMultipart extends AbstractMultipartForm {
private final List<FormBodyPart> parts;
public HttpStrictMultipart(
final Charset charset,
final String boundary,
final List<FormBodyPart> parts) {
super(charset, boundary);
this.parts = parts;
}
@Override
public List<FormBodyPart> getBodyParts() {
return this.parts;
}
@Override
protected void formatMultipartHeader(
final FormBodyPart part,
final OutputStream out) throws IOException {
// For strict, we output all fields with MIME-standard encoding.
final Header header = part.getHeader();
for (final MinimalField field: header) {
writeField(field, out);
}
}
}

View File

@ -25,10 +25,12 @@
*
*/
package com.foxinmy.weixin4j.http.apache;
package com.foxinmy.weixin4j.http.apache.mime;
import java.nio.charset.Charset;
import com.foxinmy.weixin4j.util.Consts;
/**
*
* @since 4.0
@ -42,7 +44,8 @@ public final class MIME {
public static final String ENC_8BIT = "8bit";
public static final String ENC_BINARY = "binary";
/** The default character set to be used, i.e. "US-ASCII" */
public static final Charset DEFAULT_CHARSET = Charset.forName("US-ASCII");
public static final Charset DEFAULT_CHARSET = Consts.UTF_8;
public static final Charset UTF8_CHARSET = Consts.UTF_8;
}

View File

@ -0,0 +1,63 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.mime;
/**
* Minimal MIME field.
*
* @since 4.0
*/
public class MinimalField {
private final String name;
private final String value;
public MinimalField(final String name, final String value) {
super();
this.name = name;
this.value = value;
}
public String getName() {
return this.name;
}
public String getBody() {
return this.value;
}
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder();
buffer.append(this.name);
buffer.append(": ");
buffer.append(this.value);
return buffer.toString();
}
}

View File

@ -0,0 +1,163 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.mime;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Random;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.apache.content.ContentBody;
import com.foxinmy.weixin4j.http.entity.HttpEntity;
/**
* Multipart/form coded HTTP entity consisting of multiple body parts.
*
* @since 4.0
*
*/
public class MultipartEntity implements HttpEntity {
/**
* The pool of ASCII chars to be used for generating a multipart boundary.
*/
private final static char[] MULTIPART_CHARS =
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.toCharArray();
private final MultipartEntityBuilder builder;
private volatile MultipartFormEntity entity;
/**
* Creates an instance using the specified parameters
* @param mode the mode to use, may be {@code null}, in which case {@link HttpMultipartMode#STRICT} is used
* @param boundary the boundary string, may be {@code null}, in which case {@link #generateBoundary()} is invoked to create the string
* @param charset the character set to use, may be {@code null}, in which case {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
*/
public MultipartEntity(
final HttpMultipartMode mode,
final String boundary,
final Charset charset) {
super();
this.builder = new MultipartEntityBuilder()
.setMode(mode)
.setCharset(charset != null ? charset : MIME.DEFAULT_CHARSET)
.setBoundary(boundary);
this.entity = null;
}
/**
* Creates an instance using the specified {@link HttpMultipartMode} mode.
* Boundary and charset are set to {@code null}.
* @param mode the desired mode
*/
public MultipartEntity(final HttpMultipartMode mode) {
this(mode, null, null);
}
/**
* Creates an instance using mode {@link HttpMultipartMode#STRICT}
*/
public MultipartEntity() {
this(HttpMultipartMode.STRICT, null, null);
}
protected String generateContentType(
final String boundary,
final Charset charset) {
final StringBuilder buffer = new StringBuilder();
buffer.append("multipart/form-data; boundary=");
buffer.append(boundary);
if (charset != null) {
buffer.append("; charset=");
buffer.append(charset.name());
}
return buffer.toString();
}
protected String generateBoundary() {
final StringBuilder buffer = new StringBuilder();
final Random rand = new Random();
final int count = rand.nextInt(11) + 30; // a random size from 30 to 40
for (int i = 0; i < count; i++) {
buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
}
return buffer.toString();
}
private MultipartFormEntity getEntity() {
if (this.entity == null) {
this.entity = this.builder.buildEntity();
}
return this.entity;
}
public void addPart(final FormBodyPart bodyPart) {
this.builder.addPart(bodyPart);
this.entity = null;
}
public void addPart(final String name, final ContentBody contentBody) {
addPart(new FormBodyPart(name, contentBody));
}
public boolean isRepeatable() {
return getEntity().isRepeatable();
}
public boolean isChunked() {
return getEntity().isChunked();
}
public boolean isStreaming() {
return getEntity().isStreaming();
}
@Override
public long getContentLength() {
return getEntity().getContentLength();
}
@Override
public ContentType getContentType() {
return getEntity().getContentType();
}
@Override
public InputStream getContent() throws IOException, UnsupportedOperationException {
throw new UnsupportedOperationException(
"Multipart form entity does not implement #getContent()");
}
@Override
public void writeTo(final OutputStream outstream) throws IOException {
getEntity().writeTo(outstream);
}
}

View File

@ -0,0 +1,231 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.mime;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.apache.content.ByteArrayBody;
import com.foxinmy.weixin4j.http.apache.content.ContentBody;
import com.foxinmy.weixin4j.http.apache.content.FileBody;
import com.foxinmy.weixin4j.http.apache.content.InputStreamBody;
import com.foxinmy.weixin4j.http.apache.content.StringBody;
import com.foxinmy.weixin4j.util.NameValue;
/**
* Builder for multipart {@link HttpEntity}s.
*
* @since 4.3
*/
public class MultipartEntityBuilder {
/**
* The pool of ASCII chars to be used for generating a multipart boundary.
*/
private final static char[] MULTIPART_CHARS =
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.toCharArray();
private final static String DEFAULT_SUBTYPE = "form-data";
private ContentType contentType;
private HttpMultipartMode mode = HttpMultipartMode.STRICT;
private String boundary = null;
private Charset charset = null;
private List<FormBodyPart> bodyParts = null;
public static MultipartEntityBuilder create() {
return new MultipartEntityBuilder();
}
MultipartEntityBuilder() {
}
public MultipartEntityBuilder setMode(final HttpMultipartMode mode) {
this.mode = mode;
return this;
}
public MultipartEntityBuilder setLaxMode() {
this.mode = HttpMultipartMode.BROWSER_COMPATIBLE;
return this;
}
public MultipartEntityBuilder setStrictMode() {
this.mode = HttpMultipartMode.STRICT;
return this;
}
public MultipartEntityBuilder setBoundary(final String boundary) {
this.boundary = boundary;
return this;
}
/**
* @since 4.4
*/
public MultipartEntityBuilder setMimeSubtype(final String subType) {
this.contentType = ContentType.create("multipart/" + subType);
return this;
}
/**
* @since 4.4
*
* @deprecated (4.5) Use {@link #setContentType(org.apache.http.entity.ContentType)}.
*/
@Deprecated
public MultipartEntityBuilder seContentType(final ContentType contentType) {
return setContentType(contentType);
}
/**
* @since 4.5
*/
public MultipartEntityBuilder setContentType(final ContentType contentType) {
this.contentType = contentType;
return this;
}
public MultipartEntityBuilder setCharset(final Charset charset) {
this.charset = charset;
return this;
}
/**
* @since 4.4
*/
public MultipartEntityBuilder addPart(final FormBodyPart bodyPart) {
if (bodyPart == null) {
return this;
}
if (this.bodyParts == null) {
this.bodyParts = new ArrayList<FormBodyPart>();
}
this.bodyParts.add(bodyPart);
return this;
}
public MultipartEntityBuilder addPart(final String name, final ContentBody contentBody) {
return addPart(FormBodyPartBuilder.create(name, contentBody).build());
}
public MultipartEntityBuilder addTextBody(
final String name, final String text, final ContentType contentType) {
return addPart(name, new StringBody(text, contentType));
}
public MultipartEntityBuilder addTextBody(
final String name, final String text) {
return addTextBody(name, text, ContentType.DEFAULT_TEXT);
}
public MultipartEntityBuilder addBinaryBody(
final String name, final byte[] b, final ContentType contentType, final String filename) {
return addPart(name, new ByteArrayBody(b, contentType, filename));
}
public MultipartEntityBuilder addBinaryBody(
final String name, final byte[] b) {
return addBinaryBody(name, b, ContentType.DEFAULT_BINARY, null);
}
public MultipartEntityBuilder addBinaryBody(
final String name, final File file, final ContentType contentType, final String filename) {
return addPart(name, new FileBody(file, contentType, filename));
}
public MultipartEntityBuilder addBinaryBody(
final String name, final File file) {
return addBinaryBody(name, file, ContentType.DEFAULT_BINARY, file != null ? file.getName() : null);
}
public MultipartEntityBuilder addBinaryBody(
final String name, final InputStream stream, final ContentType contentType,
final String filename) {
return addPart(name, new InputStreamBody(stream, contentType, filename));
}
public MultipartEntityBuilder addBinaryBody(final String name, final InputStream stream) {
return addBinaryBody(name, stream, ContentType.DEFAULT_BINARY, null);
}
private String generateBoundary() {
final StringBuilder buffer = new StringBuilder();
final Random rand = new Random();
final int count = rand.nextInt(11) + 30; // a random size from 30 to 40
for (int i = 0; i < count; i++) {
buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
}
return buffer.toString();
}
public MultipartFormEntity buildEntity() {
String boundaryCopy = boundary;
if (boundaryCopy == null && contentType != null) {
boundaryCopy = contentType.getParameter("boundary");
}
if (boundaryCopy == null) {
boundaryCopy = generateBoundary();
}
Charset charsetCopy = charset;
if (charsetCopy == null && contentType != null) {
charsetCopy = contentType.getCharset();
}
final List<NameValue> paramsList = new ArrayList<NameValue>(2);
paramsList.add(new NameValue("boundary", boundaryCopy));
if (charsetCopy != null) {
paramsList.add(new NameValue("charset", charsetCopy.name()));
}
final NameValue[] params = paramsList.toArray(new NameValue[paramsList.size()]);
final ContentType contentTypeCopy = contentType != null ?
contentType.withParameters(params) :
ContentType.create("multipart/" + DEFAULT_SUBTYPE, params);
final List<FormBodyPart> bodyPartsCopy = bodyParts != null ? new ArrayList<FormBodyPart>(bodyParts) :
Collections.<FormBodyPart>emptyList();
final HttpMultipartMode modeCopy = mode != null ? mode : HttpMultipartMode.STRICT;
final AbstractMultipartForm form;
switch (modeCopy) {
case BROWSER_COMPATIBLE:
form = new HttpBrowserCompatibleMultipart(charsetCopy, boundaryCopy, bodyPartsCopy);
break;
case RFC6532:
form = new HttpRFC6532Multipart(charsetCopy, boundaryCopy, bodyPartsCopy);
break;
default:
form = new HttpStrictMultipart(charsetCopy, boundaryCopy, bodyPartsCopy);
}
return new MultipartFormEntity(form, contentTypeCopy, form.getTotalLength());
}
}

View File

@ -0,0 +1,97 @@
/*
* ====================================================================
* 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 com.foxinmy.weixin4j.http.apache.mime;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.entity.HttpEntity;
class MultipartFormEntity implements HttpEntity {
private final AbstractMultipartForm multipart;
private final ContentType contentType;
private final long contentLength;
MultipartFormEntity(
final AbstractMultipartForm multipart,
final ContentType contentType,
final long contentLength) {
super();
this.multipart = multipart;
this.contentType = contentType;
this.contentLength = contentLength;
}
AbstractMultipartForm getMultipart() {
return this.multipart;
}
public boolean isRepeatable() {
return this.contentLength != -1;
}
public boolean isChunked() {
return !isRepeatable();
}
public boolean isStreaming() {
return !isRepeatable();
}
@Override
public long getContentLength() {
return this.contentLength;
}
public ContentType getContentType() {
return this.contentType;
}
@Override
public InputStream getContent() throws IOException {
if (this.contentLength < 0) {
throw new IllegalArgumentException("Content length is unknown");
} else if (this.contentLength > 25 * 1024) {
throw new IllegalArgumentException("Content length is too long: " + this.contentLength);
}
final ByteArrayOutputStream outstream = new ByteArrayOutputStream();
writeTo(outstream);
outstream.flush();
return new ByteArrayInputStream(outstream.toByteArray());
}
@Override
public void writeTo(final OutputStream outstream) throws IOException {
this.multipart.writeTo(outstream);
}
}

View File

@ -33,7 +33,7 @@ import com.foxinmy.weixin4j.http.HttpHeaders;
import com.foxinmy.weixin4j.http.HttpMethod;
import com.foxinmy.weixin4j.http.HttpRequest;
import com.foxinmy.weixin4j.http.HttpResponse;
import com.foxinmy.weixin4j.http.apache.MultipartEntity;
import com.foxinmy.weixin4j.http.apache.mime.MultipartEntity;
import com.foxinmy.weixin4j.http.entity.HttpEntity;
import com.foxinmy.weixin4j.util.Consts;
import com.foxinmy.weixin4j.util.StringUtil;

View File

@ -31,7 +31,7 @@ import com.foxinmy.weixin4j.http.HttpClientException;
import com.foxinmy.weixin4j.http.HttpHeaders;
import com.foxinmy.weixin4j.http.HttpMethod;
import com.foxinmy.weixin4j.http.HttpRequest;
import com.foxinmy.weixin4j.http.apache.MultipartEntity;
import com.foxinmy.weixin4j.http.apache.mime.MultipartEntity;
import com.foxinmy.weixin4j.http.entity.HttpEntity;
import com.foxinmy.weixin4j.util.StringUtil;

View File

@ -17,9 +17,9 @@ import com.foxinmy.weixin4j.http.HttpRequest;
import com.foxinmy.weixin4j.http.HttpResponse;
import com.foxinmy.weixin4j.http.MimeType;
import com.foxinmy.weixin4j.http.URLParameter;
import com.foxinmy.weixin4j.http.apache.FormBodyPart;
import com.foxinmy.weixin4j.http.apache.HttpMultipartMode;
import com.foxinmy.weixin4j.http.apache.MultipartEntity;
import com.foxinmy.weixin4j.http.apache.mime.FormBodyPart;
import com.foxinmy.weixin4j.http.apache.mime.HttpMultipartMode;
import com.foxinmy.weixin4j.http.apache.mime.MultipartEntityBuilder;
import com.foxinmy.weixin4j.http.entity.FormUrlEntity;
import com.foxinmy.weixin4j.http.entity.HttpEntity;
import com.foxinmy.weixin4j.http.entity.StringEntity;
@ -85,13 +85,13 @@ public class WeixinRequestExecutor {
*/
public WeixinResponse post(String url, FormBodyPart... bodyParts)
throws WeixinException {
MultipartEntity entity = new MultipartEntity(
HttpMultipartMode.BROWSER_COMPATIBLE, null, Consts.UTF_8);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
for (FormBodyPart bodyPart : bodyParts) {
entity.addPart(bodyPart);
builder.addPart(bodyPart);
}
HttpRequest request = new HttpRequest(HttpMethod.POST, url);
request.setEntity(entity);
request.setEntity(builder.setMode(HttpMultipartMode.RFC6532)
.buildEntity());
return doRequest(request);
}

View File

@ -25,11 +25,10 @@
*
*/
package com.foxinmy.weixin4j.http.apache;
package com.foxinmy.weixin4j.util;
import java.io.Serializable;
/**
* A resizable byte array.
*
@ -48,33 +47,30 @@ public final class ByteArrayBuffer implements Serializable {
*
* @param capacity the capacity
*/
public ByteArrayBuffer(int capacity) {
public ByteArrayBuffer(final int capacity) {
super();
if (capacity < 0) {
throw new IllegalArgumentException("Buffer capacity may not be negative");
}
this.buffer = new byte[capacity];
}
private void expand(int newlen) {
byte newbuffer[] = new byte[Math.max(this.buffer.length << 1, newlen)];
private void expand(final int newlen) {
final byte newbuffer[] = new byte[Math.max(this.buffer.length << 1, newlen)];
System.arraycopy(this.buffer, 0, newbuffer, 0, this.len);
this.buffer = newbuffer;
}
/**
* Appends <code>len</code> bytes to this buffer from the given source
* array starting at index <code>off</code>. The capacity of the buffer
* is increased, if necessary, to accommodate all <code>len</code> bytes.
* Appends {@code len} bytes to this buffer from the given source
* array starting at index {@code off}. The capacity of the buffer
* is increased, if necessary, to accommodate all {@code len} bytes.
*
* @param b the bytes to be appended.
* @param off the index of the first byte to append.
* @param len the number of bytes to append.
* @throws IndexOutOfBoundsException if <code>off</code> if out of
* range, <code>len</code> is negative, or
* <code>off</code> + <code>len</code> is out of range.
* @throws IndexOutOfBoundsException if {@code off} if out of
* range, {@code len} is negative, or
* {@code off} + {@code len} is out of range.
*/
public void append(final byte[] b, int off, int len) {
public void append(final byte[] b, final int off, final int len) {
if (b == null) {
return;
}
@ -85,7 +81,7 @@ public final class ByteArrayBuffer implements Serializable {
if (len == 0) {
return;
}
int newlen = this.len + len;
final int newlen = this.len + len;
if (newlen > this.buffer.length) {
expand(newlen);
}
@ -94,13 +90,13 @@ public final class ByteArrayBuffer implements Serializable {
}
/**
* Appends <code>b</code> byte to this buffer. The capacity of the buffer
* Appends {@code b} byte to this buffer. The capacity of the buffer
* is increased, if necessary, to accommodate the additional byte.
*
* @param b the byte to be appended.
*/
public void append(int b) {
int newlen = this.len + 1;
public void append(final int b) {
final int newlen = this.len + 1;
if (newlen > this.buffer.length) {
expand(newlen);
}
@ -109,20 +105,20 @@ public final class ByteArrayBuffer implements Serializable {
}
/**
* Appends <code>len</code> chars to this buffer from the given source
* array starting at index <code>off</code>. The capacity of the buffer
* is increased if necessary to accommodate all <code>len</code> chars.
* Appends {@code len} chars to this buffer from the given source
* array starting at index {@code off}. The capacity of the buffer
* is increased if necessary to accommodate all {@code len} chars.
* <p>
* The chars are converted to bytes using simple cast.
*
* @param b the chars to be appended.
* @param off the index of the first char to append.
* @param len the number of bytes to append.
* @throws IndexOutOfBoundsException if <code>off</code> if out of
* range, <code>len</code> is negative, or
* <code>off</code> + <code>len</code> is out of range.
* @throws IndexOutOfBoundsException if {@code off} if out of
* range, {@code len} is negative, or
* {@code off} + {@code len} is out of range.
*/
public void append(final char[] b, int off, int len) {
public void append(final char[] b, final int off, final int len) {
if (b == null) {
return;
}
@ -133,8 +129,8 @@ public final class ByteArrayBuffer implements Serializable {
if (len == 0) {
return;
}
int oldlen = this.len;
int newlen = oldlen + len;
final int oldlen = this.len;
final int newlen = oldlen + len;
if (newlen > this.buffer.length) {
expand(newlen);
}
@ -145,21 +141,21 @@ public final class ByteArrayBuffer implements Serializable {
}
/**
* Appends <code>len</code> chars to this buffer from the given source
* char array buffer starting at index <code>off</code>. The capacity
* Appends {@code len} chars to this buffer from the given source
* char array buffer starting at index {@code off}. The capacity
* of the buffer is increased if necessary to accommodate all
* <code>len</code> chars.
* {@code len} chars.
* <p>
* The chars are converted to bytes using simple cast.
*
* @param b the chars to be appended.
* @param off the index of the first char to append.
* @param len the number of bytes to append.
* @throws IndexOutOfBoundsException if <code>off</code> if out of
* range, <code>len</code> is negative, or
* <code>off</code> + <code>len</code> is out of range.
* @throws IndexOutOfBoundsException if {@code off} if out of
* range, {@code len} is negative, or
* {@code off} + {@code len} is out of range.
*/
public void append(final CharArrayBuffer b, int off, int len) {
public void append(final CharArrayBuffer b, final int off, final int len) {
if (b == null) {
return;
}
@ -179,7 +175,7 @@ public final class ByteArrayBuffer implements Serializable {
* @return byte array
*/
public byte[] toByteArray() {
byte[] b = new byte[this.len];
final byte[] b = new byte[this.len];
if (this.len > 0) {
System.arraycopy(this.buffer, 0, b, 0, this.len);
}
@ -187,16 +183,16 @@ public final class ByteArrayBuffer implements Serializable {
}
/**
* Returns the <code>byte</code> value in this buffer at the specified
* Returns the {@code byte} value in this buffer at the specified
* index. The index argument must be greater than or equal to
* <code>0</code>, and less than the length of this buffer.
* {@code 0}, and less than the length of this buffer.
*
* @param i the index of the desired byte value.
* @return the byte value at the specified index.
* @throws IndexOutOfBoundsException if <code>index</code> is
* @throws IndexOutOfBoundsException if {@code index} is
* negative or greater than or equal to {@link #length()}.
*/
public int byteAt(int i) {
public int byteAt(final int i) {
return this.buffer[i];
}
@ -223,18 +219,18 @@ public final class ByteArrayBuffer implements Serializable {
/**
* Ensures that the capacity is at least equal to the specified minimum.
* If the current capacity is less than the argument, then a new internal
* array is allocated with greater capacity. If the <code>required</code>
* array is allocated with greater capacity. If the {@code required}
* argument is non-positive, this method takes no action.
*
* @param required the minimum required capacity.
*
* @since 4.1
*/
public void ensureCapacity(int required) {
public void ensureCapacity(final int required) {
if (required <= 0) {
return;
}
int available = this.buffer.length - this.len;
final int available = this.buffer.length - this.len;
if (required > available) {
expand(this.len + required);
}
@ -252,14 +248,14 @@ public final class ByteArrayBuffer implements Serializable {
/**
* Sets the length of the buffer. The new length value is expected to be
* less than the current capacity and greater than or equal to
* <code>0</code>.
* {@code 0}.
*
* @param len the new length
* @throws IndexOutOfBoundsException if the
* <code>len</code> argument is greater than the current
* capacity of the buffer or less than <code>0</code>.
* {@code len} argument is greater than the current
* capacity of the buffer or less than {@code 0}.
*/
public void setLength(int len) {
public void setLength(final int len) {
if (len < 0 || len > this.buffer.length) {
throw new IndexOutOfBoundsException("len: "+len+" < 0 or > buffer len: "+this.buffer.length);
}
@ -267,9 +263,9 @@ public final class ByteArrayBuffer implements Serializable {
}
/**
* Returns <code>true</code> if this buffer is empty, that is, its
* {@link #length()} is equal to <code>0</code>.
* @return <code>true</code> if this buffer is empty, <code>false</code>
* Returns {@code true} if this buffer is empty, that is, its
* {@link #length()} is equal to {@code 0}.
* @return {@code true} if this buffer is empty, {@code false}
* otherwise.
*/
public boolean isEmpty() {
@ -277,9 +273,9 @@ public final class ByteArrayBuffer implements Serializable {
}
/**
* Returns <code>true</code> if this buffer is full, that is, its
* Returns {@code true} if this buffer is full, that is, its
* {@link #length()} is equal to its {@link #capacity()}.
* @return <code>true</code> if this buffer is full, <code>false</code>
* @return {@code true} if this buffer is full, {@code false}
* otherwise.
*/
public boolean isFull() {
@ -289,30 +285,32 @@ public final class ByteArrayBuffer implements Serializable {
/**
* Returns the index within this buffer of the first occurrence of the
* specified byte, starting the search at the specified
* <code>beginIndex</code> and finishing at <code>endIndex</code>.
* {@code beginIndex} and finishing at {@code endIndex}.
* If no such byte occurs in this buffer within the specified bounds,
* <code>-1</code> is returned.
* {@code -1} is returned.
* <p>
* There is no restriction on the value of <code>beginIndex</code> and
* <code>endIndex</code>. If <code>beginIndex</code> is negative,
* it has the same effect as if it were zero. If <code>endIndex</code> is
* There is no restriction on the value of {@code beginIndex} and
* {@code endIndex}. If {@code beginIndex} is negative,
* it has the same effect as if it were zero. If {@code endIndex} is
* greater than {@link #length()}, it has the same effect as if it were
* {@link #length()}. If the <code>beginIndex</code> is greater than
* the <code>endIndex</code>, <code>-1</code> is returned.
* {@link #length()}. If the {@code beginIndex} is greater than
* the {@code endIndex}, {@code -1} is returned.
*
* @param b the byte to search for.
* @param beginIndex the index to start the search from.
* @param endIndex the index to finish the search at.
* @param from the index to start the search from.
* @param to the index to finish the search at.
* @return the index of the first occurrence of the byte in the buffer
* within the given bounds, or <code>-1</code> if the byte does
* within the given bounds, or {@code -1} if the byte does
* not occur.
*
* @since 4.1
*/
public int indexOf(byte b, int beginIndex, int endIndex) {
public int indexOf(final byte b, final int from, final int to) {
int beginIndex = from;
if (beginIndex < 0) {
beginIndex = 0;
}
int endIndex = to;
if (endIndex > this.len) {
endIndex = this.len;
}
@ -329,17 +327,17 @@ public final class ByteArrayBuffer implements Serializable {
/**
* Returns the index within this buffer of the first occurrence of the
* specified byte, starting the search at <code>0</code> and finishing
* specified byte, starting the search at {@code 0} and finishing
* at {@link #length()}. If no such byte occurs in this buffer within
* those bounds, <code>-1</code> is returned.
* those bounds, {@code -1} is returned.
*
* @param b the byte to search for.
* @return the index of the first occurrence of the byte in the
* buffer, or <code>-1</code> if the byte does not occur.
* buffer, or {@code -1} if the byte does not occur.
*
* @since 4.1
*/
public int indexOf(byte b) {
public int indexOf(final byte b) {
return indexOf(b, 0, this.len);
}
}

View File

@ -25,16 +25,20 @@
*
*/
package com.foxinmy.weixin4j.http.apache;
package com.foxinmy.weixin4j.util;
import java.io.Serializable;
import java.nio.CharBuffer;
import com.foxinmy.weixin4j.http.HTTP;
/**
* A resizable char array.
*
* @since 4.0
*/
public final class CharArrayBuffer implements Serializable {
public final class CharArrayBuffer implements CharSequence, Serializable {
private static final long serialVersionUID = -6208952725094867135L;
@ -47,33 +51,30 @@ public final class CharArrayBuffer implements Serializable {
*
* @param capacity the capacity
*/
public CharArrayBuffer(int capacity) {
public CharArrayBuffer(final int capacity) {
super();
if (capacity < 0) {
throw new IllegalArgumentException("Buffer capacity may not be negative");
}
this.buffer = new char[capacity];
}
private void expand(int newlen) {
char newbuffer[] = new char[Math.max(this.buffer.length << 1, newlen)];
private void expand(final int newlen) {
final char newbuffer[] = new char[Math.max(this.buffer.length << 1, newlen)];
System.arraycopy(this.buffer, 0, newbuffer, 0, this.len);
this.buffer = newbuffer;
}
/**
* Appends <code>len</code> chars to this buffer from the given source
* array starting at index <code>off</code>. The capacity of the buffer
* is increased, if necessary, to accommodate all <code>len</code> chars.
* Appends {@code len} chars to this buffer from the given source
* array starting at index {@code off}. The capacity of the buffer
* is increased, if necessary, to accommodate all {@code len} chars.
*
* @param b the chars to be appended.
* @param off the index of the first char to append.
* @param len the number of chars to append.
* @throws IndexOutOfBoundsException if <code>off</code> is out of
* range, <code>len</code> is negative, or
* <code>off</code> + <code>len</code> is out of range.
* @throws IndexOutOfBoundsException if {@code off} is out of
* range, {@code len} is negative, or
* {@code off} + {@code len} is out of range.
*/
public void append(final char[] b, int off, int len) {
public void append(final char[] b, final int off, final int len) {
if (b == null) {
return;
}
@ -84,7 +85,7 @@ public final class CharArrayBuffer implements Serializable {
if (len == 0) {
return;
}
int newlen = this.len + len;
final int newlen = this.len + len;
if (newlen > this.buffer.length) {
expand(newlen);
}
@ -98,33 +99,31 @@ public final class CharArrayBuffer implements Serializable {
*
* @param str the string.
*/
public void append(String str) {
if (str == null) {
str = "null";
}
int strlen = str.length();
int newlen = this.len + strlen;
public void append(final String str) {
final String s = str != null ? str : "null";
final int strlen = s.length();
final int newlen = this.len + strlen;
if (newlen > this.buffer.length) {
expand(newlen);
}
str.getChars(0, strlen, this.buffer, this.len);
s.getChars(0, strlen, this.buffer, this.len);
this.len = newlen;
}
/**
* Appends <code>len</code> chars to this buffer from the given source
* buffer starting at index <code>off</code>. The capacity of the
* Appends {@code len} chars to this buffer from the given source
* buffer starting at index {@code off}. The capacity of the
* destination buffer is increased, if necessary, to accommodate all
* <code>len</code> chars.
* {@code len} chars.
*
* @param b the source buffer to be appended.
* @param off the index of the first char to append.
* @param len the number of chars to append.
* @throws IndexOutOfBoundsException if <code>off</code> is out of
* range, <code>len</code> is negative, or
* <code>off</code> + <code>len</code> is out of range.
* @throws IndexOutOfBoundsException if {@code off} is out of
* range, {@code len} is negative, or
* {@code off} + {@code len} is out of range.
*/
public void append(final CharArrayBuffer b, int off, int len) {
public void append(final CharArrayBuffer b, final int off, final int len) {
if (b == null) {
return;
}
@ -133,7 +132,7 @@ public final class CharArrayBuffer implements Serializable {
/**
* Appends all chars to this buffer from the given source buffer starting
* at index <code>0</code>. The capacity of the destination buffer is
* at index {@code 0}. The capacity of the destination buffer is
* increased, if necessary, to accommodate all {@link #length()} chars.
*
* @param b the source buffer to be appended.
@ -146,13 +145,13 @@ public final class CharArrayBuffer implements Serializable {
}
/**
* Appends <code>ch</code> char to this buffer. The capacity of the buffer
* Appends {@code ch} char to this buffer. The capacity of the buffer
* is increased, if necessary, to accommodate the additional char.
*
* @param ch the char to be appended.
*/
public void append(char ch) {
int newlen = this.len + 1;
public void append(final char ch) {
final int newlen = this.len + 1;
if (newlen > this.buffer.length) {
expand(newlen);
}
@ -161,20 +160,20 @@ public final class CharArrayBuffer implements Serializable {
}
/**
* Appends <code>len</code> bytes to this buffer from the given source
* array starting at index <code>off</code>. The capacity of the buffer
* is increased, if necessary, to accommodate all <code>len</code> bytes.
* Appends {@code len} bytes to this buffer from the given source
* array starting at index {@code off}. The capacity of the buffer
* is increased, if necessary, to accommodate all {@code len} bytes.
* <p>
* The bytes are converted to chars using simple cast.
*
* @param b the bytes to be appended.
* @param off the index of the first byte to append.
* @param len the number of bytes to append.
* @throws IndexOutOfBoundsException if <code>off</code> is out of
* range, <code>len</code> is negative, or
* <code>off</code> + <code>len</code> is out of range.
* @throws IndexOutOfBoundsException if {@code off} is out of
* range, {@code len} is negative, or
* {@code off} + {@code len} is out of range.
*/
public void append(final byte[] b, int off, int len) {
public void append(final byte[] b, final int off, final int len) {
if (b == null) {
return;
}
@ -185,8 +184,8 @@ public final class CharArrayBuffer implements Serializable {
if (len == 0) {
return;
}
int oldlen = this.len;
int newlen = oldlen + len;
final int oldlen = this.len;
final int newlen = oldlen + len;
if (newlen > this.buffer.length) {
expand(newlen);
}
@ -197,20 +196,20 @@ public final class CharArrayBuffer implements Serializable {
}
/**
* Appends <code>len</code> bytes to this buffer from the given source
* array starting at index <code>off</code>. The capacity of the buffer
* is increased, if necessary, to accommodate all <code>len</code> bytes.
* Appends {@code len} bytes to this buffer from the given source
* array starting at index {@code off}. The capacity of the buffer
* is increased, if necessary, to accommodate all {@code len} bytes.
* <p>
* The bytes are converted to chars using simple cast.
*
* @param b the bytes to be appended.
* @param off the index of the first byte to append.
* @param len the number of bytes to append.
* @throws IndexOutOfBoundsException if <code>off</code> is out of
* range, <code>len</code> is negative, or
* <code>off</code> + <code>len</code> is out of range.
* @throws IndexOutOfBoundsException if {@code off} is out of
* range, {@code len} is negative, or
* {@code off} + {@code len} is out of range.
*/
public void append(final ByteArrayBuffer b, int off, int len) {
public void append(final ByteArrayBuffer b, final int off, final int len) {
if (b == null) {
return;
}
@ -241,7 +240,7 @@ public final class CharArrayBuffer implements Serializable {
* @return char array
*/
public char[] toCharArray() {
char[] b = new char[this.len];
final char[] b = new char[this.len];
if (this.len > 0) {
System.arraycopy(this.buffer, 0, b, 0, this.len);
}
@ -249,16 +248,17 @@ public final class CharArrayBuffer implements Serializable {
}
/**
* Returns the <code>char</code> value in this buffer at the specified
* Returns the {@code char} value in this buffer at the specified
* index. The index argument must be greater than or equal to
* <code>0</code>, and less than the length of this buffer.
* {@code 0}, and less than the length of this buffer.
*
* @param i the index of the desired char value.
* @return the char value at the specified index.
* @throws IndexOutOfBoundsException if <code>index</code> is
* @throws IndexOutOfBoundsException if {@code index} is
* negative or greater than or equal to {@link #length()}.
*/
public char charAt(int i) {
@Override
public char charAt(final int i) {
return this.buffer[i];
}
@ -287,6 +287,7 @@ public final class CharArrayBuffer implements Serializable {
*
* @return the length of the buffer
*/
@Override
public int length() {
return this.len;
}
@ -294,16 +295,16 @@ public final class CharArrayBuffer implements Serializable {
/**
* Ensures that the capacity is at least equal to the specified minimum.
* If the current capacity is less than the argument, then a new internal
* array is allocated with greater capacity. If the <code>required</code>
* array is allocated with greater capacity. If the {@code required}
* argument is non-positive, this method takes no action.
*
* @param required the minimum required capacity.
*/
public void ensureCapacity(int required) {
public void ensureCapacity(final int required) {
if (required <= 0) {
return;
}
int available = this.buffer.length - this.len;
final int available = this.buffer.length - this.len;
if (required > available) {
expand(this.len + required);
}
@ -312,14 +313,14 @@ public final class CharArrayBuffer implements Serializable {
/**
* Sets the length of the buffer. The new length value is expected to be
* less than the current capacity and greater than or equal to
* <code>0</code>.
* {@code 0}.
*
* @param len the new length
* @throws IndexOutOfBoundsException if the
* <code>len</code> argument is greater than the current
* capacity of the buffer or less than <code>0</code>.
* {@code len} argument is greater than the current
* capacity of the buffer or less than {@code 0}.
*/
public void setLength(int len) {
public void setLength(final int len) {
if (len < 0 || len > this.buffer.length) {
throw new IndexOutOfBoundsException("len: "+len+" < 0 or > buffer len: "+this.buffer.length);
}
@ -327,9 +328,9 @@ public final class CharArrayBuffer implements Serializable {
}
/**
* Returns <code>true</code> if this buffer is empty, that is, its
* {@link #length()} is equal to <code>0</code>.
* @return <code>true</code> if this buffer is empty, <code>false</code>
* Returns {@code true} if this buffer is empty, that is, its
* {@link #length()} is equal to {@code 0}.
* @return {@code true} if this buffer is empty, {@code false}
* otherwise.
*/
public boolean isEmpty() {
@ -337,9 +338,9 @@ public final class CharArrayBuffer implements Serializable {
}
/**
* Returns <code>true</code> if this buffer is full, that is, its
* Returns {@code true} if this buffer is full, that is, its
* {@link #length()} is equal to its {@link #capacity()}.
* @return <code>true</code> if this buffer is full, <code>false</code>
* @return {@code true} if this buffer is full, {@code false}
* otherwise.
*/
public boolean isFull() {
@ -349,28 +350,30 @@ public final class CharArrayBuffer implements Serializable {
/**
* Returns the index within this buffer of the first occurrence of the
* specified character, starting the search at the specified
* <code>beginIndex</code> and finishing at <code>endIndex</code>.
* {@code beginIndex} and finishing at {@code endIndex}.
* If no such character occurs in this buffer within the specified bounds,
* <code>-1</code> is returned.
* {@code -1} is returned.
* <p>
* There is no restriction on the value of <code>beginIndex</code> and
* <code>endIndex</code>. If <code>beginIndex</code> is negative,
* it has the same effect as if it were zero. If <code>endIndex</code> is
* There is no restriction on the value of {@code beginIndex} and
* {@code endIndex}. If {@code beginIndex} is negative,
* it has the same effect as if it were zero. If {@code endIndex} is
* greater than {@link #length()}, it has the same effect as if it were
* {@link #length()}. If the <code>beginIndex</code> is greater than
* the <code>endIndex</code>, <code>-1</code> is returned.
* {@link #length()}. If the {@code beginIndex} is greater than
* the {@code endIndex}, {@code -1} is returned.
*
* @param ch the char to search for.
* @param beginIndex the index to start the search from.
* @param endIndex the index to finish the search at.
* @param from the index to start the search from.
* @param to the index to finish the search at.
* @return the index of the first occurrence of the character in the buffer
* within the given bounds, or <code>-1</code> if the character does
* within the given bounds, or {@code -1} if the character does
* not occur.
*/
public int indexOf(int ch, int beginIndex, int endIndex) {
public int indexOf(final int ch, final int from, final int to) {
int beginIndex = from;
if (beginIndex < 0) {
beginIndex = 0;
}
int endIndex = to;
if (endIndex > this.len) {
endIndex = this.len;
}
@ -387,53 +390,33 @@ public final class CharArrayBuffer implements Serializable {
/**
* Returns the index within this buffer of the first occurrence of the
* specified character, starting the search at <code>0</code> and finishing
* specified character, starting the search at {@code 0} and finishing
* at {@link #length()}. If no such character occurs in this buffer within
* those bounds, <code>-1</code> is returned.
* those bounds, {@code -1} is returned.
*
* @param ch the char to search for.
* @return the index of the first occurrence of the character in the
* buffer, or <code>-1</code> if the character does not occur.
* buffer, or {@code -1} if the character does not occur.
*/
public int indexOf(int ch) {
public int indexOf(final int ch) {
return indexOf(ch, 0, this.len);
}
/**
* Returns a substring of this buffer. The substring begins at the specified
* <code>beginIndex</code> and extends to the character at index
* <code>endIndex - 1</code>.
* {@code beginIndex} and extends to the character at index
* {@code endIndex - 1}.
*
* @param beginIndex the beginning index, inclusive.
* @param endIndex the ending index, exclusive.
* @return the specified substring.
* @exception StringIndexOutOfBoundsException if the
* <code>beginIndex</code> is negative, or
* <code>endIndex</code> is larger than the length of this
* buffer, or <code>beginIndex</code> is larger than
* <code>endIndex</code>.
* @throws StringIndexOutOfBoundsException if the
* {@code beginIndex} is negative, or
* {@code endIndex} is larger than the length of this
* buffer, or {@code beginIndex} is larger than
* {@code endIndex}.
*/
public String substring(int beginIndex, int endIndex) {
return new String(this.buffer, beginIndex, endIndex - beginIndex);
}
/**
* Returns a substring of this buffer with leading and trailing whitespace
* omitted. The substring begins with the first non-whitespace character
* from <code>beginIndex</code> and extends to the last
* non-whitespace character with the index lesser than
* <code>endIndex</code>.
*
* @param beginIndex the beginning index, inclusive.
* @param endIndex the ending index, exclusive.
* @return the specified substring.
* @exception IndexOutOfBoundsException if the
* <code>beginIndex</code> is negative, or
* <code>endIndex</code> is larger than the length of this
* buffer, or <code>beginIndex</code> is larger than
* <code>endIndex</code>.
*/
public String substringTrimmed(int beginIndex, int endIndex) {
public String substring(final int beginIndex, final int endIndex) {
if (beginIndex < 0) {
throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex);
}
@ -443,15 +426,64 @@ public final class CharArrayBuffer implements Serializable {
if (beginIndex > endIndex) {
throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex);
}
while (beginIndex < endIndex && HTTP.isWhitespace(this.buffer[beginIndex])) {
beginIndex++;
}
while (endIndex > beginIndex && HTTP.isWhitespace(this.buffer[endIndex - 1])) {
endIndex--;
}
return new String(this.buffer, beginIndex, endIndex - beginIndex);
}
/**
* Returns a substring of this buffer with leading and trailing whitespace
* omitted. The substring begins with the first non-whitespace character
* from {@code beginIndex} and extends to the last
* non-whitespace character with the index lesser than
* {@code endIndex}.
*
* @param beginIndex the beginning index, inclusive.
* @param endIndex the ending index, exclusive.
* @return the specified substring.
* @throws IndexOutOfBoundsException if the
* {@code beginIndex} is negative, or
* {@code endIndex} is larger than the length of this
* buffer, or {@code beginIndex} is larger than
* {@code endIndex}.
*/
public String substringTrimmed(final int beginIndex, final int endIndex) {
if (beginIndex < 0) {
throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex);
}
if (endIndex > this.len) {
throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len);
}
if (beginIndex > endIndex) {
throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex);
}
int beginIndex0 = beginIndex;
int endIndex0 = endIndex;
while (beginIndex0 < endIndex && HTTP.isWhitespace(this.buffer[beginIndex0])) {
beginIndex0++;
}
while (endIndex0 > beginIndex0 && HTTP.isWhitespace(this.buffer[endIndex0 - 1])) {
endIndex0--;
}
return new String(this.buffer, beginIndex0, endIndex0 - beginIndex0);
}
/**
* {@inheritDoc}
* @since 4.4
*/
@Override
public CharSequence subSequence(final int beginIndex, final int endIndex) {
if (beginIndex < 0) {
throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex);
}
if (endIndex > this.len) {
throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len);
}
if (beginIndex > endIndex) {
throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex);
}
return CharBuffer.wrap(this.buffer, beginIndex, endIndex);
}
@Override
public String toString() {
return new String(this.buffer, 0, this.len);

View File

@ -10,8 +10,8 @@ import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.MimeType;
import com.foxinmy.weixin4j.http.apache.FormBodyPart;
import com.foxinmy.weixin4j.http.apache.InputStreamBody;
import com.foxinmy.weixin4j.http.apache.content.InputStreamBody;
import com.foxinmy.weixin4j.http.apache.mime.FormBodyPart;
import com.foxinmy.weixin4j.http.weixin.ApiResult;
import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
import com.foxinmy.weixin4j.model.Token;

View File

@ -19,10 +19,10 @@ import com.foxinmy.weixin4j.http.HttpMethod;
import com.foxinmy.weixin4j.http.HttpRequest;
import com.foxinmy.weixin4j.http.HttpResponse;
import com.foxinmy.weixin4j.http.MimeType;
import com.foxinmy.weixin4j.http.apache.ByteArrayBody;
import com.foxinmy.weixin4j.http.apache.FormBodyPart;
import com.foxinmy.weixin4j.http.apache.InputStreamBody;
import com.foxinmy.weixin4j.http.apache.StringBody;
import com.foxinmy.weixin4j.http.apache.content.ByteArrayBody;
import com.foxinmy.weixin4j.http.apache.content.InputStreamBody;
import com.foxinmy.weixin4j.http.apache.content.StringBody;
import com.foxinmy.weixin4j.http.apache.mime.FormBodyPart;
import com.foxinmy.weixin4j.http.entity.StringEntity;
import com.foxinmy.weixin4j.http.weixin.ApiResult;
import com.foxinmy.weixin4j.http.weixin.WeixinResponse;

View File

@ -22,9 +22,9 @@ import com.foxinmy.weixin4j.http.HttpMethod;
import com.foxinmy.weixin4j.http.HttpRequest;
import com.foxinmy.weixin4j.http.HttpResponse;
import com.foxinmy.weixin4j.http.MimeType;
import com.foxinmy.weixin4j.http.apache.ByteArrayBody;
import com.foxinmy.weixin4j.http.apache.FormBodyPart;
import com.foxinmy.weixin4j.http.apache.InputStreamBody;
import com.foxinmy.weixin4j.http.apache.content.ByteArrayBody;
import com.foxinmy.weixin4j.http.apache.content.InputStreamBody;
import com.foxinmy.weixin4j.http.apache.mime.FormBodyPart;
import com.foxinmy.weixin4j.http.weixin.ApiResult;
import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
import com.foxinmy.weixin4j.model.Token;