From 3779c33046fd9fb43e623409330dd0b40cdf9370 Mon Sep 17 00:00:00 2001 From: jinyu Date: Sun, 4 Feb 2018 14:48:10 +0800 Subject: [PATCH] media --- .../foxinmy/weixin4j/http/ContentType.java | 138 ++++++++- .../weixin4j/http/{apache => }/HTTP.java | 50 +++- .../weixin4j/http/HeaderValueFormatter.java | 253 ++++++++++++++++ .../http/apache/ContentDescriptor.java | 89 ------ .../foxinmy/weixin4j/http/apache/Header.java | 146 --------- .../weixin4j/http/apache/HttpMultipart.java | 278 ------------------ .../http/apache/HttpMultipartMode.java | 12 - .../weixin4j/http/apache/InputStreamBody.java | 99 ------- .../weixin4j/http/apache/MultipartEntity.java | 174 ----------- .../{ => content}/AbstractContentBody.java | 60 ++-- .../apache/{ => content}/ByteArrayBody.java | 29 +- .../http/apache/content/ContentBody.java | 86 ++++++ .../http/apache/content/FileBody.java | 134 +++++++++ .../InputStreamBody.java} | 87 +++--- .../http/apache/{ => content}/StringBody.java | 79 +++-- .../apache/mime/AbstractMultipartForm.java | 194 ++++++++++++ .../http/apache/{ => mime}/FormBodyPart.java | 63 ++-- .../http/apache/mime/FormBodyPartBuilder.java | 149 ++++++++++ .../weixin4j/http/apache/mime/Header.java | 144 +++++++++ .../mime/HttpBrowserCompatibleMultipart.java | 78 +++++ .../http/apache/mime/HttpMultipart.java | 141 +++++++++ .../HttpMultipartMode.java} | 16 +- .../apache/mime/HttpRFC6532Multipart.java | 71 +++++ .../http/apache/mime/HttpStrictMultipart.java | 71 +++++ .../weixin4j/http/apache/{ => mime}/MIME.java | 9 +- .../http/apache/mime/MinimalField.java | 63 ++++ .../http/apache/mime/MultipartEntity.java | 163 ++++++++++ .../apache/mime/MultipartEntityBuilder.java | 231 +++++++++++++++ .../http/apache/mime/MultipartFormEntity.java | 97 ++++++ .../http/support/apache3/HttpComponent3.java | 2 +- .../http/support/apache4/HttpComponent4.java | 2 +- .../http/weixin/WeixinRequestExecutor.java | 14 +- .../apache => util}/ByteArrayBuffer.java | 134 +++++---- .../apache => util}/CharArrayBuffer.java | 246 +++++++++------- .../foxinmy/weixin4j/mp/api/CustomApi.java | 4 +- .../com/foxinmy/weixin4j/mp/api/MediaApi.java | 8 +- .../com/foxinmy/weixin4j/qy/api/MediaApi.java | 6 +- 37 files changed, 2469 insertions(+), 1151 deletions(-) rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/{apache => }/HTTP.java (71%) create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HeaderValueFormatter.java delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ContentDescriptor.java delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/Header.java delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/HttpMultipart.java delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/HttpMultipartMode.java delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/InputStreamBody.java delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/MultipartEntity.java rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/{ => content}/AbstractContentBody.java (59%) rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/{ => content}/ByteArrayBody.java (80%) create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/ContentBody.java create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/FileBody.java rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/{FileBody.java => content/InputStreamBody.java} (52%) rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/{ => content}/StringBody.java (68%) create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/AbstractMultipartForm.java rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/{ => mime}/FormBodyPart.java (62%) create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/FormBodyPartBuilder.java create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/Header.java create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpBrowserCompatibleMultipart.java create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpMultipart.java rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/{ContentBody.java => mime/HttpMultipartMode.java} (80%) create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpRFC6532Multipart.java create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpStrictMultipart.java rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/{ => mime}/MIME.java (87%) create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MinimalField.java create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartEntity.java create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartEntityBuilder.java create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartFormEntity.java rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/{http/apache => util}/ByteArrayBuffer.java (66%) rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/{http/apache => util}/CharArrayBuffer.java (58%) diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/ContentType.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/ContentType.java index 3f3beaa6..90ef02e4 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/ContentType.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/ContentType.java @@ -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 paramMap = new LinkedHashMap(); + 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 newParams = new ArrayList( + paramMap.size() + 1); + if (this.charset != null && !paramMap.containsKey("charset")) { + newParams.add(new NameValue("charset", this.charset.name())); + } + for (final Map.Entry entry : paramMap.entrySet()) { + newParams.add(new NameValue(entry.getKey(), entry.getValue())); + } + return create(this.getMimeType(), + newParams.toArray(new NameValue[newParams.size()]), true); + } } \ No newline at end of file diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/HTTP.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HTTP.java similarity index 71% rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/HTTP.java rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HTTP.java index 21232a46..cdf657d4 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/HTTP.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HTTP.java @@ -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() { } - } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HeaderValueFormatter.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HeaderValueFormatter.java new file mode 100644 index 00000000..f136bb01 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HeaderValueFormatter.java @@ -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 + * . + * + */ + +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 true to always format with quoted values, + * false to use quotes only when necessary + * @param formatter the formatter to use, or null + * 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 null + * + * @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 true to always format with a quoted value, + * false to use quotes only when necessary + * @param formatter the formatter to use, or null + * 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 null + * + * @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 null + * @param value the value to append, never null + * @param quote true to always format with quotes, + * false 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 true if the character is a separator, + * false 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 true if the character is unsafe, + * false otherwise + */ + protected boolean isUnsafe(final char ch) { + return UNSAFE_CHARS.indexOf(ch) >= 0; + } + + +} // class BasicHeaderValueFormatter diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ContentDescriptor.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ContentDescriptor.java deleted file mode 100644 index aa647f89..00000000 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ContentDescriptor.java +++ /dev/null @@ -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 - * . - * - */ - -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 TEXT, IMAGE, MULTIPART - * @see #getMimeType() - * @return the MIME media type when content-type specified, - * otherwise the correct default (TEXT) - */ - 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 (PLAIN) - */ - String getSubType(); - - /** - *

