blob: 0e9a2b7e15f7fb7ee8b42232cdcbe53a85f0054c [file] [log] [blame]
// Copyright (C) 2009 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.mime;
import static java.util.Comparator.comparing;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import eu.medsea.mimeutil.MimeException;
import eu.medsea.mimeutil.MimeType;
import eu.medsea.mimeutil.MimeUtil2;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
@Singleton
public class MimeUtilFileTypeRegistry implements FileTypeRegistry {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String KEY_SAFE = "safe";
private static final String SECTION_MIMETYPE = "mimetype";
private final Config cfg;
private final MimeUtil2 mimeUtil;
@Inject
MimeUtilFileTypeRegistry(@GerritServerConfig Config gsc, MimeUtil2 mu2) {
cfg = gsc;
mimeUtil = mu2;
}
/**
* Get specificity of mime types with generic types forced to low values
*
* <p>"application/octet-stream" is forced to -1. "text/plain" is forced to 0. All other mime
* types return the specificity reported by mimeType itself.
*
* @param mimeType The mimeType to get the corrected specificity for.
* @return The corrected specificity.
*/
private int getCorrectedMimeSpecificity(MimeType mimeType) {
// Although the documentation of MimeType's getSpecificity claims that for
// example "application/octet-stream" always has a specificity of 0, it
// effectively returns 1 for us. This causes problems when trying to get
// the correct mime type via sorting. For example in
// [application/octet-stream, image/x-icon] both mime types come with
// specificity 1 for us. Hence, getMimeType below may end up using
// application/octet-stream instead of the more specific image/x-icon.
// Therefore, we have to force the specificity of generic types below the
// default of 1.
//
final String mimeTypeStr = mimeType.toString();
if (mimeTypeStr.equals("application/octet-stream")) {
return -1;
}
if (mimeTypeStr.equals("text/plain")) {
return 0;
}
return mimeType.getSpecificity();
}
@Override
@SuppressWarnings("unchecked")
public MimeType getMimeType(String path, byte[] content) {
Set<MimeType> mimeTypes = new HashSet<>();
if (content != null && content.length > 0) {
try {
mimeTypes.addAll(mimeUtil.getMimeTypes(content));
} catch (MimeException e) {
logger.atWarning().withCause(e).log("Unable to determine MIME type from content");
}
}
return getMimeType(mimeTypes, path);
}
@Override
@SuppressWarnings("unchecked")
public MimeType getMimeType(String path, InputStream is) {
Set<MimeType> mimeTypes = new HashSet<>();
try {
mimeTypes.addAll(mimeUtil.getMimeTypes(is));
} catch (MimeException e) {
logger.atWarning().withCause(e).log("Unable to determine MIME type from content");
}
return getMimeType(mimeTypes, path);
}
@SuppressWarnings("unchecked")
private MimeType getMimeType(Set<MimeType> mimeTypes, String path) {
try {
mimeTypes.addAll(mimeUtil.getMimeTypes(path));
} catch (MimeException e) {
logger.atWarning().withCause(e).log("Unable to determine MIME type from path");
}
if (isUnknownType(mimeTypes)) {
return MimeUtil2.UNKNOWN_MIME_TYPE;
}
return Collections.max(mimeTypes, comparing(this::getCorrectedMimeSpecificity));
}
@Override
public boolean isSafeInline(MimeType type) {
if (MimeUtil2.UNKNOWN_MIME_TYPE.equals(type)) {
// Most browsers perform content type sniffing when they get told
// a generic content type. This is bad, so assume we cannot send
// the file inline.
//
return false;
}
final boolean any = isSafe(cfg, "*/*", false);
final boolean genericMedia = isSafe(cfg, type.getMediaType() + "/*", any);
return isSafe(cfg, type.toString(), genericMedia);
}
private static boolean isSafe(Config cfg, String type, boolean def) {
return cfg.getBoolean(SECTION_MIMETYPE, type, KEY_SAFE, def);
}
private static boolean isUnknownType(Collection<MimeType> mimeTypes) {
if (mimeTypes.isEmpty()) {
return true;
}
return mimeTypes.size() == 1 && mimeTypes.contains(MimeUtil2.UNKNOWN_MIME_TYPE);
}
}