Compare commits

...

2 Commits

Author SHA1 Message Date
d47de0609c Improve zip resources handling 2026-04-12 20:37:55 +02:00
765f486938 Small fixes in HTTP server 2026-04-12 20:37:17 +02:00
2 changed files with 91 additions and 27 deletions

View File

@@ -224,8 +224,7 @@ public class FileInfoService {
archiveInfo.viewUrl = "/view" + requestedUri + "/1"; archiveInfo.viewUrl = "/view" + requestedUri + "/1";
Path zipPath = Paths.get(fileApiConfiguration.directory() + "/file/" + requestedPath); Path zipPath = Paths.get(fileApiConfiguration.directory() + "/file/" + requestedPath);
try { try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
ZipFile zipFile = new ZipFile(zipPath.toFile());
zipFile.stream().filter(e -> !e.isDirectory()).forEach(e -> { zipFile.stream().filter(e -> !e.isDirectory()).forEach(e -> {
String zipEntryName = e.getName(); String zipEntryName = e.getName();
String zipEntryNameUri = String zipEntryNameUri =

View File

@@ -3,6 +3,7 @@ package sh.rhiobet.lalafin.file;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -73,8 +74,9 @@ public class FileServeService {
} }
response = Response.ok(path.toFile()); response = Response.ok(path.toFile());
channel.position(rangeStart);
response.entity(new FileServeInputStream(Channels.newInputStream(channel), response.entity(new FileServeInputStream(Channels.newInputStream(channel),
rangeStart, rangeEnd)); rangeEnd + 1 - rangeStart));
response.header("Content-Length", response.header("Content-Length",
Long.toString(rangeEnd + 1 - rangeStart)); Long.toString(rangeEnd + 1 - rangeStart));
@@ -87,8 +89,8 @@ public class FileServeService {
channel.close(); channel.close();
} }
response.header("Accept-Ranges", "bytes"); response.header("Accept-Ranges", "bytes");
response.header("Content-Disposition", response.header("Content-Disposition", "inline; filename*=UTF-8''" +
"inline; filename=\"" + fileInfo.filename + "\""); URLEncoder.encode(fileInfo.filename, StandardCharsets.UTF_8).replace("+", "%20"));
response.header("Content-Type", FileHelper.getMimeType(fileInfo.filename)); response.header("Content-Type", FileHelper.getMimeType(fileInfo.filename));
if (path.toString().contains("/.thumbnails/")) { if (path.toString().contains("/.thumbnails/")) {
response.header("Cache-Control", "max-age=604800"); response.header("Cache-Control", "max-age=604800");
@@ -110,17 +112,19 @@ public class FileServeService {
ZipEntry zipEntry = zipFile.getEntry(name); ZipEntry zipEntry = zipFile.getEntry(name);
if (zipEntry == null) { if (zipEntry == null) {
zipFile.close();
throw new IOException(); throw new IOException();
} }
ResponseBuilder response; ResponseBuilder response;
long fileSize = zipEntry.getSize(); long fileSize = zipEntry.getSize();
response = Response.ok(zipFile.getInputStream(zipEntry)); response = Response.ok(new ZipEntryInputStream(zipFile, zipFile.getInputStream(zipEntry)));
response.header("Content-Length", Long.toString(fileSize)); response.header("Content-Length", Long.toString(fileSize));
response.header("Accept-Ranges", "bytes"); response.header("Accept-Ranges", "bytes");
response.header("Content-Disposition", "inline; filename=\"" + name + "\""); response.header("Content-Disposition", "inline; filename*=UTF-8''" +
URLEncoder.encode(fileInfo.filename, StandardCharsets.UTF_8).replace("+", "%20"));
response.header("Content-Type", FileHelper.getMimeType(name)); response.header("Content-Type", FileHelper.getMimeType(name));
return response.build(); return response.build();
} catch (IOException e) { } catch (IOException e) {
@@ -133,22 +137,14 @@ class FileServeInputStream extends InputStream {
private InputStream is; private InputStream is;
private long remaining; private long remaining;
public FileServeInputStream(InputStream is, long startRange, long endRange) throws IOException { public FileServeInputStream(InputStream is, long remaining) throws IOException {
this.is = is; this.is = is;
try { this.remaining = remaining;
this.is.skip(startRange);
} catch (IOException e) {
try {
this.is.close();
} catch (Exception ignored) {}
throw e;
}
this.remaining = endRange + 1 - startRange;
} }
@Override @Override
public int available() throws IOException { public int available() throws IOException {
return this.is.available(); return (int) Math.min(this.is.available(), this.remaining);
} }
@Override @Override
@@ -170,27 +166,37 @@ class FileServeInputStream extends InputStream {
@Override @Override
public byte[] readAllBytes() throws IOException { public byte[] readAllBytes() throws IOException {
return this.is.readAllBytes(); byte[] bytes = this.is.readNBytes((int) this.remaining);
this.remaining -= bytes.length;
return bytes;
} }
@Override @Override
public byte[] readNBytes(int arg0) throws IOException { public byte[] readNBytes(int len) throws IOException {
return this.is.readNBytes(arg0); byte[] bytes = this.is.readNBytes((int) Math.min(len, this.remaining));
this.remaining -= bytes.length;
return bytes;
} }
@Override @Override
public int readNBytes(byte[] arg0, int arg1, int arg2) throws IOException { public int readNBytes(byte[] buf, int off, int len) throws IOException {
return this.is.readNBytes(arg0, arg1, arg2); int read = this.is.readNBytes(buf, off, (int) Math.min(len, this.remaining));
this.remaining -= read;
return read;
} }
@Override @Override
public long skip(long arg0) throws IOException { public long skip(long n) throws IOException {
return this.is.skip(arg0); long skipped = this.is.skip(Math.min(n, this.remaining));
this.remaining -= skipped;
return skipped;
} }
@Override @Override
public void skipNBytes(long arg0) throws IOException { public void skipNBytes(long n) throws IOException {
this.is.skipNBytes(arg0); long toSkip = Math.min(n, this.remaining);
this.is.skipNBytes(toSkip);
this.remaining -= toSkip;
} }
@Override @Override
@@ -216,3 +222,62 @@ class FileServeInputStream extends InputStream {
} }
} }
} }
class ZipEntryInputStream extends InputStream {
private final ZipFile zipFile;
private final InputStream is;
public ZipEntryInputStream(ZipFile zipFile, InputStream is) {
this.zipFile = zipFile;
this.is = is;
}
@Override
public int read() throws IOException {
return this.is.read();
}
@Override
public int read(byte[] buffer) throws IOException {
return this.is.read(buffer);
}
@Override
public int read(byte[] buffer, int off, int len) throws IOException {
return this.is.read(buffer, off, len);
}
@Override
public byte[] readAllBytes() throws IOException {
return this.is.readAllBytes();
}
@Override
public byte[] readNBytes(int len) throws IOException {
return this.is.readNBytes(len);
}
@Override
public int readNBytes(byte[] buf, int off, int len) throws IOException {
return this.is.readNBytes(buf, off, len);
}
@Override
public long skip(long n) throws IOException {
return this.is.skip(n);
}
@Override
public int available() throws IOException {
return this.is.available();
}
@Override
public void close() throws IOException {
try {
this.is.close();
} finally {
this.zipFile.close();
}
}
}