1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package wjhk.jupload2.upload.helper;
22
23 import java.io.BufferedOutputStream;
24 import java.io.DataOutputStream;
25 import java.io.IOException;
26 import java.io.OutputStream;
27 import java.io.PushbackInputStream;
28 import java.net.Proxy;
29 import java.net.ProxySelector;
30 import java.net.Socket;
31 import java.net.URISyntaxException;
32 import java.net.URL;
33 import java.security.KeyManagementException;
34 import java.security.KeyStoreException;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.UnrecoverableKeyException;
37 import java.security.cert.CertificateException;
38
39 import wjhk.jupload2.exception.JUploadException;
40 import wjhk.jupload2.exception.JUploadIOException;
41 import wjhk.jupload2.policies.UploadPolicy;
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 public class HTTPConnectionHelper extends OutputStream {
65
66
67
68
69
70
71
72
73 final static private int STATUS_NOT_INITIALIZED = 0;
74
75
76
77
78
79 final static private int STATUS_BEFORE_SERVER_CONNECTION = 1;
80
81
82
83
84
85
86
87 final static private int STATUS_WRITING_REQUEST = 2;
88
89
90
91
92
93
94
95 final static private int STATUS_READING_RESPONSE = 3;
96
97
98
99
100
101
102
103
104 final static private int STATUS_CONNECTION_CLOSED = 4;
105
106
107
108
109
110
111
112 private String boundary = calculateRandomBoundary();
113
114
115
116
117 private boolean bChunkEnabled;
118
119
120
121
122
123 private boolean bLastChunk;
124
125
126
127
128 private ByteArrayEncoder byteArrayEncoder = null;
129
130
131
132
133 private int connectionStatus = STATUS_NOT_INITIALIZED;
134
135
136
137
138
139 private HTTPInputStreamReader httpInputStreamReader = null;
140
141
142
143
144 private PushbackInputStream inputStream = null;
145
146
147
148
149 private String method = null;
150
151
152
153
154
155
156
157
158 private DataOutputStream outputStream = null;
159
160
161
162
163 private Proxy proxy = null;
164
165
166
167
168 private Socket socket = null;
169
170
171
172
173 private UploadPolicy uploadPolicy = null;
174
175
176
177
178 private URL url = null;
179
180
181
182
183 private boolean useProxy;
184
185
186
187
188 private boolean useSSL;
189
190
191
192
193
194
195
196
197
198
199 public HTTPConnectionHelper(UploadPolicy uploadPolicy) {
200 this.uploadPolicy = uploadPolicy;
201 }
202
203
204
205
206
207
208
209
210
211
212
213
214
215 public HTTPConnectionHelper(URL url, String method, boolean bChunkEnabled, boolean bLastChunk,
216 UploadPolicy uploadPolicy) throws JUploadIOException {
217 this.uploadPolicy = uploadPolicy;
218 initRequest(url, method, bChunkEnabled, bLastChunk);
219 }
220
221
222
223
224
225
226
227
228
229
230
231
232 public synchronized void initRequest(URL url, String method, boolean bChunkEnabled, boolean bLastChunk)
233 throws JUploadIOException {
234
235
236 if (this.connectionStatus != STATUS_NOT_INITIALIZED && this.connectionStatus != STATUS_CONNECTION_CLOSED) {
237 throw new JUploadIOException("Bad status of the HttpConnectionHelper in initRequest: " + getStatusLabel());
238 }
239
240
241 if (isKeepAlive()) {
242 dispose();
243 }
244
245
246 this.url = url;
247 this.method = method;
248 this.bChunkEnabled = bChunkEnabled;
249 this.bLastChunk = bLastChunk;
250
251
252 initByteArrayEncoder();
253
254
255 this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
256 }
257
258
259
260
261
262
263
264
265 public ByteArrayEncoder getByteArrayEncoder() throws JUploadIOException {
266 return this.byteArrayEncoder;
267 }
268
269
270
271
272 public String getBoundary() {
273 return this.boundary;
274 }
275
276
277
278
279
280
281
282 public synchronized void sendRequest() throws JUploadIOException {
283
284
285 if (this.connectionStatus != STATUS_BEFORE_SERVER_CONNECTION) {
286 throw new JUploadIOException("Bad status of the HttpConnectionHelper in sendRequest: " + getStatusLabel());
287 }
288
289 try {
290
291 if (!this.byteArrayEncoder.isClosed()) {
292 this.byteArrayEncoder.close();
293 }
294
295
296
297 this.httpInputStreamReader = null;
298
299
300
301
302 if (this.socket == null || !this.uploadPolicy.getAllowHttpPersistent()) {
303 this.socket = new HttpConnect(this.uploadPolicy).connect(this.url, this.proxy);
304 this.outputStream = new DataOutputStream(new BufferedOutputStream(this.socket.getOutputStream()));
305 this.inputStream = new PushbackInputStream(this.socket.getInputStream(), 1);
306 }
307
308
309 this.outputStream.write(this.byteArrayEncoder.getEncodedByteArray());
310
311
312
313 this.connectionStatus = STATUS_WRITING_REQUEST;
314
315 } catch (IOException e) {
316 throw new JUploadIOException("Unable to open socket", e);
317 } catch (KeyManagementException e) {
318 throw new JUploadIOException("Unable to open socket", e);
319 } catch (UnrecoverableKeyException e) {
320 throw new JUploadIOException("Unable to open socket", e);
321 } catch (NoSuchAlgorithmException e) {
322 throw new JUploadIOException("Unable to open socket", e);
323 } catch (KeyStoreException e) {
324 throw new JUploadIOException("Unable to open socket", e);
325 } catch (CertificateException e) {
326 throw new JUploadIOException("Unable to open socket", e);
327 } catch (IllegalArgumentException e) {
328 throw new JUploadIOException("Unable to open socket", e);
329 }
330
331 }
332
333
334
335
336
337
338 public synchronized void dispose() throws JUploadIOException {
339
340 try {
341
342
343 if (this.socket != null && !this.useSSL && !this.socket.isOutputShutdown()) {
344 this.socket.shutdownOutput();
345 }
346 } catch (IOException e) {
347 throw new JUploadIOException(e);
348 }
349 try {
350 if (this.socket != null && !this.useSSL && !this.socket.isInputShutdown()) {
351 this.socket.shutdownInput();
352 }
353 } catch (IOException e) {
354 throw new JUploadIOException(e);
355 }
356
357 try {
358 if (this.outputStream != null) {
359 this.outputStream.close();
360 }
361 } catch (IOException e) {
362 throw new JUploadIOException(e);
363 } finally {
364 this.outputStream = null;
365 }
366
367 try {
368 if (this.inputStream != null) {
369 this.inputStream.close();
370 }
371 } catch (IOException e) {
372 throw new JUploadIOException(e);
373 } finally {
374 this.inputStream = null;
375 }
376
377 try {
378 if (this.socket != null) {
379 if (!this.socket.isClosed()) {
380 this.socket.close();
381 }
382 }
383 } catch (IOException e) {
384 throw new JUploadIOException(e);
385 } finally {
386 this.socket = null;
387 }
388 }
389
390
391
392
393
394
395
396
397
398
399
400
401
402 public OutputStream getOutputStream() {
403 return this;
404 }
405
406
407
408
409
410
411 public PushbackInputStream getInputStream() {
412 return this.inputStream;
413 }
414
415
416
417
418
419
420 public String getMethod() {
421 return this.method;
422 }
423
424
425
426
427
428
429 public String getResponseBody() {
430 return this.httpInputStreamReader.getResponseBody();
431 }
432
433
434
435
436
437
438 public String getResponseHeaders() {
439 return this.httpInputStreamReader.getResponseHeaders();
440 }
441
442
443
444
445
446
447 public String getResponseMsg() {
448 return this.httpInputStreamReader.getResponseMsg();
449 }
450
451
452
453
454
455
456 public synchronized String getStatusLabel() {
457 switch (this.connectionStatus) {
458 case STATUS_NOT_INITIALIZED:
459 return "Not initialized";
460 case STATUS_BEFORE_SERVER_CONNECTION:
461 return "Before server connection";
462 case STATUS_WRITING_REQUEST:
463 return "Writing request to the network";
464 case STATUS_READING_RESPONSE:
465 return "Reading server response";
466 case STATUS_CONNECTION_CLOSED:
467 return "Connection closed";
468 }
469 return "Unknown status in HTTPConnectionHelper.getStatusLabel()";
470 }
471
472
473
474
475
476
477 Socket getSocket() {
478 return this.socket;
479 }
480
481
482
483
484
485
486
487
488
489 public synchronized HTTPConnectionHelper append(int b) throws JUploadIOException {
490 if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
491 this.byteArrayEncoder.append(b);
492 } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
493 try {
494 this.outputStream.write(b);
495 } catch (IOException e) {
496 throw new JUploadIOException(e.getClass().getName() + " while writing to httpDataOut", e);
497 }
498 } else {
499 throw new JUploadIOException("Wrong status in HTTPConnectionHelper.write() [" + getStatusLabel() + "]");
500 }
501 return this;
502 }
503
504
505
506
507
508
509
510
511
512 public synchronized HTTPConnectionHelper append(byte[] bytes) throws JUploadIOException {
513 return this.append(bytes, 0, bytes.length);
514 }
515
516
517
518
519
520
521
522
523
524
525
526 public synchronized HTTPConnectionHelper append(byte[] bytes, int off, int len) throws JUploadIOException {
527
528 if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
529 this.byteArrayEncoder.append(bytes);
530 } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
531 try {
532 this.outputStream.write(bytes, off, len);
533 } catch (IOException e) {
534 throw new JUploadIOException(e.getClass().getName() + " while writing to httpDataOut", e);
535 }
536 } else {
537 throw new JUploadIOException("Wrong status in HTTPConnectionHelper.write() [" + getStatusLabel() + "]");
538 }
539
540 if (this.uploadPolicy.getDebugLevel() > 100) {
541 this.uploadPolicy.displayDebug("[HTTPConnectionHelper append(byte[],int,int)] ("
542 + len
543 + " bytes appended to "
544 + (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION ? " current ByteArrayEncoder"
545 : " socket") + ")", 101);
546 }
547
548 return this;
549 }
550
551
552
553
554
555
556
557
558
559 public synchronized HTTPConnectionHelper append(String str) throws JUploadIOException {
560 this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] " + str, 70);
561 if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
562 this.byteArrayEncoder.append(str);
563 } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
564 ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy, this.byteArrayEncoder.getBoundary(),
565 this.byteArrayEncoder.getEncoding());
566 bae.append(str);
567 bae.close();
568 this.append(bae);
569 }
570 return this;
571 }
572
573
574
575
576
577
578
579
580
581
582 public synchronized HTTPConnectionHelper append(ByteArrayEncoder bae) throws JUploadIOException {
583 this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] " + bae.getString(), 70);
584 return this.append(bae.getEncodedByteArray());
585 }
586
587
588
589
590
591
592
593
594 public synchronized int readHttpResponse() throws JUploadException {
595
596
597 if (this.connectionStatus != STATUS_WRITING_REQUEST) {
598 throw new JUploadIOException("Bad status of the HttpConnectionHelper in readHttpResponse: "
599 + getStatusLabel());
600 }
601 this.connectionStatus = STATUS_READING_RESPONSE;
602
603
604 if (this.httpInputStreamReader == null) {
605 this.httpInputStreamReader = new HTTPInputStreamReader(this, this.uploadPolicy);
606 }
607
608
609 try {
610 this.outputStream.flush();
611 } catch (IOException ioe) {
612 throw new JUploadIOException("flushing outputStream, in " + getClass().getName() + ".readHttpResponse()");
613 }
614 this.httpInputStreamReader.readHttpResponse();
615
616 if (this.httpInputStreamReader.gotClose) {
617
618 dispose();
619 }
620
621
622 this.connectionStatus = STATUS_CONNECTION_CLOSED;
623
624
625
626
627 return this.httpInputStreamReader.gethttpStatusCode();
628 }
629
630
631
632
633
634
635
636
637
638
639
640 private synchronized void initByteArrayEncoder() throws JUploadIOException {
641 if (this.byteArrayEncoder != null && !this.byteArrayEncoder.isClosed()) {
642 this.byteArrayEncoder.close();
643 this.byteArrayEncoder = null;
644 }
645 this.byteArrayEncoder = new ByteArrayEncoderHTTP(this.uploadPolicy, this.boundary);
646 this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
647 this.proxy = null;
648 try {
649 this.proxy = ProxySelector.getDefault().select(this.url.toURI()).get(0);
650 } catch (URISyntaxException e) {
651 throw new JUploadIOException("Error while managing url " + this.url.toExternalForm(), e);
652 }
653 this.useProxy = ((this.proxy != null) && (this.proxy.type() != Proxy.Type.DIRECT));
654 this.useSSL = this.url.getProtocol().equals("https");
655
656
657
658 this.byteArrayEncoder.append(this.method);
659 this.byteArrayEncoder.append(" ");
660 this.uploadPolicy.displayDebug("[initByteArrayEncoder] proxy=" + proxy + ", proxy.type=" + this.proxy.type()
661 + ", useProxy=" + useProxy + ", url.host=" + this.url.getHost() + ", url.port=" + this.url.getPort(),
662 80);
663 if (this.useProxy && (!this.useSSL)) {
664
665
666
667 this.byteArrayEncoder.append(this.url.getProtocol()).append("://").append(this.url.getHost());
668
669 if (this.url.getPort() > 0) {
670 this.byteArrayEncoder.append(":").append(Integer.toString(this.url.getPort()));
671 }
672 }
673 this.byteArrayEncoder.append(this.url.getPath());
674
675
676
677
678
679
680 if (null != this.url.getQuery() && !"".equals(this.url.getQuery()))
681 this.byteArrayEncoder.append("?").append(this.url.getQuery());
682
683 this.byteArrayEncoder.append(" ").append(this.uploadPolicy.getServerProtocol()).append("\r\n");
684
685
686 this.byteArrayEncoder.append("Host: ").append(this.url.getHost());
687 if (this.url.getPort() > 0) {
688 this.byteArrayEncoder.append(":").append(Integer.toString(this.url.getPort()));
689 }
690 this.byteArrayEncoder.append("\r\nAccept: */*\r\n");
691
692
693
694 this.byteArrayEncoder.append("Accept-Encoding: identity\r\n");
695
696
697
698 if (!this.uploadPolicy.getAllowHttpPersistent()) {
699 this.byteArrayEncoder.append("Connection: close\r\n");
700 } else {
701 if (!this.bChunkEnabled || this.bLastChunk || this.useProxy
702 || !this.uploadPolicy.getServerProtocol().equals("HTTP/1.1")) {
703 this.byteArrayEncoder.append("Connection: close\r\n");
704 } else {
705 this.byteArrayEncoder.append("Keep-Alive: 300\r\n");
706 if (this.useProxy)
707 this.byteArrayEncoder.append("Proxy-Connection: keep-alive\r\n");
708 else
709 this.byteArrayEncoder.append("Connection: keep-alive\r\n");
710 }
711 }
712
713
714 this.uploadPolicy.onAppendHeader(this.byteArrayEncoder);
715 }
716
717
718
719
720 private boolean isKeepAlive() {
721 if (this.socket == null) {
722 return false;
723 } else if (!this.uploadPolicy.getAllowHttpPersistent()) {
724 return false;
725 } else {
726 if (!this.bChunkEnabled || this.bLastChunk || this.useProxy
727 || !this.uploadPolicy.getServerProtocol().equals("HTTP/1.1")) {
728 return false;
729 } else {
730 return true;
731 }
732 }
733 }
734
735
736
737
738
739
740 private final String calculateRandomBoundary() {
741 StringBuffer sbRan = new StringBuffer(11);
742 sbRan.append("-----------------------------");
743 String alphaNum = "1234567890abcdefghijklmnopqrstuvwxyz";
744 int num;
745 for (int i = 0; i < 11; i++) {
746 num = (int) (Math.random() * (alphaNum.length() - 1));
747 sbRan.append(alphaNum.charAt(num));
748 }
749 return sbRan.toString();
750 }
751
752
753
754
755
756 @Override
757 public void write(int b) throws IOException {
758 try {
759 append(b);
760 } catch (JUploadIOException e) {
761
762
763
764 if (e.getCause() == null) {
765
766 throw new IOException();
767 } else if (e.getCause() instanceof IOException) {
768 throw (IOException) e.getCause();
769 } else {
770
771 throw new IOException(e.getCause().getClass().getName() + ": " + e.getCause().getMessage());
772 }
773 }
774 }
775
776
777 @Override
778 public void write(byte[] b, int off, int len) throws IOException {
779 try {
780 append(b, off, len);
781 } catch (JUploadIOException e) {
782
783
784
785 if (e.getCause() == null) {
786
787 throw new IOException();
788 } else if (e.getCause() instanceof IOException) {
789 throw (IOException) e.getCause();
790 } else {
791
792 throw new IOException(e.getCause().getClass().getName() + ": " + e.getCause().getMessage());
793 }
794 }
795 }
796
797
798 @Override
799 public void write(byte[] b) throws IOException {
800 write(b, 0, b.length);
801 }
802
803
804
805
806
807
808
809 @Override
810 public void close() throws IOException {
811 this.uploadPolicy
812 .displayWarn("This method should not be called (HTTPConnectionHelper.close()). Please use the "
813 + getClass().getName() + ".sendRequest() method");
814 super.close();
815 }
816
817
818
819
820
821
822
823 @Override
824 public void flush() throws IOException {
825 if (this.connectionStatus == STATUS_WRITING_REQUEST) {
826 this.outputStream.flush();
827 } else {
828 throw new IOException("Wrong status in " + getClass().getName() + ".flush method: " + getStatusLabel());
829 }
830 }
831 }