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
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import static org.fao.geonet.resources.Resources.DEFAULT_LOGO_EXTENSION;


public class BaseMetadataIndexer implements IMetadataIndexer, ApplicationEventPublisherAware {
@Autowired
Expand Down Expand Up @@ -456,7 +454,9 @@ public void indexMetadata(final String metadataId,
}
}

String logoUUID = null;
// Group logo are in the harvester folder and contains extension in file name
boolean groupLogoAdded = false;

if (groupOwner != null) {
final Optional<Group> groupOpt = groupRepository.findById(groupOwner);
if (groupOpt.isPresent()) {
Expand All @@ -466,37 +466,29 @@ public void indexMetadata(final String metadataId,
if (group.getWebsite() != null && !group.getWebsite().isEmpty() && preferGroup) {
fields.put(Geonet.IndexFieldNames.GROUP_WEBSITE, group.getWebsite());
}
if (group.getLogo() != null && preferGroup) {
logoUUID = group.getLogo();
}
}
}

// Group logo are in the harvester folder and contains extension in file name
boolean added = false;
if (StringUtils.isNotEmpty(logoUUID)) {
final Path harvesterLogosDir = resources.locateHarvesterLogosDir(getServiceContext());
try (Resources.ResourceHolder logo = resources.getImage(getServiceContext(), logoUUID, harvesterLogosDir)) {
if (logo != null) {
added = true;
fields.put(Geonet.IndexFieldNames.LOGO,
"/images/harvesting/" + logo.getPath().getFileName());
if (preferGroup && StringUtils.isNotEmpty(group.getLogo())) {
final Path harvesterLogosDir = resources.locateHarvesterLogosDir(getServiceContext());
try (Resources.ResourceHolder logo = resources.getImage(getServiceContext(), group.getLogo(), harvesterLogosDir)) {
if (logo != null) {
groupLogoAdded = true;
fields.put(Geonet.IndexFieldNames.LOGO,
"/srv/api/groups/" + group.getId() + "/logo");
}
}
}
}
}

// If not available, use the local catalog logo
if (!added) {
// If not available, use the local catalog or the harvester logo
if (!groupLogoAdded) {
Source sourceCatalogue = sourceRepository.findOneByUuid(source);
logoUUID =
sourceCatalogue != null
&& StringUtils.isNotEmpty(sourceCatalogue.getLogo())
? sourceCatalogue.getLogo() : source + DEFAULT_LOGO_EXTENSION;
final Path logosDir = resources.locateLogosDir(getServiceContext());
try (Resources.ResourceHolder image = resources.getImage(getServiceContext(), logoUUID, logosDir)) {
if (image != null) {
fields.put(Geonet.IndexFieldNames.LOGO,
"/images/logos/" + logoUUID);
if (sourceCatalogue != null && StringUtils.isNotEmpty(sourceCatalogue.getLogo())) {
try (Resources.ResourceHolder image = resources.getImage(getServiceContext(), sourceCatalogue.getLogo(), logosDir)) {
if (image != null) {
fields.put(Geonet.IndexFieldNames.LOGO,
"/srv/api/sources/" + sourceCatalogue.getUuid() + "/logo");
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
return imagePath;
}
} else {
try (DirectoryStream<Path> possibleLogos = Files.newDirectoryStream(logosDir, imageName + ".*")) {

Check failure on line 36 in core/src/main/java/org/fao/geonet/resources/FileResources.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this code to not construct the path from user-controlled data.

See more on https://sonarcloud.io/project/issues?id=geonetwork_core-geonetwork&issues=AZ6r_zHqsFkeTS6Nl5Q7&open=AZ6r_zHqsFkeTS6Nl5Q7&pullRequest=9322
for (final Path next: possibleLogos) {
String ext = FilenameUtils.getExtension(next.getFileName().toString());
if (IMAGE_EXTENSIONS.contains(ext.toLowerCase())) {
if (IMAGE_EXTENSIONS.contains(ext.toLowerCase()) || "svg".equalsIgnoreCase(ext)) {
return next;
}
}
Expand Down Expand Up @@ -106,7 +106,7 @@
file = IO.toPath(filename);
}

if (!Files.exists(file)) {

Check warning on line 109 in core/src/main/java/org/fao/geonet/resources/FileResources.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this code to not construct the path from user-controlled data.

See more on https://sonarcloud.io/project/issues?id=geonetwork_core-geonetwork&issues=AZ6r_zHqsFkeTS6Nl5Q9&open=AZ6r_zHqsFkeTS6Nl5Q9&pullRequest=9322
Path webappCopy = null;
if (context != null) {
final String realPath = context.getRealPath(filename);
Expand All @@ -118,7 +118,7 @@
if (webappCopy == null) {
webappCopy = appPath.resolve(filename);
}
if (Files.exists(webappCopy)) {

Check warning on line 121 in core/src/main/java/org/fao/geonet/resources/FileResources.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this code to not construct the path from user-controlled data.

See more on https://sonarcloud.io/project/issues?id=geonetwork_core-geonetwork&issues=AZ6r_zHqsFkeTS6Nl5Q8&open=AZ6r_zHqsFkeTS6Nl5Q8&pullRequest=9322
IO.copyDirectoryOrFile(webappCopy, file, false);
}

Expand All @@ -127,7 +127,7 @@
final String suffixless = FilenameUtils.removeExtension(fileName);
final String suffix = FilenameUtils.getExtension(fileName);

if (!Files.exists(file) && IMAGE_WRITE_SUFFIXES.contains(suffix.toLowerCase())) {

Check warning on line 130 in core/src/main/java/org/fao/geonet/resources/FileResources.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this code to not construct the path from user-controlled data.

See more on https://sonarcloud.io/project/issues?id=geonetwork_core-geonetwork&issues=AZ6r_zHqsFkeTS6Nl5Q-&open=AZ6r_zHqsFkeTS6Nl5Q-&pullRequest=9322
// find a different format and convert it to our desired format
DirectoryStream.Filter<Path> filter = entry -> {
String name = entry.getFileName().toString();
Expand All @@ -136,13 +136,13 @@
boolean canReadImage = name.length() > indexOfDot && IMAGE_READ_SUFFIXES.contains(ext);
return startsWith && canReadImage;
};
try (DirectoryStream<Path> paths = Files.newDirectoryStream(file.getParent(), filter)) {

Check failure on line 139 in core/src/main/java/org/fao/geonet/resources/FileResources.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this code to not construct the path from user-controlled data.

See more on https://sonarcloud.io/project/issues?id=geonetwork_core-geonetwork&issues=AZ6r_zHqsFkeTS6Nl5Q6&open=AZ6r_zHqsFkeTS6Nl5Q6&pullRequest=9322
Iterator<Path> iter = paths.iterator();
if (iter.hasNext()) {
Path path = iter.next();
try (
InputStream in = IO.newInputStream(path);
OutputStream out = Files.newOutputStream(file)

Check failure on line 145 in core/src/main/java/org/fao/geonet/resources/FileResources.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this code to not construct the path from user-controlled data.

See more on https://sonarcloud.io/project/issues?id=geonetwork_core-geonetwork&issues=AZ6r_zHqsFkeTS6Nl5Q5&open=AZ6r_zHqsFkeTS6Nl5Q5&pullRequest=9322
) {
try {
BufferedImage image = ImageIO.read(in);
Expand Down
23 changes: 18 additions & 5 deletions core/src/main/java/org/fao/geonet/resources/ResourceFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
import jeeves.config.springutil.JeevesDelegatingFilterProxy;
import org.fao.geonet.NodeInfo;
import org.fao.geonet.domain.Pair;
import org.fao.geonet.domain.Source;
import org.fao.geonet.kernel.GeonetworkDataDirectory;
import org.fao.geonet.kernel.setting.SettingManager;
import org.fao.geonet.repository.SourceRepository;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.ServletWebRequest;
Expand All @@ -40,8 +42,11 @@
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;

import static com.google.common.net.MediaType.SVG_UTF_8;

/**
* Servlet for serving up resources located in GeoNetwork data directory. For example, this solves a
* largely historical issue because logos are hardcoded across the application to be in
Expand All @@ -52,8 +57,10 @@
* User: jeichar Date: 1/17/12 Time: 4:03 PM
*/
public class ResourceFilter implements Filter {
public static final String DEFAULT_LOGO = "GN3.png";
private static final int FIVE_DAYS = 60 * 60 * 24 * 5;
private static final int SIX_HOURS = 60 * 60 * 6;
public static final String IMAGES_LOGOS_FOLDER = "images/logos/";
private FilterConfig config;
private ServletContext servletContext;
private volatile Pair<byte[], Long> defaultImage;
Expand Down Expand Up @@ -94,13 +101,17 @@ public Instance(ServletRequest request, ServletResponse response) throws IOExcep
this.siteId = applicationContext.getBean(SettingManager.class).getSiteId();
this.resourcesDir = resources.locateResourcesDir(servletContext, applicationContext);
this.schemaPublicationDir = applicationContext.getBean(GeonetworkDataDirectory.class).getSchemaPublicationDir();
SourceRepository sourceRepository = applicationContext.getBean(SourceRepository.class);
this.nodeId = applicationContext.getBean(NodeInfo.class).getId();
if (defaultImage == null) {
defaultImage = resources.loadResource(resourcesDir, servletContext, appPath, "images/logos/" + siteId + ".png", new byte[0], -1);
Optional<Source> catalogues = sourceRepository.findById(this.nodeId);
String defaultImageName = IMAGES_LOGOS_FOLDER + (
catalogues.isPresent() ? catalogues.get().getLogo() : DEFAULT_LOGO);
defaultImage = resources.loadResource(resourcesDir, servletContext, appPath, defaultImageName, new byte[0], -1);
}
this.nodeId = applicationContext.getBean(NodeInfo.class).getId();
if (!faviconMap.containsKey(nodeId)) {
final byte[] defaultImageBytes = defaultImage.one();
addFavIcon(nodeId, resources.loadResource(resourcesDir, servletContext, appPath, "images/logos/" + siteId + ".ico",
addFavIcon(nodeId, resources.loadResource(resourcesDir, servletContext, appPath, IMAGES_LOGOS_FOLDER + siteId + ".ico",
defaultImageBytes, -1));
}

Expand Down Expand Up @@ -136,8 +147,8 @@ public void execute() throws IOException {

httpServletResponse.setContentType(contentType);
httpServletResponse.addHeader("Cache-Control", "max-age=" + SIX_HOURS + ", public");
if (filename.equals("images/logos/" + siteId + ".ico")) {
favicon = resources.loadResource(resourcesDir, servletContext, appPath, "images/logos/" + siteId + ".ico", favicon.one(),
if (filename.equals(IMAGES_LOGOS_FOLDER + siteId + ".ico")) {
favicon = resources.loadResource(resourcesDir, servletContext, appPath, IMAGES_LOGOS_FOLDER + siteId + ".ico", favicon.one(),
favicon.two());
addFavIcon(nodeId, favicon);

Expand Down Expand Up @@ -186,6 +197,8 @@ private String extensionToMediaType(String ext) {
contentType = MediaType.APPLICATION_XML_VALUE;
} else if(ext.equals("txt")) {
contentType = MediaType.TEXT_PLAIN_VALUE;
} else if (ext.equals("svg")) {
contentType = SVG_UTF_8.toString();
} else {
contentType = "image/" + ext;
}
Expand Down
1 change: 0 additions & 1 deletion core/src/main/java/org/fao/geonet/resources/Resources.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
*/
public abstract class Resources {
public static final String BLANK_LOGO = "blank.png";
public static final String DEFAULT_LOGO_EXTENSION = ".png";

protected final static Set<String> IMAGE_READ_SUFFIXES;
protected final static Set<String> IMAGE_WRITE_SUFFIXES;
Expand Down Expand Up @@ -290,7 +289,7 @@
try(ResourceHolder src = getImage(context, srcPath.getFileName().toString(), srcPath.getParent());
ResourceHolder des = getWritableImage(context, filename, logosDir)) {
if (src != null) {
java.nio.file.Files.copy(src.getPath(), des.getPath(), REPLACE_EXISTING, NOFOLLOW_LINKS);

Check failure on line 292 in core/src/main/java/org/fao/geonet/resources/Resources.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this code to not construct the path from user-controlled data.

See more on https://sonarcloud.io/project/issues?id=geonetwork_core-geonetwork&issues=AZ6sdZxPun0fPz_6VrUr&open=AZ6sdZxPun0fPz_6VrUr&pullRequest=9322
} else {
des.abort();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ <h2>
<i class="fa fa-fw fa-cog" />
<span />
</h2>
<img class="gn-source-logo" alt="" src="http://localhost:8080/geonetwork/srv/../images/logos/4e87228d-10f0-4f6d-92ac-a7243a8bec78.png" />
<img class="gn-source-logo" alt="" src="http://localhost:8080/geonetwork/srv/api/sources/4e87228d-10f0-4f6d-92ac-a7243a8bec78/logo" />
</section>
<section class="gn-md-side-access">
<a class="btn btn-block btn-primary" href="www.geonet.org">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ <h2>
<i class="fa fa-fw fa-cog" />
<span />
</h2>
<img class="gn-source-logo" alt="" src="http://localhost:8080/geonetwork/srv/../images/logos/4e87228d-10f0-4f6d-92ac-a7243a8bec78.png" />
<img class="gn-source-logo" alt="" src="http://localhost:8080/geonetwork/srv/api/sources/4e87228d-10f0-4f6d-92ac-a7243a8bec78/logo" />
</section>
<section class="gn-md-side-access">
<a class="btn btn-block btn-primary" href="www.geonet.org">
Expand Down
101 changes: 101 additions & 0 deletions services/src/main/java/org/fao/geonet/api/LogoUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright (C) 2001-2026 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
* Rome - Italy. email: geonetwork@osgeo.org
*/

package org.fao.geonet.api;

import jeeves.server.context.ServiceContext;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.fao.geonet.api.records.attachments.AttachmentsApi;
import org.fao.geonet.resources.Resources;
import org.springframework.web.context.request.WebRequest;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;

public final class LogoUtils {
public static final String API_GET_LOGO_NOTE = "If last-modified header "
+ "is present it is used to check if the logo has been modified since "
+ "the header date. If it hasn't been modified returns an empty 304 Not"
+ " Modified response. If modified returns the image. If there is "
+ "no logo then returns a transparent 1x1 px PNG image.";

private static final int SIX_HOURS = 60 * 60 * 6;

private static final String TRANSPARENT_1_X_1_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII";

private static final byte[] TRANSPARENT_1_X_1_PNG = org.apache.commons.codec.binary.Base64.decodeBase64(TRANSPARENT_1_X_1_PNG_BASE64);

private LogoUtils() {
}

public static Resources.ResourceHolder getImage(Resources resources,
ServiceContext serviceContext,
String logoRef) throws IOException {
final Path logosDir = resources.locateLogosDir(serviceContext);
final Path harvesterLogosDir = resources.locateHarvesterLogosDir(serviceContext);
Resources.ResourceHolder image = null;
if (isLocalLogoRef(logoRef)) {
image = resources.getImage(serviceContext, logoRef, logosDir);
if (image == null) {
image = resources.getImage(serviceContext, logoRef, harvesterLogosDir);
}
}
return image;
}

public static void writeImageOrTransparentLogo(WebRequest webRequest,
HttpServletResponse response,
Resources.ResourceHolder image) throws IOException {
if (image != null) {
FileTime lastModifiedTime = image.getLastModifiedTime();
response.setDateHeader("Expires", System.currentTimeMillis() + SIX_HOURS * 1000L);
if (webRequest.checkNotModified(lastModifiedTime.toMillis())) {
return;
}
response.setContentType(AttachmentsApi.getFileContentType(image.getPath().getFileName().toString()));
response.setContentLength((int) Files.size(image.getPath()));
response.addHeader("Cache-Control", "max-age=" + SIX_HOURS + ", public");
FileUtils.copyFile(image.getPath().toFile(), response.getOutputStream());
return;
}

if (webRequest.checkNotModified(0L)) {
return;
}
response.setContentType("image/png");
response.setContentLength(TRANSPARENT_1_X_1_PNG.length);
response.addHeader("Cache-Control", "max-age=" + SIX_HOURS + ", public");
response.getOutputStream().write(TRANSPARENT_1_X_1_PNG);
}

private static boolean isLocalLogoRef(String logoRef) {
return StringUtils.isNotBlank(logoRef)
&& !logoRef.startsWith("http://")
&& !logoRef.startsWith("https://")
&& !logoRef.startsWith("https//");
}
}
Loading
Loading