The body descriptors character set, defaulted appropriately for the MIME type.

- *

- * For TEXT types, this will be defaulted to us-ascii. - * For other types, when the charset parameter is missing this property will be null. - *

- * @return Character set, which has been parsed from the - * content-type definition. Not null for TEXT types, when unset will - * be set to default 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(); - -} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/Header.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/Header.java deleted file mode 100644 index f6782fd4..00000000 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/Header.java +++ /dev/null @@ -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 - * . - * - */ - -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 { - - private final List fields; - private final Map> fieldMap; - - public Header() { - super(); - this.fields = new LinkedList(); - this.fieldMap = new HashMap>(); - } - - public void addField(final NameValue field) { - if (field == null) { - return; - } - String key = field.getName().toLowerCase(Locale.US); - List values = this.fieldMap.get(key); - if (values == null) { - values = new LinkedList(); - this.fieldMap.put(key, values); - } - values.add(field); - this.fields.add(field); - } - - public List getFields() { - return new ArrayList(this.fields); - } - - public NameValue getField(final String name) { - if (name == null) { - return null; - } - String key = name.toLowerCase(Locale.US); - List list = this.fieldMap.get(key); - if (list != null && !list.isEmpty()) { - return list.get(0); - } - return null; - } - - public List getFields(final String name) { - if (name == null) { - return null; - } - String key = name.toLowerCase(Locale.US); - List list = this.fieldMap.get(key); - if (list == null || list.isEmpty()) { - return Collections.emptyList(); - } else { - return new ArrayList(list); - } - } - - public int removeFields(final String name) { - if (name == null) { - return 0; - } - String key = name.toLowerCase(Locale.US); - List 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 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 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 iterator() { - return Collections.unmodifiableList(fields).iterator(); - } - - @Override - public String toString() { - return this.fields.toString(); - } - -} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/HttpMultipart.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/HttpMultipart.java deleted file mode 100644 index 4fd3071f..00000000 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/HttpMultipart.java +++ /dev/null @@ -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 - * . - * - */ - -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 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(); - 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 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. - *

- * 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, -1 - * 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; - } - } - -} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/HttpMultipartMode.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/HttpMultipartMode.java deleted file mode 100644 index 18e75bf7..00000000 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/HttpMultipartMode.java +++ /dev/null @@ -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 -} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/InputStreamBody.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/InputStreamBody.java deleted file mode 100644 index a2fea006..00000000 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/InputStreamBody.java +++ /dev/null @@ -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 - * . - * - */ - -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; - } - -} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/MultipartEntity.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/MultipartEntity.java deleted file mode 100644 index 5f9c831d..00000000 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/MultipartEntity.java +++ /dev/null @@ -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 - * . - * - */ - -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(); - } -} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/AbstractContentBody.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/AbstractContentBody.java similarity index 59% rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/AbstractContentBody.java rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/AbstractContentBody.java index 2a3dec29..39e2849d 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/AbstractContentBody.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/AbstractContentBody.java @@ -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) { - 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; - } - } + /** + * @since 4.3 + */ + public AbstractContentBody(final ContentType contentType) { + super(); + 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; + } +} \ No newline at end of file diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ByteArrayBody.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/ByteArrayBody.java similarity index 80% rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ByteArrayBody.java rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/ByteArrayBody.java index c778695f..c052afd6 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ByteArrayBody.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/ByteArrayBody.java @@ -24,13 +24,18 @@ * . * */ -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; } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/ContentBody.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/ContentBody.java new file mode 100644 index 00000000..a7377486 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/ContentBody.java @@ -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 + * . + * + */ + +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(); + + /** + *

+ * The body descriptors character set, defaulted appropriately for the MIME + * type. + *

+ *

+ * 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. + *

+ * + * @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; +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/FileBody.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/FileBody.java new file mode 100644 index 00000000..cd78cce9 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/FileBody.java @@ -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 + * . + * + */ + +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; + } + +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/FileBody.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/InputStreamBody.java similarity index 52% rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/FileBody.java rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/InputStreamBody.java index 269d7eb7..a19cacc4 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/FileBody.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/InputStreamBody.java @@ -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"); - } - this.file = file; - if (filename != null) - this.filename = filename; - else - this.filename = file.getName(); - this.charset = charset; + public InputStreamBody(final InputStream in, final String mimeType, final String filename) { + this(in, ContentType.create(mimeType), filename); + } + + public InputStreamBody(final InputStream in, final String filename) { + this(in, ContentType.DEFAULT_BINARY, filename); } /** - * @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, final String filename) { + super(contentType); + this.in = in; + this.filename = filename; } - public FileBody(final File file, final String mimeType) { - this(file, mimeType, null); + /** + * @since 4.3 + */ + public InputStreamBody(final InputStream in, final ContentType contentType) { + this(in, contentType, 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; } } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/StringBody.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/StringBody.java similarity index 68% rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/StringBody.java rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/StringBody.java index 06df2b00..7144dda6 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/StringBody.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/content/StringBody.java @@ -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; } - } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/AbstractMultipartForm.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/AbstractMultipartForm.java new file mode 100644 index 00000000..6623e03a --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/AbstractMultipartForm.java @@ -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 + * . + * + */ + +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 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. + *

