22
22
import java .io .InputStream ;
23
23
import java .net .MalformedURLException ;
24
24
import java .net .URL ;
25
+ import java .net .URLConnection ;
26
+ import java .net .URLStreamHandler ;
25
27
import java .util .jar .Manifest ;
26
28
27
29
import org .springframework .boot .loader .util .AsciiBytes ;
33
35
*/
34
36
class JarURLConnection extends java .net .JarURLConnection {
35
37
36
- static final String PROTOCOL = "jar" ;
38
+ private static final FileNotFoundException FILE_NOT_FOUND_EXCEPTION = new FileNotFoundException () ;
37
39
38
- static final String SEPARATOR = "!/" ;
40
+ private static final String SEPARATOR = "!/" ;
39
41
40
- private static final String PREFIX = PROTOCOL + ":" + "file:" ;
42
+ private static final URL EMPTY_JAR_URL ;
41
43
42
- private final JarFile jarFile ;
44
+ static {
45
+ try {
46
+ EMPTY_JAR_URL = new URL ("jar:" , null , 0 , "file:!/" , new URLStreamHandler () {
47
+ @ Override
48
+ protected URLConnection openConnection (URL u ) throws IOException {
49
+ // Stub URLStreamHandler to prevent the wrong JAR Handler from being
50
+ // Instantiated and cached.
51
+ return null ;
52
+ }
53
+ });
54
+ }
55
+ catch (MalformedURLException ex ) {
56
+ throw new IllegalStateException (ex );
57
+ }
58
+ }
43
59
44
- private JarEntryData jarEntryData ;
60
+ private static final JarEntryName EMPTY_JAR_ENTRY_NAME = new JarEntryName ( "" ) ;
45
61
46
- private String jarEntryName ;
62
+ private static ThreadLocal < Boolean > useFastExceptions = new ThreadLocal < Boolean >() ;
47
63
48
- private String contentType ;
64
+ private final String jarFileUrlSpec ;
65
+
66
+ private final JarFile jarFile ;
67
+
68
+ private JarEntryData jarEntryData ;
49
69
50
70
private URL jarFileUrl ;
51
71
72
+ private JarEntryName jarEntryName ;
73
+
52
74
protected JarURLConnection (URL url , JarFile jarFile ) throws MalformedURLException {
53
- super (new URL (buildRootUrl (jarFile )));
75
+ // What we pass to super is ultimately ignored
76
+ super (EMPTY_JAR_URL );
54
77
this .url = url ;
55
78
this .jarFile = jarFile ;
56
-
57
79
String spec = url .getFile ();
58
80
int separator = spec .lastIndexOf (SEPARATOR );
59
81
if (separator == -1 ) {
60
82
throw new MalformedURLException ("no " + SEPARATOR + " found in url spec:"
61
83
+ spec );
62
84
}
63
- if ( separator + 2 ! = spec .length ()) {
64
- this .jarEntryName = decode (spec .substring (separator + 2 ));
65
- }
85
+ this . jarFileUrlSpec = spec .substring ( 0 , separator );
86
+ this .jarEntryName = getJarEntryName (spec .substring (separator + 2 ));
87
+ }
66
88
67
- String container = spec .substring (0 , separator );
68
- if (container .indexOf (SEPARATOR ) == -1 ) {
69
- this .jarFileUrl = new URL (container );
70
- }
71
- else {
72
- this .jarFileUrl = new URL ("jar:" + container );
89
+ private JarEntryName getJarEntryName (String spec ) {
90
+ if (spec .length () == 0 ) {
91
+ return EMPTY_JAR_ENTRY_NAME ;
73
92
}
93
+ return new JarEntryName (spec );
74
94
}
75
95
76
96
@ Override
77
97
public void connect () throws IOException {
78
- if (this .jarEntryName != null ) {
79
- this .jarEntryData = this .jarFile .getJarEntryData (this .jarEntryName );
98
+ if (!this .jarEntryName .isEmpty ()) {
99
+ this .jarEntryData = this .jarFile .getJarEntryData (this .jarEntryName
100
+ .asAsciiBytes ());
80
101
if (this .jarEntryData == null ) {
102
+ if (Boolean .TRUE .equals (useFastExceptions .get ())) {
103
+ throw FILE_NOT_FOUND_EXCEPTION ;
104
+ }
81
105
throw new FileNotFoundException ("JAR entry " + this .jarEntryName
82
106
+ " not found in " + this .jarFile .getName ());
83
107
}
@@ -103,9 +127,24 @@ public JarFile getJarFile() throws IOException {
103
127
104
128
@ Override
105
129
public URL getJarFileURL () {
130
+ if (this .jarFileUrl == null ) {
131
+ this .jarFileUrl = buildJarFileUrl ();
132
+ }
106
133
return this .jarFileUrl ;
107
134
}
108
135
136
+ private URL buildJarFileUrl () {
137
+ try {
138
+ if (this .jarFileUrlSpec .indexOf (SEPARATOR ) == -1 ) {
139
+ return new URL (this .jarFileUrlSpec );
140
+ }
141
+ return new URL ("jar:" + this .jarFileUrlSpec );
142
+ }
143
+ catch (MalformedURLException ex ) {
144
+ throw new IllegalStateException (ex );
145
+ }
146
+ }
147
+
109
148
@ Override
110
149
public JarEntry getJarEntry () throws IOException {
111
150
connect ();
@@ -114,13 +153,13 @@ public JarEntry getJarEntry() throws IOException {
114
153
115
154
@ Override
116
155
public String getEntryName () {
117
- return this .jarEntryName ;
156
+ return this .jarEntryName . toString () ;
118
157
}
119
158
120
159
@ Override
121
160
public InputStream getInputStream () throws IOException {
122
161
connect ();
123
- if (this .jarEntryName == null ) {
162
+ if (this .jarEntryName . isEmpty () ) {
124
163
throw new IOException ("no entry name specified" );
125
164
}
126
165
return this .jarEntryData .getInputStream ();
@@ -130,8 +169,10 @@ public InputStream getInputStream() throws IOException {
130
169
public int getContentLength () {
131
170
try {
132
171
connect ();
133
- return this .jarEntryData == null ? this .jarFile .size () : this .jarEntryData
134
- .getSize ();
172
+ if (this .jarEntryData != null ) {
173
+ return this .jarEntryData .getSize ();
174
+ }
175
+ return this .jarFile .size ();
135
176
}
136
177
catch (IOException ex ) {
137
178
return -1 ;
@@ -146,58 +187,86 @@ public Object getContent() throws IOException {
146
187
147
188
@ Override
148
189
public String getContentType () {
149
- if (this .contentType == null ) {
150
- // Guess the content type, don't bother with steams as mark is not
151
- // supported
152
- this .contentType = (this .jarEntryName == null ? "x-java/jar" : null );
153
- this .contentType = (this .contentType == null ? guessContentTypeFromName (this .jarEntryName )
154
- : this .contentType );
155
- this .contentType = (this .contentType == null ? "content/unknown"
156
- : this .contentType );
157
- }
158
- return this .contentType ;
159
- }
160
-
161
- private static String buildRootUrl (JarFile jarFile ) {
162
- String path = jarFile .getRootJarFile ().getFile ().getPath ();
163
- StringBuilder builder = new StringBuilder (PREFIX .length () + path .length ()
164
- + SEPARATOR .length ());
165
- builder .append (PREFIX );
166
- builder .append (path );
167
- builder .append (SEPARATOR );
168
- return builder .toString ();
169
- }
170
-
171
- private static String decode (String source ) {
172
- int length = source .length ();
173
- if ((length == 0 ) || (source .indexOf ('%' ) < 0 )) {
174
- return source ;
175
- }
176
- ByteArrayOutputStream bos = new ByteArrayOutputStream (length );
177
- for (int i = 0 ; i < length ; i ++) {
178
- int ch = source .charAt (i );
179
- if (ch == '%' ) {
180
- if ((i + 2 ) >= length ) {
181
- throw new IllegalArgumentException ("Invalid encoded sequence \" "
182
- + source .substring (i ) + "\" " );
190
+ return this .jarEntryName .getContentType ();
191
+ }
192
+
193
+ static void setUseFastExceptions (boolean useFastExceptions ) {
194
+ JarURLConnection .useFastExceptions .set (useFastExceptions );
195
+ }
196
+
197
+ /**
198
+ * A JarEntryName parsed from a URL String.
199
+ */
200
+ private static class JarEntryName {
201
+
202
+ private final AsciiBytes name ;
203
+
204
+ private String contentType ;
205
+
206
+ public JarEntryName (String spec ) {
207
+ this .name = decode (spec );
208
+ }
209
+
210
+ private AsciiBytes decode (String source ) {
211
+ int length = (source == null ? 0 : source .length ());
212
+ if ((length == 0 ) || (source .indexOf ('%' ) < 0 )) {
213
+ return new AsciiBytes (source );
214
+ }
215
+ ByteArrayOutputStream bos = new ByteArrayOutputStream (length );
216
+ for (int i = 0 ; i < length ; i ++) {
217
+ int ch = source .charAt (i );
218
+ if (ch == '%' ) {
219
+ if ((i + 2 ) >= length ) {
220
+ throw new IllegalArgumentException ("Invalid encoded sequence \" "
221
+ + source .substring (i ) + "\" " );
222
+ }
223
+ ch = decodeEscapeSequence (source , i );
224
+ i += 2 ;
183
225
}
184
- ch = decodeEscapeSequence (source , i );
185
- i += 2 ;
226
+ bos .write (ch );
186
227
}
187
- bos .write (ch );
228
+ // AsciiBytes is what is used to store the JarEntries so make it symmetric
229
+ return new AsciiBytes (bos .toByteArray ());
188
230
}
189
- // AsciiBytes is what is used to store the JarEntries so make it symmetric
190
- return new AsciiBytes (bos .toByteArray ()).toString ();
191
231
192
- }
232
+ private char decodeEscapeSequence (String source , int i ) {
233
+ int hi = Character .digit (source .charAt (i + 1 ), 16 );
234
+ int lo = Character .digit (source .charAt (i + 2 ), 16 );
235
+ if (hi == -1 || lo == -1 ) {
236
+ throw new IllegalArgumentException ("Invalid encoded sequence \" "
237
+ + source .substring (i ) + "\" " );
238
+ }
239
+ return ((char ) ((hi << 4 ) + lo ));
240
+ }
241
+
242
+ @ Override
243
+ public String toString () {
244
+ return this .name .toString ();
245
+ }
246
+
247
+ public AsciiBytes asAsciiBytes () {
248
+ return this .name ;
249
+ }
250
+
251
+ public boolean isEmpty () {
252
+ return this .name .length () == 0 ;
253
+ }
254
+
255
+ public String getContentType () {
256
+ if (this .contentType == null ) {
257
+ this .contentType = deduceContentType ();
258
+ }
259
+ return this .contentType ;
260
+ }
193
261
194
- private static char decodeEscapeSequence ( String source , int i ) {
195
- int hi = Character . digit ( source . charAt ( i + 1 ), 16 );
196
- int lo = Character . digit ( source . charAt ( i + 2 ), 16 );
197
- if ( hi == - 1 || lo == - 1 ) {
198
- throw new IllegalArgumentException ( "Invalid encoded sequence \" "
199
- + source . substring ( i ) + " \" " ) ;
262
+ private String deduceContentType ( ) {
263
+ // Guess the content type, don't bother with streams as mark is not supported
264
+ String type = ( isEmpty () ? "x-java/jar" : null );
265
+ type = ( type != null ? type : guessContentTypeFromName ( toString ()));
266
+ type = ( type != null ? type : "content/unknown" );
267
+ return type ;
200
268
}
201
- return (( char ) (( hi << 4 ) + lo ));
269
+
202
270
}
271
+
203
272
}
0 commit comments