mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-06 06:00:26 +00:00
8367561: Getting some "header" property from a file:// URL causes a file descriptor leak
Reviewed-by: dfuchs, vyazici
This commit is contained in:
parent
3cbcda5ff3
commit
4a0200caf9
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1995, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -101,9 +101,21 @@ public abstract class URLConnection extends java.net.URLConnection {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called whenever the headers related methods are called on the
|
||||
* {@code URLConnection}. This method does any necessary checks and initializations
|
||||
* to make sure that the headers can be served. If this {@code URLConnection} cannot
|
||||
* serve the headers, then this method throws an {@code IOException}.
|
||||
*
|
||||
* @throws IOException if the headers cannot be served
|
||||
*/
|
||||
protected void ensureCanServeHeaders() throws IOException {
|
||||
getInputStream();
|
||||
}
|
||||
|
||||
public String getHeaderField(String name) {
|
||||
try {
|
||||
getInputStream();
|
||||
ensureCanServeHeaders();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
@ -111,13 +123,13 @@ public abstract class URLConnection extends java.net.URLConnection {
|
||||
}
|
||||
|
||||
|
||||
Map<String, List<String>> headerFields;
|
||||
private Map<String, List<String>> headerFields;
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getHeaderFields() {
|
||||
if (headerFields == null) {
|
||||
try {
|
||||
getInputStream();
|
||||
ensureCanServeHeaders();
|
||||
if (properties == null) {
|
||||
headerFields = super.getHeaderFields();
|
||||
} else {
|
||||
@ -137,7 +149,7 @@ public abstract class URLConnection extends java.net.URLConnection {
|
||||
*/
|
||||
public String getHeaderFieldKey(int n) {
|
||||
try {
|
||||
getInputStream();
|
||||
ensureCanServeHeaders();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
@ -152,7 +164,7 @@ public abstract class URLConnection extends java.net.URLConnection {
|
||||
*/
|
||||
public String getHeaderField(int n) {
|
||||
try {
|
||||
getInputStream();
|
||||
ensureCanServeHeaders();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
@ -221,7 +233,7 @@ public abstract class URLConnection extends java.net.URLConnection {
|
||||
*/
|
||||
public int getContentLength() {
|
||||
try {
|
||||
getInputStream();
|
||||
ensureCanServeHeaders();
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -25,15 +25,30 @@
|
||||
|
||||
package sun.net.www.protocol.file;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FilePermission;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.FileNameMap;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.FileNameMap;
|
||||
import java.io.*;
|
||||
import java.text.Collator;
|
||||
import java.security.Permission;
|
||||
import sun.net.www.*;
|
||||
import java.util.*;
|
||||
import java.text.Collator;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import sun.net.www.MessageHeader;
|
||||
import sun.net.www.ParseUtil;
|
||||
import sun.net.www.URLConnection;
|
||||
|
||||
/**
|
||||
* Open a file input stream given a URL.
|
||||
@ -67,25 +82,49 @@ public class FileURLConnection extends URLConnection {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* If already connected, then this method is a no-op.
|
||||
* If not already connected, then this method does
|
||||
* readability checks for the File.
|
||||
* <p>
|
||||
* If the File is a directory then the readability check
|
||||
* is done by verifying that File.list() does not return
|
||||
* null. On the other hand, if the File is not a directory,
|
||||
* then this method constructs a temporary FileInputStream
|
||||
* for the File and lets the FileInputStream's constructor
|
||||
* implementation do the necessary readability checks.
|
||||
* That temporary FileInputStream is closed before returning
|
||||
* from this method.
|
||||
* <p>
|
||||
* In either case, if the readability checks fail, then
|
||||
* an IOException is thrown from this method and the
|
||||
* FileURLConnection stays unconnected.
|
||||
* <p>
|
||||
* A normal return from this method implies that the
|
||||
* FileURLConnection is connected and the readability
|
||||
* checks have passed for the File.
|
||||
* <p>
|
||||
* Note: the semantics of FileURLConnection object is that the
|
||||
* results of the various URLConnection calls, such as
|
||||
* getContentType, getInputStream or getContentLength reflect
|
||||
* whatever was true when connect was called.
|
||||
*/
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
if (!connected) {
|
||||
|
||||
isDirectory = file.isDirectory();
|
||||
// verify readability of the directory or the regular file
|
||||
if (isDirectory) {
|
||||
String[] fileList = file.list();
|
||||
if (fileList == null)
|
||||
if (fileList == null) {
|
||||
throw new FileNotFoundException(file.getPath() + " exists, but is not accessible");
|
||||
}
|
||||
directoryListing = Arrays.asList(fileList);
|
||||
} else {
|
||||
is = new BufferedInputStream(new FileInputStream(file.getPath()));
|
||||
// let FileInputStream constructor do the necessary readability checks
|
||||
// and propagate any failures
|
||||
new FileInputStream(file.getPath()).close();
|
||||
}
|
||||
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
@ -112,9 +151,9 @@ public class FileURLConnection extends URLConnection {
|
||||
FileNameMap map = java.net.URLConnection.getFileNameMap();
|
||||
String contentType = map.getContentTypeFor(file.getPath());
|
||||
if (contentType != null) {
|
||||
properties.add(CONTENT_TYPE, contentType);
|
||||
properties.set(CONTENT_TYPE, contentType);
|
||||
}
|
||||
properties.add(CONTENT_LENGTH, Long.toString(length));
|
||||
properties.set(CONTENT_LENGTH, Long.toString(length));
|
||||
|
||||
/*
|
||||
* Format the last-modified field into the preferred
|
||||
@ -126,30 +165,34 @@ public class FileURLConnection extends URLConnection {
|
||||
SimpleDateFormat fo =
|
||||
new SimpleDateFormat ("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
|
||||
fo.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
properties.add(LAST_MODIFIED, fo.format(date));
|
||||
properties.set(LAST_MODIFIED, fo.format(date));
|
||||
}
|
||||
} else {
|
||||
properties.add(CONTENT_TYPE, TEXT_PLAIN);
|
||||
properties.set(CONTENT_TYPE, TEXT_PLAIN);
|
||||
}
|
||||
initializedHeaders = true;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String,List<String>> getHeaderFields() {
|
||||
@Override
|
||||
public Map<String, List<String>> getHeaderFields() {
|
||||
initializeHeaders();
|
||||
return super.getHeaderFields();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeaderField(String name) {
|
||||
initializeHeaders();
|
||||
return super.getHeaderField(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeaderField(int n) {
|
||||
initializeHeaders();
|
||||
return super.getHeaderField(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContentLength() {
|
||||
initializeHeaders();
|
||||
if (length > Integer.MAX_VALUE)
|
||||
@ -157,54 +200,74 @@ public class FileURLConnection extends URLConnection {
|
||||
return (int) length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLengthLong() {
|
||||
initializeHeaders();
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeaderFieldKey(int n) {
|
||||
initializeHeaders();
|
||||
return super.getHeaderFieldKey(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageHeader getProperties() {
|
||||
initializeHeaders();
|
||||
return super.getProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
initializeHeaders();
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized InputStream getInputStream()
|
||||
throws IOException {
|
||||
|
||||
connect();
|
||||
// connect() does the necessary readability checks and is expected to
|
||||
// throw IOException if any of those checks fail. A normal completion of connect()
|
||||
// must mean that connect succeeded.
|
||||
assert connected : "not connected";
|
||||
|
||||
if (is == null) {
|
||||
if (isDirectory) {
|
||||
// a FileURLConnection only ever creates and provides a single InputStream
|
||||
if (is != null) {
|
||||
return is;
|
||||
}
|
||||
|
||||
if (directoryListing == null) {
|
||||
throw new FileNotFoundException(file.getPath());
|
||||
}
|
||||
if (isDirectory) {
|
||||
// a successful connect() implies the directoryListing is non-null
|
||||
// if the file is a directory
|
||||
assert directoryListing != null : "missing directory listing";
|
||||
|
||||
directoryListing.sort(Collator.getInstance());
|
||||
directoryListing.sort(Collator.getInstance());
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String fileName : directoryListing) {
|
||||
sb.append(fileName);
|
||||
sb.append("\n");
|
||||
}
|
||||
// Put it into a (default) locale-specific byte-stream.
|
||||
is = new ByteArrayInputStream(sb.toString().getBytes());
|
||||
} else {
|
||||
throw new FileNotFoundException(file.getPath());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String fileName : directoryListing) {
|
||||
sb.append(fileName);
|
||||
sb.append("\n");
|
||||
}
|
||||
// Put it into a (default) locale-specific byte-stream.
|
||||
is = new ByteArrayInputStream(sb.toString().getBytes());
|
||||
} else {
|
||||
is = new BufferedInputStream(new FileInputStream(file.getPath()));
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void ensureCanServeHeaders() throws IOException {
|
||||
// connect() (if not already connected) does the readability checks
|
||||
// and throws an IOException if those checks fail. A successful
|
||||
// completion from connect() implies the File is readable.
|
||||
connect();
|
||||
}
|
||||
|
||||
|
||||
Permission permission;
|
||||
|
||||
/* since getOutputStream isn't supported, only read permission is
|
||||
|
||||
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLConnection;
|
||||
import java.net.UnknownServiceException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8367561
|
||||
* @summary verify that the implementation of URLConnection APIs for "file:"
|
||||
* protocol does not leak InputStream(s)
|
||||
* @run junit/othervm ${test.main.class}
|
||||
*/
|
||||
class FileURLConnStreamLeakTest {
|
||||
|
||||
private static final String FILE_URLCONNECTION_CLASSNAME = "sun.net.www.protocol.file.FileURLConnection";
|
||||
|
||||
private Path testFile;
|
||||
// FileInputStream has a Cleaner which closes its underlying file descriptor.
|
||||
// Here we keep reference to the URLConnection for the duration of each test method.
|
||||
// This ensures that any FileInputStream this URLConnection may be retaining, won't be GCed
|
||||
// until after the test method has checked for file descriptor leaks.
|
||||
private URLConnection conn;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
final Path file = Files.createTempFile(Path.of("."), "8367561-", ".txt");
|
||||
Files.writeString(file, String.valueOf(System.currentTimeMillis()));
|
||||
this.testFile = file;
|
||||
this.conn = this.testFile.toUri().toURL().openConnection();
|
||||
assertNotNull(this.conn, "URLConnection for " + this.testFile + " is null");
|
||||
assertEquals(FILE_URLCONNECTION_CLASSNAME, conn.getClass().getName(),
|
||||
"unexpected URLConnection type");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() throws Exception {
|
||||
this.conn = null;
|
||||
// the file should already have been deleted by the test method
|
||||
Files.deleteIfExists(this.testFile);
|
||||
}
|
||||
|
||||
static List<Consumer<URLConnection>> urlConnOperations() {
|
||||
return List.of(
|
||||
URLConnection::getContentEncoding,
|
||||
URLConnection::getContentLength,
|
||||
URLConnection::getContentLengthLong,
|
||||
URLConnection::getContentType,
|
||||
URLConnection::getDate,
|
||||
URLConnection::getExpiration,
|
||||
URLConnection::getLastModified
|
||||
);
|
||||
}
|
||||
|
||||
@MethodSource("urlConnOperations")
|
||||
@ParameterizedTest
|
||||
void testURLConnOps(final Consumer<URLConnection> connConsumer) throws IOException {
|
||||
connConsumer.accept(this.conn);
|
||||
// verify that the URLConnection isn't holding on to any file descriptors
|
||||
// of this test file.
|
||||
Files.delete(this.testFile); // must not fail
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHeaderField() throws Exception {
|
||||
final var _ = this.conn.getHeaderField(0);
|
||||
// verify that the URLConnection isn't holding on to any file descriptors
|
||||
// of this test file.
|
||||
Files.delete(this.testFile); // must not fail
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHeaderFieldString() throws Exception {
|
||||
final String val = this.conn.getHeaderField("foo");
|
||||
assertNull(val, "unexpected header field value: " + val);
|
||||
// verify that the URLConnection isn't holding on to any file descriptors
|
||||
// of this test file.
|
||||
Files.delete(this.testFile); // must not fail
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHeaderFieldDate() throws Exception {
|
||||
final var _ = this.conn.getHeaderFieldDate("bar", 42);
|
||||
// verify that the URLConnection isn't holding on to any file descriptors
|
||||
// of this test file.
|
||||
Files.delete(this.testFile); // must not fail
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHeaderFieldInt() throws Exception {
|
||||
final int val = this.conn.getHeaderFieldInt("hello", 42);
|
||||
assertEquals(42, val, "unexpected header value");
|
||||
// verify that the URLConnection isn't holding on to any file descriptors
|
||||
// of this test file.
|
||||
Files.delete(this.testFile); // must not fail
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHeaderFieldKey() throws Exception {
|
||||
final String val = this.conn.getHeaderFieldKey(42);
|
||||
assertNull(val, "unexpected header value: " + val);
|
||||
// verify that the URLConnection isn't holding on to any file descriptors
|
||||
// of this test file.
|
||||
Files.delete(this.testFile); // must not fail
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHeaderFieldLong() throws Exception {
|
||||
final long val = this.conn.getHeaderFieldLong("foo", 42);
|
||||
assertEquals(42, val, "unexpected header value");
|
||||
// verify that the URLConnection isn't holding on to any file descriptors
|
||||
// of this test file.
|
||||
Files.delete(this.testFile); // must not fail
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHeaderFields() throws Exception {
|
||||
final Map<String, List<String>> headers = this.conn.getHeaderFields();
|
||||
assertNotNull(headers, "null headers");
|
||||
// verify that the URLConnection isn't holding on to any file descriptors
|
||||
// of this test file.
|
||||
Files.delete(this.testFile); // must not fail
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetInputStream() throws Exception {
|
||||
try (final InputStream is = this.conn.getInputStream()) {
|
||||
assertNotNull(is, "input stream is null");
|
||||
}
|
||||
// verify that the URLConnection isn't holding on to any file descriptors
|
||||
// of this test file.
|
||||
Files.delete(this.testFile); // must not fail
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetOutputStream() throws Exception {
|
||||
// FileURLConnection only supports reading
|
||||
assertThrows(UnknownServiceException.class, this.conn::getOutputStream);
|
||||
// verify that the URLConnection isn't holding on to any file descriptors
|
||||
// of this test file.
|
||||
Files.delete(this.testFile); // must not fail
|
||||
}
|
||||
}
|
||||
|
||||
150
test/jdk/sun/net/www/protocol/file/GetInputStreamTest.java
Normal file
150
test/jdk/sun/net/www/protocol/file/GetInputStreamTest.java
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.text.Collator;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary verify the behaviour of URLConnection.getInputStream()
|
||||
* for "file:" protocol
|
||||
* @run junit ${test.main.class}
|
||||
*/
|
||||
class GetInputStreamTest {
|
||||
|
||||
/**
|
||||
* Calls URLConnection.getInputStream() on the URLConnection for a directory and verifies
|
||||
* the contents returned by the InputStream.
|
||||
*/
|
||||
@Test
|
||||
void testDirInputStream() throws Exception {
|
||||
final Path dir = Files.createTempDirectory(Path.of("."), "fileurlconn-");
|
||||
final int numEntries = 3;
|
||||
// write some files into that directory
|
||||
for (int i = 1; i <= numEntries; i++) {
|
||||
Files.writeString(dir.resolve(i + ".txt"), "" + i);
|
||||
}
|
||||
final String expectedDirListing = getDirListing(dir.toFile(), numEntries);
|
||||
final URLConnection conn = dir.toUri().toURL().openConnection();
|
||||
assertNotNull(conn, "URLConnection is null for " + dir);
|
||||
// call getInputStream() and verify that the streamed directory
|
||||
// listing is the expected one
|
||||
try (final InputStream is = conn.getInputStream()) {
|
||||
assertNotNull(is, "InputStream is null for " + conn);
|
||||
final String actual = new BufferedReader(new InputStreamReader(is))
|
||||
.readAllAsString();
|
||||
assertEquals(expectedDirListing, actual,
|
||||
"unexpected content from input stream for dir " + dir);
|
||||
}
|
||||
// now that we successfully obtained the InputStream, read its content
|
||||
// and closed it, call getInputStream() again and verify that it can no longer
|
||||
// be used to read any more content.
|
||||
try (final InputStream is = conn.getInputStream()) {
|
||||
assertNotNull(is, "input stream is null for " + conn);
|
||||
final int readByte = is.read();
|
||||
assertEquals(-1, readByte, "expected to have read EOF from the stream");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls URLConnection.getInputStream() on the URLConnection for a regular file and verifies
|
||||
* the contents returned by the InputStream.
|
||||
*/
|
||||
@Test
|
||||
void testRegularFileInputStream() throws Exception {
|
||||
final Path dir = Files.createTempDirectory(Path.of("."), "fileurlconn-");
|
||||
final Path regularFile = dir.resolve("foo.txt");
|
||||
final String expectedContent = "bar";
|
||||
Files.writeString(regularFile, expectedContent);
|
||||
|
||||
final URLConnection conn = regularFile.toUri().toURL().openConnection();
|
||||
assertNotNull(conn, "URLConnection is null for " + regularFile);
|
||||
// get the input stream and verify the streamed content
|
||||
try (final InputStream is = conn.getInputStream()) {
|
||||
assertNotNull(is, "input stream is null for " + conn);
|
||||
final String actual = new BufferedReader(new InputStreamReader(is))
|
||||
.readAllAsString();
|
||||
assertEquals(expectedContent, actual,
|
||||
"unexpected content from input stream for file " + regularFile);
|
||||
}
|
||||
// now that we successfully obtained the InputStream, read its content
|
||||
// and closed it, call getInputStream() again and verify that it can no longer
|
||||
// be used to read any more content.
|
||||
try (final InputStream is = conn.getInputStream()) {
|
||||
assertNotNull(is, "input stream is null for " + conn);
|
||||
// for regular files the FileURLConnection's InputStream throws a IOException
|
||||
// when attempting to read after EOF
|
||||
final IOException thrown = assertThrows(IOException.class, is::read);
|
||||
final String exMessage = thrown.getMessage();
|
||||
assertEquals("Stream closed", exMessage, "unexpected exception message");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that URLConnection.getInputStream() for a non-existent file path
|
||||
* throws FileNotFoundException.
|
||||
*/
|
||||
@Test
|
||||
void testNonExistentFile() throws Exception {
|
||||
final Path existentDir = Files.createTempDirectory(Path.of("."), "fileurlconn-");
|
||||
final Path nonExistent = existentDir.resolve("non-existent");
|
||||
final URLConnection conn = nonExistent.toUri().toURL().openConnection();
|
||||
assertNotNull(conn, "URLConnection is null for " + nonExistent);
|
||||
final FileNotFoundException thrown = assertThrows(FileNotFoundException.class,
|
||||
conn::getInputStream);
|
||||
final String exMessage = thrown.getMessage();
|
||||
assertTrue(exMessage != null && exMessage.contains(nonExistent.getFileName().toString()),
|
||||
"unexpected exception message: " + exMessage);
|
||||
}
|
||||
|
||||
private static String getDirListing(final File dir, final int numExpectedEntries) {
|
||||
final List<String> dirListing = Arrays.asList(dir.list());
|
||||
dirListing.sort(Collator.getInstance()); // same as what FileURLConnection does
|
||||
|
||||
assertEquals(numExpectedEntries, dirListing.size(),
|
||||
dir + " - expected " + numExpectedEntries + " entries but found: " + dirListing);
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (String fileName : dirListing) {
|
||||
sb.append(fileName);
|
||||
sb.append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user