| /* |
| * Copyright (C) 2013 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 com.android.tools.lint.client.api.UElementHandler |
| import com.android.tools.lint.detector.api.Category |
| import com.android.tools.lint.detector.api.Detector |
| import com.android.tools.lint.detector.api.Implementation |
| import com.android.tools.lint.detector.api.Incident |
| import com.android.tools.lint.detector.api.Issue |
| import com.android.tools.lint.detector.api.JavaContext |
| import com.android.tools.lint.detector.api.Scope |
| import com.android.tools.lint.detector.api.Severity |
| import com.android.tools.lint.detector.api.SourceCodeScanner |
| import com.android.tools.lint.detector.api.TypeEvaluator |
| import com.android.tools.lint.detector.api.isAndroidProject |
| import com.android.tools.lint.detector.api.targetSdkAtLeast |
| import com.intellij.psi.PsiClass |
| import com.intellij.psi.PsiClassType |
| import com.intellij.psi.PsiMethod |
| import com.intellij.psi.PsiModifier |
| import org.jetbrains.uast.UCallExpression |
| import org.jetbrains.uast.UDeclaration |
| import org.jetbrains.uast.UElement |
| |
| /** |
| * Looks for addJavascriptInterface calls on interfaces have been |
| * properly annotated with `@JavaScriptInterface` |
| */ |
| class JavaScriptInterfaceDetector : Detector(), SourceCodeScanner { |
| |
| // ---- implements SourceCodeScanner ---- |
| |
| override fun getApplicableMethodNames(): List<String> { |
| return listOf(ADD_JAVASCRIPT_INTERFACE) |
| } |
| |
| override fun visitMethodCall( |
| context: JavaContext, |
| node: UCallExpression, |
| method: PsiMethod |
| ) { |
| val arguments = node.valueArguments |
| if (arguments.size != 2) { |
| return |
| } |
| |
| val evaluator = context.evaluator |
| if (!evaluator.isMemberInClass(method, WEB_VIEW_CLS)) { |
| return |
| } |
| |
| val first = arguments[0] |
| val evaluated = TypeEvaluator.evaluate(first) |
| if (evaluated is PsiClassType) { |
| val cls = evaluated.resolve() ?: return |
| if (isJavaScriptAnnotated(cls)) { |
| return |
| } |
| |
| val location = context.getNameLocation(node) |
| val message = "None of the methods in the added interface (${cls.name}) have " + |
| "been annotated with `@android.webkit.JavascriptInterface`; they will not " + |
| "be visible in API 17" |
| val incident = Incident(ISSUE, node, location, message) |
| context.report(incident, targetSdkAtLeast(17)) |
| } |
| } |
| |
| override fun getApplicableUastTypes(): List<Class<out UElement>>? = |
| listOf(UDeclaration::class.java) |
| |
| override fun createUastHandler(context: JavaContext): UElementHandler? { |
| return object : UElementHandler() { |
| override fun visitDeclaration(node: UDeclaration) { |
| val modifierList = node.modifierList ?: return |
| if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) { |
| return |
| } |
| for (annotation in node.uAnnotations) { |
| if (annotation.qualifiedName == JAVASCRIPT_INTERFACE_CLS) { |
| val incident = Incident( |
| ISSUE, node as UElement, context.getNameLocation(node), |
| "Must be public when using `@JavascriptInterface`" |
| ) |
| context.report(incident, isAndroidProject()) |
| } |
| } |
| } |
| } |
| } |
| |
| companion object { |
| /** The main issue discovered by this detector. */ |
| @JvmField |
| val ISSUE = Issue.create( |
| id = "JavascriptInterface", |
| briefDescription = "Missing @JavascriptInterface on methods", |
| explanation = """ |
| As of API 17, you must annotate methods in objects registered with the \ |
| `addJavascriptInterface` method with a `@JavascriptInterface` annotation. |
| """, |
| category = Category.SECURITY, |
| moreInfo = "https://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String)", |
| androidSpecific = true, |
| priority = 8, |
| severity = Severity.ERROR, |
| implementation = Implementation( |
| JavaScriptInterfaceDetector::class.java, Scope.JAVA_FILE_SCOPE |
| ) |
| ) |
| |
| private const val ADD_JAVASCRIPT_INTERFACE = "addJavascriptInterface" |
| private const val JAVASCRIPT_INTERFACE_CLS = "android.webkit.JavascriptInterface" |
| private const val WEB_VIEW_CLS = "android.webkit.WebView" |
| |
| private fun isJavaScriptAnnotated(clz: PsiClass?): Boolean { |
| var current = clz |
| while (current != null) { |
| val modifierList = current.modifierList |
| if (modifierList?.findAnnotation(JAVASCRIPT_INTERFACE_CLS) != null) { |
| return true |
| } |
| |
| for (method in current.methods) { |
| if (method.modifierList.findAnnotation(JAVASCRIPT_INTERFACE_CLS) != null) { |
| return true |
| } |
| } |
| |
| current = current.superClass |
| } |
| |
| return false |
| } |
| } |
| } |