Skip to content

Commit 700658b

Browse files
committed
Validate complete JassDoc downloads
1 parent 763116f commit 700658b

2 files changed

Lines changed: 92 additions & 10 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/JassDocService.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.sql.PreparedStatement;
3030
import java.sql.ResultSet;
3131
import java.sql.SQLException;
32+
import java.sql.Statement;
3233
import java.time.Duration;
3334
import java.time.Instant;
3435
import java.util.ArrayList;
@@ -809,6 +810,9 @@ void restoreBackup(Path backup, Path target, IOException installFailure) {
809810

810811
private void validateDownloadedDatabase(Path downloaded) throws IOException {
811812
try (Connection conn = open(downloaded)) {
813+
if (!passesIntegrityCheck(conn)) {
814+
throw new IOException("Downloaded JassDoc database failed SQLite integrity check");
815+
}
812816
if (discoverSchemas(conn).isEmpty() && !hasCompatibleLegacyJassdocSchema(conn)) {
813817
throw new IOException("Downloaded file is not a compatible JassDoc database");
814818
}
@@ -817,6 +821,15 @@ private void validateDownloadedDatabase(Path downloaded) throws IOException {
817821
}
818822
}
819823

824+
private boolean passesIntegrityCheck(Connection conn) throws SQLException {
825+
try (Statement statement = conn.createStatement();
826+
ResultSet result = statement.executeQuery("PRAGMA integrity_check")) {
827+
return result.next()
828+
&& "ok".equalsIgnoreCase(result.getString(1))
829+
&& !result.next();
830+
}
831+
}
832+
820833
private void replaceAtomically(Path source, Path target) throws IOException {
821834
try {
822835
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING,
@@ -827,14 +840,7 @@ private void replaceAtomically(Path source, Path target) throws IOException {
827840
}
828841

829842
private HttpURLConnection openHttpConnection(URL url) throws IOException {
830-
Optional<String> proxySetting = Utils.getEnvOrConfig("WURST_JASSDOC_DB_PROXY");
831-
if (proxySetting.isEmpty()) {
832-
Optional<String> noProxy = firstConfigured("NO_PROXY", "no_proxy");
833-
if (noProxy.isPresent() && shouldBypassProxy(url.getHost(), noProxy.get())) {
834-
return (HttpURLConnection) url.openConnection();
835-
}
836-
proxySetting = firstConfigured("HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy");
837-
}
843+
Optional<String> proxySetting = selectProxySetting(url, Utils::getEnvOrConfig);
838844
if (proxySetting.isEmpty()) {
839845
return (HttpURLConnection) url.openConnection();
840846
}
@@ -851,6 +857,25 @@ private HttpURLConnection openHttpConnection(URL url) throws IOException {
851857
return connection;
852858
}
853859

860+
static Optional<String> selectProxySetting(URL url,
861+
Function<String, Optional<String>> lookup) {
862+
Optional<String> proxySetting = lookup.apply("WURST_JASSDOC_DB_PROXY");
863+
if (proxySetting.isPresent()) {
864+
return proxySetting;
865+
}
866+
Optional<String> noProxy = firstConfigured(lookup, "NO_PROXY", "no_proxy");
867+
if (noProxy.isPresent() && shouldBypassProxy(url.getHost(), noProxy.get())) {
868+
return Optional.empty();
869+
}
870+
if ("https".equalsIgnoreCase(url.getProtocol())) {
871+
return firstConfigured(lookup, "HTTPS_PROXY", "https_proxy");
872+
}
873+
if ("http".equalsIgnoreCase(url.getProtocol())) {
874+
return firstConfigured(lookup, "HTTP_PROXY", "http_proxy");
875+
}
876+
return Optional.empty();
877+
}
878+
854879
static URI parseHttpProxyUri(String value) throws IOException {
855880
URI proxyUri;
856881
try {
@@ -895,9 +920,10 @@ static boolean shouldBypassProxy(String host, String noProxySetting) {
895920
return false;
896921
}
897922

898-
private Optional<String> firstConfigured(String... names) {
923+
private static Optional<String> firstConfigured(
924+
Function<String, Optional<String>> lookup, String... names) {
899925
return Stream.of(names)
900-
.map(Utils::getEnvOrConfig)
926+
.map(lookup)
901927
.flatMap(Optional::stream)
902928
.findFirst();
903929
}

de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/languageserver/JassDocServiceTests.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
import org.testng.annotations.Test;
44

55
import java.io.IOException;
6+
import java.io.RandomAccessFile;
7+
import java.net.URI;
68
import java.nio.charset.StandardCharsets;
79
import java.nio.file.Files;
810
import java.nio.file.Path;
911
import java.sql.Connection;
1012
import java.sql.DriverManager;
1113
import java.sql.ResultSet;
1214
import java.sql.Statement;
15+
import java.util.Map;
16+
import java.util.Optional;
1317

1418
import static org.testng.Assert.assertEquals;
1519
import static org.testng.Assert.assertFalse;
@@ -74,6 +78,42 @@ public void incompleteLegacySchemaDoesNotReplaceExistingDatabase() throws Except
7478
assertEquals(Files.readString(target, StandardCharsets.UTF_8), "working database");
7579
}
7680

81+
@Test
82+
public void corruptDatabaseWithValidSchemaDoesNotReplaceExistingDatabase() throws Exception {
83+
Path dir = Files.createTempDirectory("jassdoc-corrupt-download-");
84+
Path target = dir.resolve("jassdoc-latest.db");
85+
Path downloaded = dir.resolve("download.tmp");
86+
Files.writeString(target, "working database", StandardCharsets.UTF_8);
87+
int pageSize;
88+
int indexRootPage;
89+
try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + downloaded.toAbsolutePath());
90+
Statement statement = conn.createStatement()) {
91+
statement.execute("CREATE TABLE docs(name TEXT, documentation TEXT)");
92+
for (int i = 0; i < 1_000; i++) {
93+
statement.execute("INSERT INTO docs VALUES ('name" + i + "', '"
94+
+ "documentation".repeat(40) + "')");
95+
}
96+
statement.execute("CREATE INDEX docs_name_idx ON docs(name)");
97+
try (ResultSet result = statement.executeQuery("PRAGMA page_size")) {
98+
assertTrue(result.next());
99+
pageSize = result.getInt(1);
100+
}
101+
try (ResultSet result = statement.executeQuery(
102+
"SELECT rootpage FROM sqlite_master WHERE name = 'docs_name_idx'")) {
103+
assertTrue(result.next());
104+
indexRootPage = result.getInt(1);
105+
}
106+
}
107+
try (RandomAccessFile file = new RandomAccessFile(downloaded.toFile(), "rw")) {
108+
file.seek((long) (indexRootPage - 1) * pageSize);
109+
file.write(new byte[32]);
110+
}
111+
112+
assertThrows(IOException.class,
113+
() -> new JassDocService().installDownloadedDatabase(downloaded, target));
114+
assertEquals(Files.readString(target, StandardCharsets.UTF_8), "working database");
115+
}
116+
77117
@Test
78118
public void restoreOverwritesAResidualPartialTarget() throws IOException {
79119
Path dir = Files.createTempDirectory("jassdoc-restore-");
@@ -116,6 +156,22 @@ public void proxyBypassSupportsStandardHostForms() {
116156
"github.com", "localhost, example.com"));
117157
}
118158

159+
@Test
160+
public void standardProxyMatchesRequestProtocol() throws Exception {
161+
Map<String, String> settings = Map.of(
162+
"HTTPS_PROXY", "http://secure-proxy.example:8443",
163+
"HTTP_PROXY", "http://plain-proxy.example:8080");
164+
165+
assertEquals(JassDocService.selectProxySetting(
166+
URI.create("https://github.com/example").toURL(),
167+
name -> Optional.ofNullable(settings.get(name))).orElseThrow(),
168+
"http://secure-proxy.example:8443");
169+
assertEquals(JassDocService.selectProxySetting(
170+
URI.create("http://mirror.example/jass.db").toURL(),
171+
name -> Optional.ofNullable(settings.get(name))).orElseThrow(),
172+
"http://plain-proxy.example:8080");
173+
}
174+
119175
@Test
120176
public void unsupportedTlsProxyIsRejectedExplicitly() {
121177
IOException error = expectThrows(IOException.class,

0 commit comments

Comments
 (0)