+ * 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} + * 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; + } + } + +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/FormBodyPart.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/FormBodyPart.java similarity index 62% rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/FormBodyPart.java rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/FormBodyPart.java index 3f298395..acf474b6 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/FormBodyPart.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/FormBodyPart.java @@ -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,18 +97,33 @@ 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(); - buffer.append(body.getMimeType()); // MimeType cannot be null - if (body.getCharset() != null) { // charset may legitimately be null - buffer.append("; charset="); - buffer.append(body.getCharset()); + 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="); + buffer.append(body.getCharset()); + } + addField(MIME.CONTENT_TYPE, buffer.toString()); } - 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 } - } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/FormBodyPartBuilder.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/FormBodyPartBuilder.java new file mode 100644 index 00000000..9f32c22a --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/FormBodyPartBuilder.java @@ -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 + * . + * + */ + +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 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(); + } + +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/Header.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/Header.java new file mode 100644 index 00000000..412603c8 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/Header.java @@ -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 + * . + * + */ + +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 { + + private final List fields; + private final Map> fieldMap; + + public Header() { + super(); + this.fields = new LinkedList(); + this.fieldMap = new HashMap>(); + } + + public void addField(final MinimalField field) { + if (field == null) { + return; + } + final String key = field.getName().toLowerCase(Locale.ROOT); + List values = this.fieldMap.get(key); + if (values == null) { + values = new LinkedList(); + this.fieldMap.put(key, values); + } + values.add(field); + this.fields.add(field); + } + + public List getFields() { + return new ArrayList(this.fields); + } + + public MinimalField getField(final String name) { + if (name == null) { + return null; + } + final String key = name.toLowerCase(Locale.ROOT); + final List list = this.fieldMap.get(key); + if (list != null && !list.isEmpty()) { + return list.get(0); + } + return null; + } + + public List getFields(final String name) { + if (name == null) { + return null; + } + final String key = name.toLowerCase(Locale.ROOT); + final List list = this.fieldMap.get(key); + if (list == null || list.isEmpty()) { + return Collections.emptyList(); + } else { + return new ArrayList(list); + } + } + + public int removeFields(final String name) { + if (name == null) { + return 0; + } + final String key = name.toLowerCase(Locale.ROOT); + final List 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 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 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 iterator() { + return Collections.unmodifiableList(fields).iterator(); + } + + @Override + public String toString() { + return this.fields.toString(); + } +} \ No newline at end of file diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpBrowserCompatibleMultipart.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpBrowserCompatibleMultipart.java new file mode 100644 index 00000000..b5749498 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpBrowserCompatibleMultipart.java @@ -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 + * . + * + */ + +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 parts; + + public HttpBrowserCompatibleMultipart( + final Charset charset, + final String boundary, + final List parts) { + super(charset, boundary); + this.parts = parts; + } + + @Override + public List 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); + } + + } + +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpMultipart.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpMultipart.java new file mode 100644 index 00000000..05a87c99 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpMultipart.java @@ -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 + * . + * + */ + +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 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(); + } + + /** + * 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 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; + } + +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ContentBody.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpMultipartMode.java similarity index 80% rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ContentBody.java rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpMultipartMode.java index 636be946..37216151 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ContentBody.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpMultipartMode.java @@ -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 } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpRFC6532Multipart.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpRFC6532Multipart.java new file mode 100644 index 00000000..f6a3b820 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpRFC6532Multipart.java @@ -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 + * . + * + */ + +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 parts; + + public HttpRFC6532Multipart( + final Charset charset, + final String boundary, + final List parts) { + super(charset, boundary); + this.parts = parts; + } + + @Override + public List 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); + } + } + +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpStrictMultipart.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpStrictMultipart.java new file mode 100644 index 00000000..8aa46ff8 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/HttpStrictMultipart.java @@ -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 + * . + * + */ + +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 parts; + + public HttpStrictMultipart( + final Charset charset, + final String boundary, + final List parts) { + super(charset, boundary); + this.parts = parts; + } + + @Override + public List 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); + } + } + +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/MIME.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MIME.java similarity index 87% rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/MIME.java rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MIME.java index d2d8004c..ba7de88c 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/MIME.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MIME.java @@ -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; } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MinimalField.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MinimalField.java new file mode 100644 index 00000000..3d552e55 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MinimalField.java @@ -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 + * . + * + */ + +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(); + } + +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartEntity.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartEntity.java new file mode 100644 index 00000000..a81b1afe --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartEntity.java @@ -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 + * . + * + */ + +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); + } +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartEntityBuilder.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartEntityBuilder.java new file mode 100644 index 00000000..8121a76c --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartEntityBuilder.java @@ -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 + * . + * + */ + +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 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(); + } + 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 paramsList = new ArrayList(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 bodyPartsCopy = bodyParts != null ? new ArrayList(bodyParts) : + Collections.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()); + } +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartFormEntity.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartFormEntity.java new file mode 100644 index 00000000..74e6cb46 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/mime/MultipartFormEntity.java @@ -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 + * . + * + */ + +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); + } +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/support/apache3/HttpComponent3.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/support/apache3/HttpComponent3.java index 1abaf54d..2865348c 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/support/apache3/HttpComponent3.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/support/apache3/HttpComponent3.java @@ -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; diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/support/apache4/HttpComponent4.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/support/apache4/HttpComponent4.java index 12d64342..375288d5 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/support/apache4/HttpComponent4.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/support/apache4/HttpComponent4.java @@ -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; diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinRequestExecutor.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinRequestExecutor.java index e94fc61e..462f8faa 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinRequestExecutor.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinRequestExecutor.java @@ -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); } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ByteArrayBuffer.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/ByteArrayBuffer.java similarity index 66% rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ByteArrayBuffer.java rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/ByteArrayBuffer.java index e33116ed..bc0a3cfb 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/ByteArrayBuffer.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/ByteArrayBuffer.java @@ -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 len bytes to this buffer from the given source - * array starting at index off. The capacity of the buffer - * is increased, if necessary, to accommodate all len 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 off if out of - * range, len is negative, or - * off + len 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 b 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 len chars to this buffer from the given source - * array starting at index off. The capacity of the buffer - * is increased if necessary to accommodate all len 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. *

