Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/java.base/share/classes/sun/net/www/URLConnection.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -111,7 +111,7 @@ public String getHeaderField(String name) {
}


Map<String, List<String>> headerFields;
private Map<String, List<String>> headerFields;

@Override
public Map<String, List<String>> getHeaderFields() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,31 @@

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.Collections;
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.
Expand Down Expand Up @@ -61,31 +77,55 @@ public class FileURLConnection extends URLConnection {

private long length = -1;
private long lastModified = 0;
private Map<String, List<String>> headerFields;

protected FileURLConnection(URL u, File file) {
super(u);
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()));
try (var _ = new FileInputStream(file.getPath())) {
}
}

connected = true;
}
}
Expand Down Expand Up @@ -135,76 +175,125 @@ private void initializeHeaders() {
}
}

public Map<String,List<String>> getHeaderFields() {
@Override
public Map<String, List<String>> getHeaderFields() {
initializeHeaders();
return super.getHeaderFields();
if (headerFields == null) {
if (!isReadable()) {
return Collections.emptyMap();
}
if (properties == null) {
headerFields = Collections.emptyMap();
} else {
headerFields = properties.getHeaders();
}
}
return headerFields;
}

@Override
public String getHeaderField(String name) {
initializeHeaders();
return super.getHeaderField(name);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several header related method implementations in this class have been updated to avoid the super.XXXX() calls. The super.XXX() implementation was calling getInputStream() and ignoring/not using the returned InputStream. That call to getInputStream() was a way to initiate a readability check and if the File wasn't readable then it would return a different result compared to when it was readable. The use of getInputStream() has been replaced by a call to isReadable() which does the same checks without leaving around a InputStream instance. Apart from that change, the rest of the code that resided in the super.XXX() implementation has been literally copy/pasted in these methods to avoid any kind of behavioural change.

if (!isReadable()) {
return null;
}
return properties == null ? null : properties.findValue(name);
}

@Override
public String getHeaderField(int n) {
initializeHeaders();
return super.getHeaderField(n);
if (!isReadable()) {
return null;
}
MessageHeader props = properties;
return props == null ? null : props.getValue(n);
}

@Override
public int getContentLength() {
initializeHeaders();
if (length > Integer.MAX_VALUE)
return -1;
return (int) length;
}

@Override
public long getContentLengthLong() {
initializeHeaders();
return length;
}

@Override
public String getHeaderFieldKey(int n) {
initializeHeaders();
return super.getHeaderFieldKey(n);
if (!isReadable()) {
return null;
}
MessageHeader props = properties;
return props == null ? null : props.getKey(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;
}

private synchronized boolean isReadable() {
try {
// 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();
} catch (IOException e) {
return false;
}
return true;
}


Permission permission;

/* since getOutputStream isn't supported, only read permission is
Expand Down
Loading