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 }