* 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 off if out of - * range, len is negative, or - * off + len 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 len chars to this buffer from the given source - * char array buffer starting at index off. 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 - * len chars. + * {@code len} chars. *

* 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 off if out of - * range, len is negative, or - * off + len 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 byte 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 - * 0, 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 index 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 required + * 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 - * 0. + * {@code 0}. * * @param len the new length * @throws IndexOutOfBoundsException if the - * len argument is greater than the current - * capacity of the buffer or less than 0. + * {@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 true if this buffer is empty, that is, its - * {@link #length()} is equal to 0. - * @return true if this buffer is empty, false + * 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 true 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 true if this buffer is full, false + * @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 - * beginIndex and finishing at endIndex. + * {@code beginIndex} and finishing at {@code endIndex}. * If no such byte occurs in this buffer within the specified bounds, - * -1 is returned. + * {@code -1} is returned. *

- * There is no restriction on the value of beginIndex and - * endIndex. If beginIndex is negative, - * it has the same effect as if it were zero. If endIndex 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 beginIndex is greater than - * the endIndex, -1 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 -1 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 0 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, -1 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 -1 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); } -} +} \ No newline at end of file diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/CharArrayBuffer.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/CharArrayBuffer.java similarity index 58% rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/CharArrayBuffer.java rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/CharArrayBuffer.java index ce746828..508feea6 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/apache/CharArrayBuffer.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/CharArrayBuffer.java @@ -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 len chars to this buffer from the given source - * array starting at index off. The capacity of the buffer - * is increased, if necessary, to accommodate all len 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 off is out of - * range, len is negative, or - * off + len 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 len chars to this buffer from the given source - * buffer starting at index off. 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 - * len 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 off is out of - * range, len is negative, or - * off + len 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 0. 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 ch 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 len bytes to this buffer from the given source - * array starting at index off. The capacity of the buffer - * is increased, if necessary, to accommodate all len 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. *

