Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.MissingNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import jakarta.annotation.Nullable;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.openvsx.adapter.ExtensionQueryResult;
Expand Down Expand Up @@ -472,7 +473,7 @@ private String tryGetAssetPath(String type) {
return null;
}

protected TempFile getIcon(ExtensionVersion extVersion) throws IOException {
public @Nullable TempFile getIcon(ExtensionVersion extVersion) throws IOException {
var iconPath = tryGetAssetPath(ExtensionQueryResult.ExtensionFile.FILE_ICON);
if (StringUtils.isEmpty(iconPath)) {
loadPackageJson();
Expand Down
25 changes: 9 additions & 16 deletions server/src/main/java/org/eclipse/openvsx/ExtensionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
import org.eclipse.openvsx.json.ResultJson;
import org.eclipse.openvsx.json.TargetPlatformVersionJson;
import org.eclipse.openvsx.publish.PublishExtensionVersionHandler;
import org.eclipse.openvsx.publish.PublishingConfig;
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.scanning.ExtensionScanPersistenceService;
import org.eclipse.openvsx.scanning.ExtensionScanService;
import org.eclipse.openvsx.search.SearchUtilService;
import org.eclipse.openvsx.util.*;
import org.jobrunr.scheduling.JobRequestScheduler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.io.BufferedInputStream;
import java.io.IOException;
Expand All @@ -42,10 +42,9 @@
import java.util.Objects;
import java.util.stream.Collectors;

@Component
@Service
public class ExtensionService {
private static final int MAX_CONTENT_SIZE = 512 * 1024 * 1024;

private final PublishingConfig publishingConfig;
private final EntityManager entityManager;
private final RepositoryService repositories;
private final SearchUtilService search;
Expand All @@ -56,10 +55,8 @@ public class ExtensionService {
private final ExtensionScanService scanService;
private final ExtensionScanPersistenceService scanPersistenceService;

@Value("${ovsx.publishing.max-content-size:" + MAX_CONTENT_SIZE + "}")
int maxContentSize;

public ExtensionService(
PublishingConfig publishingConfig,
EntityManager entityManager,
RepositoryService repositories,
SearchUtilService search,
Expand All @@ -70,6 +67,7 @@ public ExtensionService(
ExtensionScanService scanService,
ExtensionScanPersistenceService scanPersistenceService
) {
this.publishingConfig = publishingConfig;
this.entityManager = entityManager;
this.repositories = repositories;
this.search = search;
Expand All @@ -81,14 +79,8 @@ public ExtensionService(
this.scanPersistenceService = scanPersistenceService;
}

// For testing only
boolean isLicenseRequired() {
return publishHandler.isLicenseRequired();
}

// For testing only
void setLicenseRequired(boolean requireLicense) {
publishHandler.setLicenseRequired(requireLicense);
private long getMaxContentSize() {
return publishingConfig.getMaxContentSize();
}

@Transactional
Expand Down Expand Up @@ -164,6 +156,7 @@ private void doPublish(TempFile extensionFile, String binaryName, PersonalAccess
}

private TempFile createExtensionFile(InputStream content) {
long maxContentSize = getMaxContentSize();
try (var input = ByteStreams.limit(new BufferedInputStream(content), maxContentSize + 1)) {
long size;
var extensionFile = new TempFile("extension_", ".vsix");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,52 @@
* ****************************************************************************** */
package org.eclipse.openvsx.publish;

import com.google.common.base.Joiner;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import java.io.IOException;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.openvsx.ExtensionProcessor;
import org.eclipse.openvsx.ExtensionService;
import org.eclipse.openvsx.ExtensionValidator;
import org.eclipse.openvsx.UserService;
import org.eclipse.openvsx.adapter.VSCodeIdNewExtensionJobRequest;
import org.eclipse.openvsx.entities.*;
import org.eclipse.openvsx.entities.Extension;
import org.eclipse.openvsx.entities.ExtensionScan;
import org.eclipse.openvsx.entities.ExtensionVersion;
import org.eclipse.openvsx.entities.FileResource;
import org.eclipse.openvsx.entities.PersonalAccessToken;
import org.eclipse.openvsx.entities.UserData;
import org.eclipse.openvsx.extension_control.ExtensionControlService;
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.scanning.ExtensionScanService;
import org.eclipse.openvsx.util.*;
import org.eclipse.openvsx.util.ErrorResultException;
import org.eclipse.openvsx.util.ExtensionId;
import org.eclipse.openvsx.util.NamingUtil;
import org.eclipse.openvsx.util.TempFile;
import org.jobrunr.scheduling.JobRequestScheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerErrorException;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.function.Consumer;
import com.google.common.base.Joiner;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

@Component
public class PublishExtensionVersionHandler {

protected final Logger logger = LoggerFactory.getLogger(PublishExtensionVersionHandler.class);

@Value("${ovsx.publishing.require-license:false}")
boolean requireLicense;

private final PublishingConfig config;
private final PublishExtensionVersionService service;
private final ExtensionVersionIntegrityService integrityService;
private final EntityManager entityManager;
Expand All @@ -56,7 +65,10 @@ public class PublishExtensionVersionHandler {
private final ExtensionControlService extensionControl;
private final ExtensionScanService scanService;

private final Predicate<Path> unsupportedIconExtensions;

public PublishExtensionVersionHandler(
PublishingConfig config,
PublishExtensionVersionService service,
ExtensionVersionIntegrityService integrityService,
EntityManager entityManager,
Expand All @@ -67,6 +79,7 @@ public PublishExtensionVersionHandler(
ExtensionControlService extensionControl,
ExtensionScanService scanService
) {
this.config = config;
this.service = service;
this.integrityService = integrityService;
this.entityManager = entityManager;
Expand All @@ -76,14 +89,19 @@ public PublishExtensionVersionHandler(
this.validator = validator;
this.extensionControl = extensionControl;
this.scanService = scanService;
}

public boolean isLicenseRequired() {
return requireLicense;
this.unsupportedIconExtensions = path -> {
if (path == null) {
return false;
}

var fileExtension = FilenameUtils.getExtension(path.toString());
return config.getUnsupportedIconFormats().stream().anyMatch(ext -> ext.equalsIgnoreCase(fileExtension));
};
}

public void setLicenseRequired(boolean requireLicense) {
this.requireLicense = requireLicense;
public boolean isLicenseRequired() {
return config.isRequireLicense();
}

@Transactional(rollbackOn = ErrorResultException.class)
Expand Down Expand Up @@ -160,6 +178,7 @@ private ExtensionVersion createExtensionVersion(ExtensionProcessor processor, Us
extVersion.setExtension(extension);

validateLicense(processor, extVersion);
validateIcon(processor, extVersion);
validateMetadata(extVersion);
entityManager.persist(extVersion);
return extVersion;
Expand All @@ -185,7 +204,7 @@ private void validateExtensionName(String namespaceName, String extensionName, S
}

private void validateLicense(ExtensionProcessor processor, ExtensionVersion extVersion) {
if (requireLicense) {
if (isLicenseRequired()) {
// Check the extension's license
try (var licenseFile = processor.getLicense(extVersion)) {
checkLicense(extVersion, licenseFile);
Expand All @@ -202,6 +221,16 @@ private void checkLicense(ExtensionVersion extVersion, TempFile licenseFile) {
}
}

private void validateIcon(ExtensionProcessor processor, ExtensionVersion extVersion) {
try (var iconFile = processor.getIcon(extVersion)) {
if (iconFile != null && unsupportedIconExtensions.test(iconFile.getPath())) {
throw new ErrorResultException("This extension cannot be accepted as it uses an unsupported icon format.");
}
} catch (IOException e) {
throw new ServerErrorException("Failed to read icon file", e);
}
}

private void validateMetadata(ExtensionVersion extVersion) {
var metadataIssues = validator.validateMetadata(extVersion);
if (!metadataIssues.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/******************************************************************************
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*****************************************************************************/

package org.eclipse.openvsx.publish;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
@ConfigurationProperties("ovsx.publishing")
public class PublishingConfig {
private static final int MAX_CONTENT_SIZE = 512 * 1024 * 1024;

private long maxContentSize = MAX_CONTENT_SIZE;

private boolean requireLicense;

/**
* Allows to specify a list of unsupported icon formats identified by their extension.
* <p>
* See: <a href="https://github.com/eclipse-openvsx/openvsx/issues/2">Block SVG Images</a>
*/
private List<String> unsupportedIconFormats = List.of("svg");

public long getMaxContentSize() {
return maxContentSize;
}

public void setMaxContentSize(int maxContentSize) {
this.maxContentSize = maxContentSize;
}

public boolean isRequireLicense() {
return requireLicense;
}

public void setRequireLicense(boolean requireLicense) {
this.requireLicense = requireLicense;
}

public List<String> getUnsupportedIconFormats() {
return unsupportedIconFormats;
}

public void setUnsupportedIconFormats(List<String> unsupportedIconFormats) {
this.unsupportedIconFormats = unsupportedIconFormats;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ public void setNamespace(Namespace namespace) {

@Override
public void close() throws IOException {
Files.delete(path);
Files.deleteIfExists(path);
}
}
Loading
Loading