View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package com.foxinmy.weixin4j.http.apache.mime;
29  
30  import java.io.ByteArrayOutputStream;
31  import java.io.IOException;
32  import java.io.OutputStream;
33  import java.nio.ByteBuffer;
34  import java.nio.CharBuffer;
35  import java.nio.charset.Charset;
36  import java.util.List;
37  
38  import com.foxinmy.weixin4j.http.apache.content.ContentBody;
39  import com.foxinmy.weixin4j.util.ByteArrayBuffer;
40  
41  /**
42   * HttpMultipart represents a collection of MIME multipart encoded content bodies. This class is
43   * capable of operating either in the strict (RFC 822, RFC 2045, RFC 2046 compliant) or
44   * the browser compatible modes.
45   *
46   * @since 4.3
47   */
48  abstract class AbstractMultipartForm {
49  
50      private static ByteArrayBuffer encode(
51              final Charset charset, final String string) {
52          final ByteBuffer encoded = charset.encode(CharBuffer.wrap(string));
53          final ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining());
54          bab.append(encoded.array(), encoded.position(), encoded.remaining());
55          return bab;
56      }
57  
58      private static void writeBytes(
59              final ByteArrayBuffer b, final OutputStream out) throws IOException {
60          out.write(b.buffer(), 0, b.length());
61      }
62  
63      private static void writeBytes(
64              final String s, final Charset charset, final OutputStream out) throws IOException {
65          final ByteArrayBuffer b = encode(charset, s);
66          writeBytes(b, out);
67      }
68  
69      private static void writeBytes(
70              final String s, final OutputStream out) throws IOException {
71          final ByteArrayBuffer b = encode(MIME.DEFAULT_CHARSET, s);
72          writeBytes(b, out);
73      }
74  
75      protected static void writeField(
76              final MinimalField field, final OutputStream out) throws IOException {
77          writeBytes(field.getName(), out);
78          writeBytes(FIELD_SEP, out);
79          writeBytes(field.getBody(), out);
80          writeBytes(CR_LF, out);
81      }
82  
83      protected static void writeField(
84              final MinimalField field, final Charset charset, final OutputStream out) throws IOException {
85          writeBytes(field.getName(), charset, out);
86          writeBytes(FIELD_SEP, out);
87          writeBytes(field.getBody(), charset, out);
88          writeBytes(CR_LF, out);
89      }
90  
91      private static final ByteArrayBuffer FIELD_SEP = encode(MIME.DEFAULT_CHARSET, ": ");
92      private static final ByteArrayBuffer CR_LF = encode(MIME.DEFAULT_CHARSET, "\r\n");
93      private static final ByteArrayBuffer TWO_DASHES = encode(MIME.DEFAULT_CHARSET, "--");
94  
95      final Charset charset;
96      final String boundary;
97  
98      /**
99       * Creates an instance with the specified settings.
100      *
101      * @param charset the character set to use. May be {@code null}, in which case {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
102      * @param boundary to use  - must not be {@code null}
103      * @throws IllegalArgumentException if charset is null or boundary is null
104      */
105     public AbstractMultipartForm(final Charset charset, final String boundary) {
106         super();
107         this.charset = charset != null ? charset : MIME.DEFAULT_CHARSET;
108         this.boundary = boundary;
109     }
110 
111     public AbstractMultipartForm(final String boundary) {
112         this(null, boundary);
113     }
114 
115     public abstract List<FormBodyPart> getBodyParts();
116 
117     void doWriteTo(
118         final OutputStream out,
119         final boolean writeContent) throws IOException {
120 
121         final ByteArrayBuffer boundaryEncoded = encode(this.charset, this.boundary);
122         for (final FormBodyPart part: getBodyParts()) {
123             writeBytes(TWO_DASHES, out);
124             writeBytes(boundaryEncoded, out);
125             writeBytes(CR_LF, out);
126 
127             formatMultipartHeader(part, out);
128 
129             writeBytes(CR_LF, out);
130 
131             if (writeContent) {
132                 part.getBody().writeTo(out);
133             }
134             writeBytes(CR_LF, out);
135         }
136         writeBytes(TWO_DASHES, out);
137         writeBytes(boundaryEncoded, out);
138         writeBytes(TWO_DASHES, out);
139         writeBytes(CR_LF, out);
140     }
141 
142     /**
143       * Write the multipart header fields; depends on the style.
144       */
145     protected abstract void formatMultipartHeader(
146         final FormBodyPart part,
147         final OutputStream out) throws IOException;
148 
149     /**
150      * Writes out the content in the multipart/form encoding. This method
151      * produces slightly different formatting depending on its compatibility
152      * mode.
153      */
154     public void writeTo(final OutputStream out) throws IOException {
155         doWriteTo(out, true);
156     }
157 
158     /**
159      * Determines the total length of the multipart content (content length of
160      * individual parts plus that of extra elements required to delimit the parts
161      * from one another). If any of the @{link BodyPart}s contained in this object
162      * is of a streaming entity of unknown length the total length is also unknown.
163      * <p>
164      * This method buffers only a small amount of data in order to determine the
165      * total length of the entire entity. The content of individual parts is not
166      * buffered.
167      * </p>
168      *
169      * @return total length of the multipart entity if known, {@code -1}
170      *   otherwise.
171      */
172     public long getTotalLength() {
173         long contentLen = 0;
174         for (final FormBodyPart part: getBodyParts()) {
175             final ContentBody body = part.getBody();
176             final long len = body.getContentLength();
177             if (len >= 0) {
178                 contentLen += len;
179             } else {
180                 return -1;
181             }
182         }
183         final ByteArrayOutputStream out = new ByteArrayOutputStream();
184         try {
185             doWriteTo(out, false);
186             final byte[] extra = out.toByteArray();
187             return contentLen + extra.length;
188         } catch (final IOException ex) {
189             // Should never happen
190             return -1;
191         }
192     }
193 
194 }