* 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 off is out of - * range, len is negative, or - * off + len 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 len bytes to this buffer from the given source - * array starting at index off. The capacity of the buffer - * is increased, if necessary, to accommodate all len 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. *

* 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 off is out of - * range, len is negative, or - * off + len 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 char 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 - * 0, 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 index 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 required + * 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 - * 0. + * {@code 0}. * * @param len the new length * @throws IndexOutOfBoundsException if the - * len argument is greater than the current - * capacity of the buffer or less than 0. + * {@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 true if this buffer is empty, that is, its - * {@link #length()} is equal to 0. - * @return true if this buffer is empty, false + * 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 true 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 true if this buffer is full, false + * @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 - * beginIndex and finishing at endIndex. + * {@code beginIndex} and finishing at {@code endIndex}. * If no such character occurs in this buffer within the specified bounds, - * -1 is returned. + * {@code -1} is returned. *

- * There is no restriction on the value of beginIndex and - * endIndex. If beginIndex is negative, - * it has the same effect as if it were zero. If endIndex 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 beginIndex is greater than - * the endIndex, -1 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 ch the char to search for. + * @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 -1 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,69 +390,98 @@ public final class CharArrayBuffer implements Serializable { /** * Returns the index within this buffer of the first occurrence of the - * specified character, starting the search at 0 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, -1 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 -1 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 - * beginIndex and extends to the character at index - * endIndex - 1. + * {@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 - * beginIndex is negative, or - * endIndex is larger than the length of this - * buffer, or beginIndex is larger than - * endIndex. + * @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) { + public String substring(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 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 beginIndex and extends to the last + * from {@code beginIndex} and extends to the last * non-whitespace character with the index lesser than - * endIndex. + * {@code endIndex}. * * @param beginIndex the beginning index, inclusive. * @param endIndex the ending index, exclusive. * @return the specified substring. - * @exception IndexOutOfBoundsException if the - * beginIndex is negative, or - * endIndex is larger than the length of this - * buffer, or beginIndex is larger than - * endIndex. + * @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(int beginIndex, int endIndex) { + public String substringTrimmed(final int beginIndex, final int endIndex) { if (beginIndex < 0) { - throw new IndexOutOfBoundsException("Negative beginIndex: "+beginIndex); + throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex); } if (endIndex > this.len) { - throw new IndexOutOfBoundsException("endIndex: "+endIndex+" > length: "+this.len); + throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len); } if (beginIndex > endIndex) { - throw new IndexOutOfBoundsException("beginIndex: "+beginIndex+" > endIndex: "+endIndex); + throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex); } - while (beginIndex < endIndex && HTTP.isWhitespace(this.buffer[beginIndex])) { - beginIndex++; + int beginIndex0 = beginIndex; + int endIndex0 = endIndex; + while (beginIndex0 < endIndex && HTTP.isWhitespace(this.buffer[beginIndex0])) { + beginIndex0++; } - while (endIndex > beginIndex && HTTP.isWhitespace(this.buffer[endIndex - 1])) { - endIndex--; + while (endIndex0 > beginIndex0 && HTTP.isWhitespace(this.buffer[endIndex0 - 1])) { + endIndex0--; } - return new String(this.buffer, beginIndex, endIndex - beginIndex); + 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 diff --git a/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/CustomApi.java b/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/CustomApi.java index 7edb780f..8da4318c 100644 --- a/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/CustomApi.java +++ b/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/CustomApi.java @@ -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; diff --git a/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/MediaApi.java b/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/MediaApi.java index 25b286fc..0a9fd796 100644 --- a/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/MediaApi.java +++ b/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/MediaApi.java @@ -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; diff --git a/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/api/MediaApi.java b/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/api/MediaApi.java index dc328853..27f63422 100644 --- a/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/api/MediaApi.java +++ b/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/api/MediaApi.java @@ -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;