1 /*
2 * junixsocket
3 *
4 * Copyright 2009-2026 Christian Kohlschütter
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package org.newsclub.net.unix.ssl;
19
20 import java.io.FilterInputStream;
21 import java.io.FilterOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.net.Socket;
26 import java.net.SocketException;
27
28 import javax.net.ssl.SSLSocket;
29
30 /**
31 * A wrapper for {@link SSLSocket} instances, with certain modifications to navigate in the real
32 * world.
33 * <p>
34 * Modified behavior:
35 * <ol>
36 * <li>Ignore {@link SocketException}s thrown upon {@link #close()}, including
37 * {@link #getInputStream()} and {@link #getOutputStream()}; however attempt closing the underlying
38 * socket upon {@link #close()} if desired. On Android, "broken pipe" exceptions may be thrown upon
39 * close.</li>
40 * </ol>
41 *
42 * @author Christian Kohlschütter
43 */
44 final class BuilderSSLSocket extends FilterSSLSocket {
45 /**
46 * The underlying {@link Socket}, or {@code null}.
47 */
48 private final Socket underlyingSocket;
49
50 /**
51 * Whether the underlying {@link Socket} should be forcibly closed upon {@link #close()} when
52 * {@link SSLSocket#close()} throws an {@link IOException}.
53 */
54 private final boolean doCloseUnderlyingSocket;
55
56 BuilderSSLSocket(SSLSocket socket, Socket underlyingSocket, boolean doCloseUnderlyingSocket) {
57 super(socket);
58 this.underlyingSocket = underlyingSocket;
59 this.doCloseUnderlyingSocket = doCloseUnderlyingSocket;
60 }
61
62 @Override
63 public InputStream getInputStream() throws IOException {
64 return new FilterInputStream(super.getInputStream()) {
65
66 @Override
67 public void close() throws IOException {
68 try {
69 super.close();
70 } catch (SocketException e) {
71 // BouncyCastle may throw "broken pipe" upon close
72 // ignore
73 }
74 }
75 };
76 }
77
78 @Override
79 public OutputStream getOutputStream() throws IOException {
80 return new FilterOutputStream(super.getOutputStream()) {
81
82 @Override
83 public void close() throws IOException {
84 try {
85 super.close();
86 } catch (SocketException e) {
87 // BouncyCastle may throw "broken pipe" upon close
88 // ignore
89 }
90 }
91 };
92 }
93
94 @Override
95 public synchronized void close() throws IOException {
96 try {
97 super.close();
98 } catch (SocketException e) {
99 // BouncyCastle may throw "broken pipe" upon close
100 // ignore, but make sure we close the underlying socket if desired
101 if (doCloseUnderlyingSocket) {
102 Socket s = underlyingSocket;
103 if (s != null) {
104 try {
105 s.close();
106 } catch (IOException e1) {
107 e1.addSuppressed(e);
108 throw e1;
109 }
110 }
111 }
112 }
113 }
114 }