Line data Source code
1 : // Copyright (C) 2009 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.mime; 16 : 17 : import static java.util.Comparator.comparing; 18 : 19 : import com.google.common.flogger.FluentLogger; 20 : import com.google.gerrit.server.config.GerritServerConfig; 21 : import com.google.inject.Inject; 22 : import com.google.inject.Singleton; 23 : import eu.medsea.mimeutil.MimeException; 24 : import eu.medsea.mimeutil.MimeType; 25 : import eu.medsea.mimeutil.MimeUtil2; 26 : import java.io.InputStream; 27 : import java.util.Collection; 28 : import java.util.Collections; 29 : import java.util.HashSet; 30 : import java.util.Set; 31 : import org.eclipse.jgit.lib.Config; 32 : 33 : @Singleton 34 : public class MimeUtilFileTypeRegistry implements FileTypeRegistry { 35 145 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 36 : 37 : private static final String KEY_SAFE = "safe"; 38 : private static final String SECTION_MIMETYPE = "mimetype"; 39 : 40 : private final Config cfg; 41 : private final MimeUtil2 mimeUtil; 42 : 43 : @Inject 44 145 : MimeUtilFileTypeRegistry(@GerritServerConfig Config gsc, MimeUtil2 mu2) { 45 145 : cfg = gsc; 46 145 : mimeUtil = mu2; 47 145 : } 48 : 49 : /** 50 : * Get specificity of mime types with generic types forced to low values 51 : * 52 : * <p>"application/octet-stream" is forced to -1. "text/plain" is forced to 0. All other mime 53 : * types return the specificity reported by mimeType itself. 54 : * 55 : * @param mimeType The mimeType to get the corrected specificity for. 56 : * @return The corrected specificity. 57 : */ 58 : private int getCorrectedMimeSpecificity(MimeType mimeType) { 59 : // Although the documentation of MimeType's getSpecificity claims that for 60 : // example "application/octet-stream" always has a specificity of 0, it 61 : // effectively returns 1 for us. This causes problems when trying to get 62 : // the correct mime type via sorting. For example in 63 : // [application/octet-stream, image/x-icon] both mime types come with 64 : // specificity 1 for us. Hence, getMimeType below may end up using 65 : // application/octet-stream instead of the more specific image/x-icon. 66 : // Therefore, we have to force the specificity of generic types below the 67 : // default of 1. 68 : // 69 22 : final String mimeTypeStr = mimeType.toString(); 70 22 : if (mimeTypeStr.equals("application/octet-stream")) { 71 22 : return -1; 72 : } 73 22 : if (mimeTypeStr.equals("text/plain")) { 74 20 : return 0; 75 : } 76 6 : return mimeType.getSpecificity(); 77 : } 78 : 79 : @Override 80 : @SuppressWarnings("unchecked") 81 : public MimeType getMimeType(String path, byte[] content) { 82 24 : Set<MimeType> mimeTypes = new HashSet<>(); 83 24 : if (content != null && content.length > 0) { 84 : try { 85 24 : mimeTypes.addAll(mimeUtil.getMimeTypes(content)); 86 0 : } catch (MimeException e) { 87 0 : logger.atWarning().withCause(e).log("Unable to determine MIME type from content"); 88 24 : } 89 : } 90 24 : return getMimeType(mimeTypes, path); 91 : } 92 : 93 : @Override 94 : @SuppressWarnings("unchecked") 95 : public MimeType getMimeType(String path, InputStream is) { 96 0 : Set<MimeType> mimeTypes = new HashSet<>(); 97 : try { 98 0 : mimeTypes.addAll(mimeUtil.getMimeTypes(is)); 99 0 : } catch (MimeException e) { 100 0 : logger.atWarning().withCause(e).log("Unable to determine MIME type from content"); 101 0 : } 102 0 : return getMimeType(mimeTypes, path); 103 : } 104 : 105 : @SuppressWarnings("unchecked") 106 : private MimeType getMimeType(Set<MimeType> mimeTypes, String path) { 107 : try { 108 24 : mimeTypes.addAll(mimeUtil.getMimeTypes(path)); 109 0 : } catch (MimeException e) { 110 0 : logger.atWarning().withCause(e).log("Unable to determine MIME type from path"); 111 24 : } 112 : 113 24 : if (isUnknownType(mimeTypes)) { 114 6 : return MimeUtil2.UNKNOWN_MIME_TYPE; 115 : } 116 : 117 22 : return Collections.max(mimeTypes, comparing(this::getCorrectedMimeSpecificity)); 118 : } 119 : 120 : @Override 121 : public boolean isSafeInline(MimeType type) { 122 1 : if (MimeUtil2.UNKNOWN_MIME_TYPE.equals(type)) { 123 : // Most browsers perform content type sniffing when they get told 124 : // a generic content type. This is bad, so assume we cannot send 125 : // the file inline. 126 : // 127 0 : return false; 128 : } 129 : 130 1 : final boolean any = isSafe(cfg, "*/*", false); 131 1 : final boolean genericMedia = isSafe(cfg, type.getMediaType() + "/*", any); 132 1 : return isSafe(cfg, type.toString(), genericMedia); 133 : } 134 : 135 : private static boolean isSafe(Config cfg, String type, boolean def) { 136 1 : return cfg.getBoolean(SECTION_MIMETYPE, type, KEY_SAFE, def); 137 : } 138 : 139 : private static boolean isUnknownType(Collection<MimeType> mimeTypes) { 140 24 : if (mimeTypes.isEmpty()) { 141 0 : return true; 142 : } 143 24 : return mimeTypes.size() == 1 && mimeTypes.contains(MimeUtil2.UNKNOWN_MIME_TYPE); 144 : } 145 : }