Skip to content

Commit 954393d

Browse files
committed
Use [Content_Types].xml to set FileResource contentType
1 parent fb56cee commit 954393d

File tree

15 files changed

+154
-80
lines changed

15 files changed

+154
-80
lines changed

server/build.gradle

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ def versions = [
2929
junit: '5.7.1',
3030
testcontainers: '1.15.2',
3131
jackson: '2.12.5',
32-
woodstox: '6.2.4',
33-
tika: '2.4.0'
32+
woodstox: '6.2.4'
3433
]
3534
ext['junit-jupiter.version'] = versions.junit
3635
sourceCompatibility = versions.java
@@ -67,7 +66,6 @@ dependencies {
6766
implementation "org.springframework.security:spring-security-oauth2-jose"
6867
implementation "org.springframework.session:spring-session-jdbc"
6968
implementation "org.flywaydb:flyway-core:${versions.flyway}"
70-
implementation "org.apache.tika:tika-core:${versions.tika}"
7169
implementation "com.google.cloud:google-cloud-storage:${versions.gcloud}"
7270
implementation "com.azure:azure-storage-blob:${versions.azure}"
7371
implementation "io.springfox:springfox-boot-starter:${versions.springfox}"

server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@
99
********************************************************************************/
1010
package org.eclipse.openvsx;
1111

12-
import java.io.EOFException;
13-
import java.io.File;
14-
import java.io.IOException;
15-
import java.io.InputStream;
12+
import java.io.*;
13+
import java.nio.charset.StandardCharsets;
1614
import java.util.*;
1715
import java.util.regex.Pattern;
1816
import java.util.stream.Collectors;
@@ -38,6 +36,11 @@
3836
import org.slf4j.LoggerFactory;
3937
import org.springframework.data.util.Pair;
4038
import org.springframework.http.HttpStatus;
39+
import org.springframework.http.MediaType;
40+
import org.xml.sax.SAXException;
41+
42+
import javax.xml.parsers.DocumentBuilderFactory;
43+
import javax.xml.parsers.ParserConfigurationException;
4144

4245
/**
4346
* Processes uploaded extension files and extracts their metadata.
@@ -291,6 +294,8 @@ private List<String> getEngines(JsonNode node) {
291294

292295
public List<FileResource> getResources(ExtensionVersion extension) {
293296
var resources = new ArrayList<>(getAllResources(extension));
297+
var contentTypes = loadContentTypes(resources);
298+
294299
var binary = getBinary(extension);
295300
if (binary != null)
296301
resources.add(binary);
@@ -310,7 +315,9 @@ public List<FileResource> getResources(ExtensionVersion extension) {
310315
if (icon != null)
311316
resources.add(icon);
312317

313-
return resources;
318+
return resources.stream()
319+
.map(resource -> setContentType(resource, contentTypes))
320+
.collect(Collectors.toList());
314321
}
315322

316323
protected List<FileResource> getAllResources(ExtensionVersion extension) {
@@ -421,6 +428,48 @@ protected FileResource getLicense(ExtensionVersion extension) {
421428
return license;
422429
}
423430

431+
private Map<String, String> loadContentTypes(List<FileResource> resources) {
432+
var contentTypes = resources.stream()
433+
.filter(r -> r.getName().equals("[Content_Types].xml"))
434+
.findFirst()
435+
.map(FileResource::getContent)
436+
.map(this::parseContentTypesXml)
437+
.orElse(new HashMap<>());
438+
439+
contentTypes.putIfAbsent(".vsix", "application/zip");
440+
return contentTypes;
441+
}
442+
443+
private Map<String, String> parseContentTypesXml(byte[] content) {
444+
try (var input = new ByteArrayInputStream(content)) {
445+
var contentTypes = new HashMap<String, String>();
446+
var document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(input);
447+
var elements = document.getDocumentElement().getElementsByTagName("Default");
448+
for(var i = 0; i < elements.getLength(); i++) {
449+
var element = elements.item(i);
450+
var attributes = element.getAttributes();
451+
var extension = attributes.getNamedItem("Extension").getTextContent();
452+
var contentType = attributes.getNamedItem("ContentType").getTextContent();
453+
contentTypes.put(extension, contentType);
454+
}
455+
456+
return contentTypes;
457+
} catch (IOException | ParserConfigurationException | SAXException e) {
458+
logger.error("failed to read content types", e);
459+
return null;
460+
}
461+
}
462+
463+
private FileResource setContentType(FileResource resource, Map<String, String> contentTypes) {
464+
var resourceName = Optional.ofNullable(resource.getName()).orElse("");
465+
var fileExtensionIndex = resourceName.lastIndexOf('.');
466+
var fileExtension = fileExtensionIndex != -1 ? resourceName.substring(fileExtensionIndex) : "";
467+
var contentType = contentTypes.getOrDefault(fileExtension, MediaType.APPLICATION_OCTET_STREAM_VALUE);
468+
469+
resource.setContentType(contentType);
470+
return resource;
471+
}
472+
424473
private void detectLicense(byte[] content, ExtensionVersion extension) {
425474
if (Strings.isNullOrEmpty(extension.getLicense())) {
426475
var detection = new LicenseDetection();

server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ public ResponseEntity<byte[]> getFile(String namespace, String extensionName, St
165165
if (resource.getType().equals(DOWNLOAD))
166166
storageUtil.increaseDownloadCount(extVersion, resource);
167167
if (resource.getStorageType().equals(FileResource.STORAGE_DB)) {
168-
var headers = storageUtil.getFileResponseHeaders(fileName);
168+
var headers = storageUtil.getFileResponseHeaders(resource);
169169
return new ResponseEntity<>(resource.getContent(), headers, HttpStatus.OK);
170170
} else {
171171
return ResponseEntity.status(HttpStatus.FOUND)

server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAdapter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ public ResponseEntity<byte[]> getAsset(HttpServletRequest request,
309309
if (resource.getType().equals(FileResource.DOWNLOAD))
310310
storageUtil.increaseDownloadCount(extVersion, resource);
311311
if (resource.getStorageType().equals(FileResource.STORAGE_DB)) {
312-
var headers = storageUtil.getFileResponseHeaders(resource.getName());
312+
var headers = storageUtil.getFileResponseHeaders(resource);
313313
return new ResponseEntity<>(resource.getContent(), headers, HttpStatus.OK);
314314
} else {
315315
return ResponseEntity.status(HttpStatus.FOUND)
@@ -408,7 +408,7 @@ private ResponseEntity<byte[]> browseFile(
408408
String version
409409
) {
410410
if (resource.getStorageType().equals(FileResource.STORAGE_DB)) {
411-
var headers = storageUtil.getFileResponseHeaders(resource.getName());
411+
var headers = storageUtil.getFileResponseHeaders(resource);
412412
return new ResponseEntity<>(resource.getContent(), headers, HttpStatus.OK);
413413
} else {
414414
var namespace = new Namespace();

server/src/main/java/org/eclipse/openvsx/dto/FileResourceDTO.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
import org.eclipse.openvsx.entities.FileResource;
1313

14-
import java.util.Objects;
15-
1614
public class FileResourceDTO {
1715

1816
private final long extensionVersionId;
@@ -23,11 +21,21 @@ public class FileResourceDTO {
2321
private final String type;
2422
private String storageType;
2523
private byte[] content;
26-
27-
public FileResourceDTO(long id, long extensionVersionId, String name, String type, String storageType, byte[] content) {
24+
private String contentType;
25+
26+
public FileResourceDTO(
27+
long id,
28+
long extensionVersionId,
29+
String name,
30+
String type,
31+
String storageType,
32+
byte[] content,
33+
String contentType
34+
) {
2835
this(id, extensionVersionId, name, type);
2936
this.storageType = storageType;
3037
this.content = content;
38+
this.contentType = contentType;
3139
}
3240

3341
public FileResourceDTO(long id, long extensionVersionId, String name, String type) {
@@ -73,6 +81,8 @@ public byte[] getContent() {
7381
return content;
7482
}
7583

84+
public String getContentType() { return contentType; }
85+
7686
public boolean isWebResource() {
7787
return type.equals(FileResource.RESOURCE) && name.startsWith("extension/");
7888
}

server/src/main/java/org/eclipse/openvsx/entities/FileResource.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public class FileResource {
4646

4747
byte[] content;
4848

49+
String contentType;
50+
4951
@Column(length = 32)
5052
String storageType;
5153

@@ -90,6 +92,14 @@ public void setContent(byte[] content) {
9092
this.content = content;
9193
}
9294

95+
public String getContentType() {
96+
return contentType;
97+
}
98+
99+
public void setContentType(String contentType) {
100+
this.contentType = contentType;
101+
}
102+
93103
public String getStorageType() {
94104
return storageType;
95105
}

server/src/main/java/org/eclipse/openvsx/repositories/FileResourceDTORepository.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,28 @@ public class FileResourceDTORepository {
3030
DSLContext dsl;
3131

3232
public List<FileResourceDTO> findAll(Collection<Long> extensionIds, Collection<String> types) {
33-
return dsl.select(FILE_RESOURCE.ID, FILE_RESOURCE.EXTENSION_ID, FILE_RESOURCE.NAME, FILE_RESOURCE.TYPE)
33+
return dsl.select(
34+
FILE_RESOURCE.ID,
35+
FILE_RESOURCE.EXTENSION_ID,
36+
FILE_RESOURCE.NAME,
37+
FILE_RESOURCE.TYPE
38+
)
3439
.from(FILE_RESOURCE)
3540
.where(FILE_RESOURCE.EXTENSION_ID.in(extensionIds))
3641
.and(FILE_RESOURCE.TYPE.in(types))
3742
.fetchInto(FileResourceDTO.class);
3843
}
3944

4045
public List<FileResourceDTO> findAllResources(String namespaceName, String extensionName, String version, String prefix) {
41-
return dsl.select(FILE_RESOURCE.ID, FILE_RESOURCE.EXTENSION_ID, FILE_RESOURCE.NAME, FILE_RESOURCE.TYPE, FILE_RESOURCE.STORAGE_TYPE, FILE_RESOURCE.CONTENT)
46+
return dsl.select(
47+
FILE_RESOURCE.ID,
48+
FILE_RESOURCE.EXTENSION_ID,
49+
FILE_RESOURCE.NAME,
50+
FILE_RESOURCE.TYPE,
51+
FILE_RESOURCE.STORAGE_TYPE,
52+
FILE_RESOURCE.CONTENT,
53+
FILE_RESOURCE.CONTENT_TYPE
54+
)
4255
.from(FILE_RESOURCE)
4356
.join(EXTENSION_VERSION).on(EXTENSION_VERSION.ID.eq(FILE_RESOURCE.EXTENSION_ID))
4457
.join(EXTENSION).on(EXTENSION.ID.eq(EXTENSION_VERSION.EXTENSION_ID))

server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,21 @@ public void uploadFile(FileResource resource) {
6464
+ blobName + ": missing Azure blob service endpoint");
6565
}
6666

67-
uploadFile(resource.getContent(), resource.getName(), blobName);
67+
uploadFile(resource, blobName);
6868
}
6969

70-
protected void uploadFile(byte[] content, String fileName, String blobName) {
70+
protected void uploadFile(FileResource resource, String blobName) {
7171
var blobClient = getContainerClient().getBlobClient(blobName);
7272
var headers = new BlobHttpHeaders();
73-
headers.setContentType(StorageUtil.getFileType(fileName).toString());
74-
if (fileName.endsWith(".vsix")) {
75-
headers.setContentDisposition("attachment; filename=\"" + fileName + "\"");
73+
headers.setContentType(resource.getContentType());
74+
if (resource.getName().endsWith(".vsix")) {
75+
headers.setContentDisposition("attachment; filename=\"" + resource.getName() + "\"");
7676
} else {
77-
var cacheControl = StorageUtil.getCacheControl(fileName);
77+
var cacheControl = StorageUtil.getCacheControl(resource.getName());
7878
headers.setCacheControl(cacheControl.getHeaderValue());
7979
}
80+
81+
var content = resource.getContent();
8082
try (var dataStream = new ByteArrayInputStream(content)) {
8183
blobClient.upload(dataStream, content.length, true);
8284
blobClient.setHttpHeaders(headers);

server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,19 @@ public void uploadFile(FileResource resource) {
6464
+ objectId + ": missing Google bucket id");
6565
}
6666

67-
uploadFile(resource.getContent(), resource.getName(), objectId);
67+
uploadFile(resource, objectId);
6868
}
6969

70-
protected void uploadFile(byte[] content, String fileName, String objectId) {
70+
protected void uploadFile(FileResource resource, String objectId) {
7171
var blobInfoBuilder = BlobInfo.newBuilder(BlobId.of(bucketId, objectId))
72-
.setContentType(StorageUtil.getFileType(fileName).toString());
73-
if (fileName.endsWith(".vsix")) {
74-
blobInfoBuilder.setContentDisposition("attachment; filename=\"" + fileName + "\"");
72+
.setContentType(resource.getContentType());
73+
if (resource.getName().endsWith(".vsix")) {
74+
blobInfoBuilder.setContentDisposition("attachment; filename=\"" + resource.getName() + "\"");
7575
} else {
76-
var cacheControl = StorageUtil.getCacheControl(fileName);
76+
var cacheControl = StorageUtil.getCacheControl(resource.getName());
7777
blobInfoBuilder.setCacheControl(cacheControl.getHeaderValue());
7878
}
79-
getStorage().create(blobInfoBuilder.build(), content);
79+
getStorage().create(blobInfoBuilder.build(), resource.getContent());
8080
}
8181

8282
@Override

server/src/main/java/org/eclipse/openvsx/storage/StorageUtil.java

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,14 @@
1010

1111
package org.eclipse.openvsx.storage;
1212

13-
import org.apache.tika.Tika;
1413
import org.springframework.http.CacheControl;
15-
import org.springframework.http.MediaType;
1614

17-
import java.net.URLConnection;
1815
import java.util.concurrent.TimeUnit;
1916

2017
class StorageUtil {
2118

2219
private StorageUtil(){}
2320

24-
@Deprecated
25-
static MediaType getFileTypeDeprecated(String fileName) {
26-
if (fileName.endsWith(".vsix"))
27-
return MediaType.APPLICATION_OCTET_STREAM;
28-
if (fileName.endsWith(".json"))
29-
return MediaType.APPLICATION_JSON;
30-
var contentType = URLConnection.guessContentTypeFromName(fileName);
31-
if (contentType != null)
32-
return MediaType.parseMediaType(contentType);
33-
return MediaType.TEXT_PLAIN;
34-
}
35-
36-
static MediaType getFileType(String fileName) {
37-
var tika = new Tika();
38-
var contentType = tika.detect(fileName); // returns 'application/octet-stream' by default
39-
return MediaType.parseMediaType(contentType);
40-
}
41-
4221
static CacheControl getCacheControl(String fileName) {
4322
// Files are requested with a version string in the URL, so their content cannot change
4423
return CacheControl.maxAge(30, TimeUnit.DAYS).cachePublic();

0 commit comments

Comments
 (0)