View Javadoc
1   //
2   // $Id: FileUploadThreadFTP.java 136 2007-05-12 20:15:36 +0000 (sam., 12 mai
3   // 2007) etienne_sf $
4   //
5   // jupload - A file upload applet.
6   // Copyright 2007 The JUpload Team
7   //
8   // Created: 2007-12-11
9   // Creator: etienne_sf
10  // Last modified: $Date: 2007-07-21 09:42:51 +0200 (sam., 21 juil. 2007) $
11  //
12  // This program is free software; you can redistribute it and/or modify it under
13  // the terms of the GNU General Public License as published by the Free Software
14  // Foundation; either version 2 of the License, or (at your option) any later
15  // version. This program is distributed in the hope that it will be useful, but
16  // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17  // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18  // details. You should have received a copy of the GNU General Public License
19  // along with this program; if not, write to the Free Software Foundation, Inc.,
20  // 675 Mass Ave, Cambridge, MA 02139, USA.
21  
22  package wjhk.jupload2.upload.helper;
23  
24  import java.io.ByteArrayOutputStream;
25  import java.io.IOException;
26  import java.io.OutputStreamWriter;
27  import java.io.UnsupportedEncodingException;
28  import java.io.Writer;
29  
30  import wjhk.jupload2.exception.JUploadIOException;
31  import wjhk.jupload2.policies.UploadPolicy;
32  
33  /**
34   * This class is a utility, which provide easy encoding for HTTP queries. The
35   * way to use this class is:
36   * <OL TYPE=1>
37   * <LI>Instantiate a new object
38   * <LI>Append data to it, using the append methods. Available for: String,
39   * byte[], other ByteArrayEncode...
40   * <LI>Close the stream. This will prevent any new data to be appended to it.
41   * The encoded length can now be calculated.
42   * <LI>Get the encoded length.
43   * <LI>Get the encoded byte array
44   * </OL>
45   * 
46   * @author etienne_sf
47   * 
48   */
49  
50  public class ByteArrayEncoderHTTP implements ByteArrayEncoder {
51  
52      /**
53       * The default encoding. It can be retrieved with
54       * {@link #getDefaultEncoding()}.
55       */
56      final static String DEFAULT_ENCODING = "UTF-8";
57  
58      /**
59       * The boundary, to put between to post variables. Can not be changed during
60       * the object 'life'.
61       */
62      private String bound = null;
63  
64      /**
65       * The current encoding. Can not be changed during the object 'life'.
66       */
67      private String encoding = DEFAULT_ENCODING;
68  
69      /**
70       * Indicate whether the encoder is closed or not. If closed, it's impossible
71       * to append new data to it. If not closed, it's impossible to get the
72       * encoded length or the encoded byte array.<BR>
73       * <B>Note:</B> a closed byte array can not be re-opened.
74       */
75      boolean closed = false;
76  
77      /**
78       * The actual array, which will collect the encoded bytes.
79       */
80      private ByteArrayOutputStream baos = new ByteArrayOutputStream();
81  
82      /**
83       * The byte array length. Calculated when the ByteArrayOutput is closed.
84       */
85      private int encodedLength = -1;
86  
87      /**
88       * The encoded byte array. Calculated when the ByteArrayOutput is closed.
89       */
90      private byte[] encodedByteArray = null;
91  
92      /**
93       * The current upload policy.
94       */
95      private UploadPolicy uploadPolicy;
96  
97      /**
98       * The writer, that will encode the input parameters to {@link #baos}.
99       */
100     private Writer writer;
101 
102     // ///////////////////////////////////////////////////////////////////////
103     // //////////////// VARIOUS UTILITIES ////////////////////////////////////
104     // ///////////////////////////////////////////////////////////////////////
105 
106     // ///////////////////////////////////////////////////////////////////////
107     // //////////////// CONSTRUCTORS /////////////////////////////////////////
108     // ///////////////////////////////////////////////////////////////////////
109 
110     /**
111      * Create an encoder, using the DEFAULT_ENCODING encoding.
112      * 
113      * @param uploadPolicy The current upload policy
114      * @throws JUploadIOException Any IO exception
115      */
116     public ByteArrayEncoderHTTP(UploadPolicy uploadPolicy)
117             throws JUploadIOException {
118         init(uploadPolicy, null, DEFAULT_ENCODING);
119     }
120 
121     /**
122      * Create an encoder, and specifies the encoding to use.
123      * 
124      * @param uploadPolicy The current upload policy
125      * @param bound Any specific boundary. May be null: in this case a default
126      *            boundary is used.
127      * @throws JUploadIOException Any IO exception
128      */
129     public ByteArrayEncoderHTTP(UploadPolicy uploadPolicy, String bound)
130             throws JUploadIOException {
131         init(uploadPolicy, bound, DEFAULT_ENCODING);
132     }
133 
134     /**
135      * Create an encoder, and specifies the boundary and encoding to use.
136      * 
137      * @param uploadPolicy The current upload policy
138      * @param bound Any specific boundary. May be null: in this case a default
139      *            boundary is used.
140      * @param encoding The encoding to use. For instance, "UTF-8".
141      * @throws JUploadIOException Any IO exception
142      */
143     public ByteArrayEncoderHTTP(UploadPolicy uploadPolicy, String bound,
144             String encoding) throws JUploadIOException {
145         init(uploadPolicy, bound, encoding);
146     }
147 
148     // ///////////////////////////////////////////////////////////////////////
149     // //////////////// Public methods ///////////////////////////////////////
150     // ///////////////////////////////////////////////////////////////////////
151 
152     /**
153      * @throws JUploadIOException
154      * @see wjhk.jupload2.upload.helper.ByteArrayEncoder#close()
155      */
156     synchronized public void close() throws JUploadIOException {
157         if (isClosed()) {
158             throw new JUploadIOException(
159                     "Trying to close an already closed ByteArrayEncoded");
160         }
161         try {
162             this.writer.close();
163         } catch (IOException e) {
164             throw new JUploadIOException(e);
165         }
166         this.encodedByteArray = this.baos.toByteArray();
167         this.encodedLength = this.encodedByteArray.length;
168         this.closed = true;
169     }
170 
171     /** {@inheritDoc} */
172     public ByteArrayEncoder append(String str) throws JUploadIOException {
173         try {
174             this.writer.append(str);
175         } catch (IOException e) {
176             throw new JUploadIOException(e);
177         }
178         // Returning the encoder allows calls like:
179         // bae.append("qdqd").append("qsldqd"); (like StringBuffer)
180         return this;
181     }
182 
183     /** {@inheritDoc} */
184     public ByteArrayEncoder append(int b) throws JUploadIOException {
185         try {
186             this.writer.flush();
187             this.baos.write(b);
188         } catch (IOException e) {
189             throw new JUploadIOException(e);
190         }
191         // Returning the encoder allows calls like:
192         // bae.append("qdqd").append("qsldqd"); (like StringBuffer)
193         return this;
194     }
195 
196     /** {@inheritDoc} */
197     public ByteArrayEncoder append(byte[] b) throws JUploadIOException {
198         try {
199             this.writer.flush();
200             this.baos.write(b);
201         } catch (IOException e) {
202             throw new JUploadIOException(e);
203         }
204         // Returning the encoder allows calls like:
205         // bae.append("qdqd").append("qsldqd"); (like StringBuffer)
206         return this;
207     }
208 
209     /** {@inheritDoc} */
210     public ByteArrayEncoder append(ByteArrayEncoder bae)
211             throws JUploadIOException {
212         this.append(bae.getEncodedByteArray());
213 
214         return this;
215     }
216 
217     /** {@inheritDoc} */
218     public ByteArrayEncoder appendTextProperty(String name, String value,
219             int index) throws JUploadIOException {
220         String propertySuffix = "";
221         // if an index is given, we may have to suffix the name of the upload
222         // parameter, depending on the value of httpUploadParameterType
223         if (index >= 0) {
224             if (this.uploadPolicy.getHttpUploadParameterType().equals(
225                     UploadPolicy.HTTPUPLOADPARAMETERTYPE_ARRAY)) {
226                 propertySuffix = "[]";
227             } else if (this.uploadPolicy.getHttpUploadParameterType().equals(
228                     UploadPolicy.HTTPUPLOADPARAMETERTYPE_ITERATION)) {
229                 propertySuffix = Integer.toString(index);
230             }
231         }
232         this.append(this.bound).append("\r\n");
233         this.append("Content-Disposition: form-data; name=\"").append(name)
234                 .append(propertySuffix).append("\"\r\n");
235         this.append("Content-Transfer-Encoding: 8bit\r\n");
236         this.append("Content-Type: text/plain; charset=").append(this.getEncoding())
237                 .append("\r\n");
238         // An empty line before the actual value.
239         this.append("\r\n");
240         // And then, the value!
241         this.append(value).append("\r\n");
242 
243         return this;
244     }
245 
246     /** {@inheritDoc} */
247     public ByteArrayEncoder appendEndPropertyList() throws JUploadIOException {
248         this.append(this.bound).append("--\r\n");
249         return this;
250     }
251 
252     /** {@inheritDoc} */
253     @SuppressWarnings("restriction")
254     public ByteArrayEncoder appendFormVariables(String formname)
255             throws JUploadIOException {
256         String action = "Entering ByteArrayEncoderHTTP.appendFormVariables() [html form: "
257                 + formname + "]";
258         try {
259             this.uploadPolicy.displayDebug(action, 80);
260             // TODO Do not use getApplet() anymore, here.
261             action = "win = netscape.javascript.JSObject.getWindow";
262             this.uploadPolicy.displayDebug("Before " + action + 
263                     " (this.uploadPolicy.getContext().getApplet(): "
264                     + this.uploadPolicy.getContext().getApplet() + ")"
265                     , 80);
266             netscape.javascript.JSObject win = netscape.javascript.JSObject
267                     .getWindow(this.uploadPolicy.getContext().getApplet());
268             this.uploadPolicy.displayDebug("After " + action + 
269                     " (win: " + win + ")", 80);
270 
271             action = "o = win.eval";
272             Object o = win.eval("document.forms[\"" + formname
273                     + "\"].elements.length");
274             if (o instanceof Number) {
275                 int len = ((Number) o).intValue();
276                 if (len <= 0) {
277                     this.uploadPolicy.displayWarn("The specified form \""
278                             + formname + "\" does not contain any elements.");
279                 }
280                 int i;
281                 for (i = 0; i < len; i++) {
282                     try {
283                         action = "name = win.eval";
284                         Object name = win.eval("document.forms[\"" + formname
285                                 + "\"][" + i + "].name");
286                         action = "value = win.eval";
287                         Object value = win.eval("document.forms[\"" + formname
288                                 + "\"][" + i + "].value");
289                         action = "elementType = win.eval";
290                         Object elementType = win.eval("document.forms[\""
291                                 + formname + "\"][" + i + "].type");
292                         action = "elementClass = win.eval";
293                         // elementClass seems to be not supported by IE7
294                         // The next line prevents formData to be sent to the
295                         // server, when formData is used on IE7. Works fine with
296                         // firefox.
297                         // Object elementClass = win.eval("document." + formname
298                         // + "[" + i + "].class");
299                         action = "etype = win.eval";
300                         Object etype = win.eval("document.forms[\"" + formname
301                                 + "\"][" + i + "].type");
302                         if (etype instanceof String) {
303                             String t = (String) etype;
304                             if (t.equals("checkbox") || t.equals("radio")) {
305                                 action = "on = win.eval";
306                                 Object on = win.eval("document.forms[\""
307                                         + formname + "\"][" + i + "].checked");
308                                 if (on instanceof Boolean) {
309                                     // Skip unchecked checkboxes and
310                                     // radiobuttons
311                                     if (!((Boolean) on).booleanValue()) {
312                                         this.uploadPolicy
313                                                 .displayDebug(
314                                                         "  [ByteArrayEncoder.appendFormVariables] Skipping unchecked checkboxes and radiobuttons",
315                                                         80);
316                                         // TODO Do not use getApplet() anymore,
317                                         // here.
318                                         continue;
319                                     }
320                                 }
321                             }
322                         }
323                         if (name instanceof String
324                                 && ((String) name).length() > 0) {
325                             if (value instanceof String) {
326                                 this.uploadPolicy.displayDebug(
327                                         "  [ByteArrayEncoder.appendFormVariables] Adding formdata element num "
328                                                 + i + " (name: " + name
329                                                 + ", value: " + value
330                                                 + ", type: " + elementType
331                                                 /* + ", class: " + elementClass */
332                                                 + ")", 80);
333                                 this.appendTextProperty((String) name,
334                                         (String) value, -1);
335                             } else {
336                                 this.uploadPolicy
337                                         .displayWarn("  [ByteArrayEncoder.appendFormVariables] Value must be an instance of String (name: "
338                                                 + name
339                                                 + ", value: )"
340                                                 + value
341                                                 + ")");
342                             }
343                         } else {
344                             this.uploadPolicy
345                                     .displayWarn("  [ByteArrayEncoder.appendFormVariables] Name must be an instance of String (name: "
346                                             + name + ", value: )" + value + ")");
347                         }
348                     } catch (netscape.javascript.JSException e1) {
349                         this.uploadPolicy
350                                 .displayWarn(e1.getStackTrace()[1]
351                                         + ": got JSException in ByteArrayEncoderHTTP.appendFormVariables() [html form: "
352                                         + formname
353                                         + "] - bailing out (action: " + action
354                                         + ")");
355                         i = len;
356                     }
357                 }
358             } else {
359                 this.uploadPolicy
360                         .displayWarn("  [ByteArrayEncoder.appendFormVariables] The specified form \""
361                                 + formname
362                                 + "\" could not be found (or has no element).");
363             }
364         } catch (netscape.javascript.JSException e) {
365             this.uploadPolicy.displayDebug(e.getStackTrace()[1]
366                     + ": No JavaScript available (action: " + action + ")", 10);
367         }
368 
369         this.uploadPolicy
370                 .displayDebug(
371                         "  [ByteArrayEncoder.appendFormVariables] End of ByteArrayEncoderHTTP.appendFormVariables()",
372                         80);
373         return this;
374     }
375 
376     /** {@inheritDoc} */
377     public String getBoundary() {
378         return this.bound;
379     }
380 
381     /**
382      * @return value of the DEFAULT_ENCODING constant.
383      */
384     public static String getDefaultEncoding() {
385         return DEFAULT_ENCODING;
386     }
387 
388     /** {@inheritDoc} */
389     public boolean isClosed() {
390         return this.closed;
391     }
392 
393     /** {@inheritDoc} */
394     public String getEncoding() {
395         return this.encoding;
396     }
397 
398     /** {@inheritDoc} */
399     public int getEncodedLength() throws JUploadIOException {
400         if (!isClosed()) {
401             throw new JUploadIOException(
402                     "Trying to get length of a on non-closed ByteArrayEncoded");
403         }
404         return this.encodedLength;
405     }
406 
407     /** {@inheritDoc} */
408     public byte[] getEncodedByteArray() throws JUploadIOException {
409         if (!isClosed()) {
410             throw new JUploadIOException(
411                     "Trying to get the byte array of a on non-closed ByteArrayEncoded");
412         }
413         return this.encodedByteArray;
414     }
415 
416     /** {@inheritDoc} */
417     public String getString() throws JUploadIOException {
418         if (!isClosed()) {
419             throw new JUploadIOException(
420                     "Trying to get the byte array of a on non-closed ByteArrayEncoded");
421         }
422         try {
423             return new String(this.encodedByteArray, getEncoding());
424         } catch (UnsupportedEncodingException e) {
425             throw new JUploadIOException(e);
426         }
427     }
428 
429     // ///////////////////////////////////////////////////////////////////////
430     // //////////////// Private methods //////////////////////////////////////
431     // ///////////////////////////////////////////////////////////////////////
432 
433     /**
434      * Initialization: called by the constructors.
435      * 
436      * @throws JUploadIOException
437      */
438     private void init(UploadPolicy uploadPolicy, String bound, String encoding)
439             throws JUploadIOException {
440         this.uploadPolicy = uploadPolicy;
441         this.encoding = encoding;
442         this.bound = bound;
443 
444         try {
445             this.writer = new OutputStreamWriter(this.baos, encoding);
446         } catch (UnsupportedEncodingException e) {
447             throw new JUploadIOException(e);
448         }
449     }
450 }