| /* |
| * Copyright (C) 2014 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.android.tools.lint.checks; |
| |
| import static com.android.SdkConstants.DOT_PROPERTIES; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.tools.lint.detector.api.Category; |
| import com.android.tools.lint.detector.api.Context; |
| import com.android.tools.lint.detector.api.Detector; |
| import com.android.tools.lint.detector.api.Implementation; |
| import com.android.tools.lint.detector.api.Issue; |
| import com.android.tools.lint.detector.api.LintUtils; |
| import com.android.tools.lint.detector.api.Location; |
| import com.android.tools.lint.detector.api.Scope; |
| import com.android.tools.lint.detector.api.Severity; |
| import com.android.tools.lint.detector.api.TextFormat; |
| import com.google.common.base.Splitter; |
| |
| import java.io.File; |
| import java.util.Iterator; |
| |
| /** |
| * Check for errors in .property files |
| * <p> |
| * TODO: Warn about bad paths like sdk properties with ' in the path, or suffix of " " etc |
| */ |
| public class PropertyFileDetector extends Detector { |
| /** Property file not escaped */ |
| public static final Issue ESCAPE = Issue.create( |
| "PropertyEscape", //$NON-NLS-1$ |
| "Incorrect property escapes", |
| "All backslashes and colons in .property files must be escaped with " + |
| "a backslash (\\). This means that when writing a Windows path, you " + |
| "must escape the file separators, so the path \\My\\Files should be " + |
| "written as `key=\\\\My\\\\Files.`", |
| |
| Category.CORRECTNESS, |
| 6, |
| Severity.ERROR, |
| new Implementation( |
| PropertyFileDetector.class, |
| Scope.PROPERTY_SCOPE)); |
| |
| /** Using HTTP instead of HTTPS for the wrapper */ |
| public static final Issue HTTP = Issue.create( |
| "UsingHttp", //$NON-NLS-1$ |
| "Using HTTP instead of HTTPS", |
| "The Gradle Wrapper is available both via HTTP and HTTPS. HTTPS is more " + |
| "secure since it protects against man-in-the-middle attacks etc. Older " + |
| "projects created in Android Studio used HTTP but we now default to HTTPS " + |
| "and recommend upgrading existing projects.", |
| |
| Category.SECURITY, |
| 6, |
| Severity.WARNING, |
| new Implementation( |
| PropertyFileDetector.class, |
| Scope.PROPERTY_SCOPE)); |
| |
| /** Constructs a new {@link PropertyFileDetector} */ |
| public PropertyFileDetector() { |
| } |
| |
| @Override |
| public boolean appliesTo(@NonNull Context context, @NonNull File file) { |
| return file.getPath().endsWith(DOT_PROPERTIES); |
| } |
| |
| @Override |
| public void run(@NonNull Context context) { |
| String contents = context.getContents(); |
| if (contents == null) { |
| return; |
| } |
| int offset = 0; |
| Iterator<String> iterator = Splitter.on('\n').split(contents).iterator(); |
| String line; |
| for (; iterator.hasNext(); offset += line.length() + 1) { |
| line = iterator.next(); |
| if (line.startsWith("#") || line.startsWith(" ")) { |
| continue; |
| } |
| if (line.indexOf('\\') == -1 && line.indexOf(':') == -1) { |
| continue; |
| } |
| int valueStart = line.indexOf('=') + 1; |
| if (valueStart == 0) { |
| continue; |
| } |
| checkLine(context, contents, line, offset, valueStart); |
| } |
| } |
| |
| private static void checkLine(@NonNull Context context, @NonNull String contents, |
| @NonNull String line, int offset, int valueStart) { |
| String prefix = "distributionUrl=http\\"; |
| if (line.startsWith(prefix)) { |
| String https = "https" + line.substring(prefix.length() - 1); |
| String message = String.format("Replace HTTP with HTTPS for better security; use %1$s", |
| https); |
| int startOffset = offset + valueStart; |
| int endOffset = startOffset + 4; // 4: "http".length() |
| Location location = Location.create(context.file, contents, startOffset, endOffset); |
| context.report(HTTP, location, message); |
| } |
| |
| boolean escaped = false; |
| boolean hadNonPathEscape = false; |
| int errorStart = -1; |
| int errorEnd = -1; |
| StringBuilder path = new StringBuilder(); |
| for (int i = valueStart; i < line.length(); i++) { |
| char c = line.charAt(i); |
| if (c == '\\') { |
| escaped = !escaped; |
| if (escaped) { |
| path.append(c); |
| } |
| } else if (c == ':') { |
| if (!escaped) { |
| hadNonPathEscape = true; |
| if (errorStart < 0) { |
| errorStart = i; |
| } |
| errorEnd = i; |
| } else { |
| escaped = false; |
| } |
| path.append(c); |
| } else { |
| if (escaped) { |
| hadNonPathEscape = true; |
| if (errorStart < 0) { |
| errorStart = i; |
| } |
| errorEnd = i; |
| } |
| escaped = false; |
| path.append(c); |
| } |
| } |
| String pathString = path.toString(); |
| String key = line.substring(0, valueStart); |
| if (hadNonPathEscape && key.endsWith(".dir=") || new File(pathString).exists()) { |
| String escapedPath = suggestEscapes(line.substring(valueStart, line.length())); |
| |
| // NOTE: Keep in sync with {@link #getSuggestedEscape} below |
| String message = "Windows file separators (`\\`) and drive letter " |
| + "separators (':') must be escaped (`\\\\`) in property files; use " |
| + escapedPath; |
| int startOffset = offset + errorStart; |
| int endOffset = offset + errorEnd + 1; |
| Location location = Location.create(context.file, contents, startOffset, |
| endOffset); |
| context.report(ESCAPE, location, message); |
| } |
| } |
| |
| @NonNull |
| static String suggestEscapes(@NonNull String value) { |
| value = value.replace("\\:", ":").replace("\\\\", "\\"); |
| return LintUtils.escapePropertyValue(value); |
| } |
| |
| /** |
| * Returns the escaped string value suggested by the error message which should have |
| * been computed by this lint detector. |
| * |
| * @param message the error message created by this lint detector |
| * @param format the format of the error message |
| * @return the suggested escaped value |
| */ |
| @Nullable |
| public static String getSuggestedEscape(@NonNull String message, @NonNull TextFormat format) { |
| return LintUtils.findSubstring(format.toText(message), "; use ", null); |
| } |
| } |