blob: f18f9bd411dcbccf89e9e2b04d2f7c8f7d546527 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:html/dom.dart';
import 'package:html/parser.dart' show parseFragment;
import 'package:source_span/source_span.dart';
import 'manifest_values.dart';
import 'manifest_warning_code.dart';
class ManifestValidator {
/**
* The source representing the file being validated.
*/
final Source source;
/**
* Initialize a newly create validator to validate the content of the given
* [source].
*/
ManifestValidator(this.source);
/*
* Validate the [contents] of the Android Manifest file.
*/
List<AnalysisError> validate(String contents, bool checkManifest) {
RecordingErrorListener recorder = new RecordingErrorListener();
ErrorReporter reporter = new ErrorReporter(recorder, source);
if (checkManifest) {
var document =
parseFragment(contents, container: MANIFEST_TAG, generateSpans: true);
var manifest = document.children.firstWhere(
(element) => element.localName == MANIFEST_TAG,
orElse: () => null);
var features = manifest?.getElementsByTagName(USES_FEATURE_TAG) ?? [];
var permissions =
manifest?.getElementsByTagName(USES_PERMISSION_TAG) ?? [];
_validateFeatures(features, reporter);
_validatePermissions(permissions, features, reporter);
}
return recorder.errors;
}
/*
* Validate the `uses-feature` tags.
*/
_validateFeatures(List<Element> features, ErrorReporter reporter) {
var unsupported = features
.where((element) => UNSUPPORTED_HARDWARE_FEATURES
.contains(element.attributes[ANDROID_NAME]))
.toList();
unsupported.forEach((element) {
if (!element.attributes.containsKey(ANDROID_REQUIRED)) {
_reportErrorForNode(reporter, element, ANDROID_NAME,
ManifestWarningCode.UNSUPPORTED_CHROME_OS_HARDWARE);
} else if (element.attributes[ANDROID_REQUIRED] == true) {
_reportErrorForNode(reporter, element, ANDROID_NAME,
ManifestWarningCode.UNSUPPORTED_CHROME_OS_HARDWARE);
}
});
}
/*
* Validate the `uses-permission` tags.
*/
_validatePermissions(List<Element> permissions, List<Element> features,
ErrorReporter reporter) {
permissions.forEach((permission) {
if (permission.attributes[ANDROID_NAME] == ANDROID_PERMISSION_CAMERA) {
if (!_hasFeatureCamera(features) ||
!_hasFeatureCameraAutoFocus(features)) {
_reportErrorForNode(reporter, permission, ANDROID_NAME,
ManifestWarningCode.CAMERA_PERMISSIONS_INCOMPATIBLE);
}
} else {
var featureName =
getImpliedUnsupportedHardware(permission.attributes[ANDROID_NAME]);
if (featureName != null) {
_reportErrorForNode(
reporter,
permission,
ANDROID_NAME,
ManifestWarningCode.PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE,
[featureName]);
}
}
});
}
bool _hasFeatureCamera(List<Element> features) =>
features.any((f) => f.localName == HARDWARE_FEATURE_CAMERA);
bool _hasFeatureCameraAutoFocus(List<Element> features) =>
features.any((f) => f.localName == HARDWARE_FEATURE_CAMERA_AUTOFOCUS);
/**
* Report an error for the given node.
*/
void _reportErrorForNode(
ErrorReporter reporter, Node node, dynamic key, ErrorCode errorCode,
[List<Object> arguments]) {
FileSpan span = node.attributeValueSpans[key];
reporter.reportErrorForOffset(
errorCode, span.start.offset, span.length, arguments);
}
}