Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove fallback URL resolver. #8454

Merged
merged 1 commit into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 11 additions & 20 deletions app/lib/package/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:pana/models.dart';
import 'package:pub_dev/service/download_counts/backend.dart';
import 'package:pub_dev/service/download_counts/download_counts.dart';
import 'package:pub_dev/shared/markdown.dart';
import 'package:pub_semver/pub_semver.dart';

import '../package/model_properties.dart';
Expand Down Expand Up @@ -1071,11 +1070,7 @@ class PackageLinks {
/// The link to `CONTRIBUTING.md` in the git repository (when the repository is verified).
final String? contributingUrl;

/// The inferred base URL that can be used to link files from.
final String? _baseUrl;

PackageLinks._(
this._baseUrl, {
PackageLinks._({
this.homepageUrl,
String? documentationUrl,
this.repositoryUrl,
Expand All @@ -1093,12 +1088,7 @@ class PackageLinks {
}) {
repositoryUrl ??= urls.inferRepositoryUrl(homepageUrl);
issueTrackerUrl ??= urls.inferIssueTrackerUrl(repositoryUrl);
final baseUrl = urls.inferBaseUrl(
homepageUrl: homepageUrl,
repositoryUrl: repositoryUrl,
);
return PackageLinks._(
baseUrl,
homepageUrl: homepageUrl,
documentationUrl: documentationUrl,
repositoryUrl: repositoryUrl,
Expand Down Expand Up @@ -1156,12 +1146,7 @@ class PackagePageData {
return inferred;
}

final baseUrl = urls.inferBaseUrl(
homepageUrl: result.homepageUrl ?? inferred.homepageUrl,
repositoryUrl: result.repositoryUrl ?? inferred.repositoryUrl,
);
return PackageLinks._(
baseUrl,
homepageUrl: result.homepageUrl ?? inferred.homepageUrl,
repositoryUrl: result.repositoryUrl ?? inferred.repositoryUrl,
issueTrackerUrl: result.issueTrackerUrl ?? inferred.issueTrackerUrl,
Expand All @@ -1170,10 +1155,16 @@ class PackagePageData {
);
}();

/// The verified repository (or homepage).
late final urlResolverFn =
scoreCard.panaReport?.result?.repository?.resolveUrl ??
fallbackUrlResolverFn(packageLinks._baseUrl);
/// The URL resolver using a verified repository
/// (unless the verification explicitly failed).
late final urlResolverFn = () {
final result = scoreCard.panaReport?.result;
final status = result?.repositoryStatus;
if (status == RepositoryStatus.failed) {
return null;
}
return result?.repository?.resolveUrl;
}();

PackageView toPackageView() {
return _view ??= PackageView.fromModel(
Expand Down
90 changes: 1 addition & 89 deletions app/lib/shared/markdown.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@

import 'package:logging/logging.dart';
import 'package:markdown/markdown.dart' as m;
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'package:sanitize_html/sanitize_html.dart';

import 'urls.dart' show getRepositoryUrl, UriExt;
import 'urls.dart' show UriExt;

/// Resolves [reference] relative to a repository URL.
typedef UrlResolverFn = String Function(
Expand Down Expand Up @@ -313,67 +312,6 @@ class _TaskListRewriteNodeVisitor implements m.NodeVisitor {
void visitText(m.Text text) {}
}

bool _isAbsolute(String url) => url.contains(':');

String _rewriteAbsoluteUrl(String url) {
final uri = Uri.parse(url);
if (uri.host == 'github.com') {
final segments = uri.pathSegments;
if (segments.length > 3 && segments[2] == 'blob') {
final newSegments = List.of(segments);
newSegments[2] = 'raw';
return uri.replace(pathSegments: newSegments).toString();
}
}
return url;
}

String _rewriteRelativeUrl({
required String baseUrl,
required String url,
required String? baseDir,
}) {
final uri = Uri.parse(url);
final linkPath = uri.path;
final linkFragment = uri.fragment;
if (linkPath.isEmpty) {
return url;
}
String newUrl;
if (linkPath.startsWith('/')) {
newUrl = Uri.parse(baseUrl).replace(path: linkPath).toString();
} else {
final adjustedLinkPath = p.normalize(p.join(baseDir ?? '.', linkPath));
final repoUrl = getRepositoryUrl(baseUrl, adjustedLinkPath);
if (repoUrl == null) {
return url;
}
newUrl = repoUrl;
}
if (linkFragment.isNotEmpty) {
newUrl = '$newUrl#$linkFragment';
}
return newUrl;
}

/// Returns null if the [url] looks invalid.
String? _pruneBaseUrl(String? url) {
if (url == null) return null;
try {
final Uri uri = Uri.parse(url);
if (uri.scheme != 'http' && uri.scheme != 'https') {
return null;
}
if (uri.host.isEmpty || !uri.host.contains('.')) {
return null;
}
return uri.toString();
} catch (e) {
// url is user-provided, may be malicious, ignoring errors.
}
return null;
}

/// Group corresponding changelog nodes together, if it matches the following
/// pattern:
/// - version identifiers are the only content in a single line
Expand Down Expand Up @@ -447,29 +385,3 @@ Version? _extractVersion(String? text) {
return null;
}
}

// TODO: remove after repository verification is launched
UrlResolverFn? fallbackUrlResolverFn(String? providedBaseUrl) {
final baseUrl = _pruneBaseUrl(providedBaseUrl);
if (baseUrl == null) {
return null;
}
return (
String url, {
bool? isEmbeddedObject,
String? relativeFrom,
}) {
String newUrl = url;
if (!_isAbsolute(newUrl)) {
newUrl = _rewriteRelativeUrl(
url: newUrl,
baseUrl: baseUrl,
baseDir: relativeFrom == null ? null : p.dirname(relativeFrom),
);
}
if ((isEmbeddedObject ?? false) && _isAbsolute(newUrl)) {
newUrl = _rewriteAbsoluteUrl(newUrl);
}
return newUrl;
};
}
106 changes: 0 additions & 106 deletions app/lib/shared/urls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,6 @@ const httpsApiDartDev = 'https://api.dart.dev/';
/// rejected and the URL mustn't be displayed.
const trustedUrlSchemes = <String>['http', 'https', 'mailto'];

/// Extensions that are considered to be images.
final _imageExtensions = <String>{
'.apng',
'.avif',
'.gif',
'.jpg',
'.jpeg',
'.png',
'.svg',
'.webp',
};

/// Common repository URL replacement patterns.
const _repositoryReplacePrefixes = {
'http://github.com': 'https://github.com',
'https://www.github.com': 'https://github.com',
'https://www.gitlab.com': 'https://gitlab.com',
};

/// Hostnames that are trusted in user-generated content (and don't get rel="ugc").
const trustedTargetHost = [
'api.dart.dev',
Expand Down Expand Up @@ -294,42 +275,6 @@ String? inferIssueTrackerUrl(String? baseUrl) {
return null;
}

/// Infer base URL that can be used to link files from.
String? inferBaseUrl({String? homepageUrl, String? repositoryUrl}) {
var baseUrl = repositoryUrl ?? homepageUrl;
if (baseUrl == null) return null;

// In a few cases people specify only a deep repository URL for their
// package (e.g. a monorepo for multiple packages). While we default the
// base URL to be the repository URL, this check allows us to use the deep
// URL for linking.
if (homepageUrl != null && homepageUrl.startsWith(baseUrl)) {
baseUrl = homepageUrl;
}

// URL cleanup
final prefixReplacements = const <String, String>{
'http://github.com/': 'https://github.com/',
'http://www.github.com/': 'https://github.com/',
'https://www.github.com/': 'https://github.com/',
'http://gitlab.com/': 'https://gitlab.com/',
'http://www.gitlab.com/': 'https://gitlab.com/',
'https://www.gitlab.com/': 'https://gitlab.com/',
};
for (final prefix in prefixReplacements.keys) {
if (baseUrl!.startsWith(prefix)) {
baseUrl = baseUrl.replaceFirst(prefix, prefixReplacements[prefix]!);
}
}
if ((baseUrl!.startsWith('https://github.com/') ||
baseUrl.startsWith('https://gitlab.com/')) &&
baseUrl.endsWith('.git')) {
baseUrl = baseUrl.substring(0, baseUrl.length - 4);
}

return baseUrl;
}

/// Infer the hosting/service provider for a given URL.
String? inferServiceProviderName(String? url) {
if (url == null) {
Expand Down Expand Up @@ -383,57 +328,6 @@ String myPublishersUrl() => '/my-publishers';
String myActivityLogUrl() => '/my-activity-log';
String createPublisherUrl() => '/create-publisher';

/// Returns an URL that is likely the downloadable URL of the given path.
String? getRepositoryUrl(
String? repository,
String relativePath, {
String branch = 'master',
}) {
if (repository == null || repository.isEmpty) return null;
for (final key in _repositoryReplacePrefixes.keys) {
if (repository!.startsWith(key)) {
repository =
repository.replaceFirst(key, _repositoryReplacePrefixes[key]!);
}
}
try {
final uri = Uri.parse(repository!);
final segments = List.of(uri.pathSegments);
while (segments.isNotEmpty && segments.last.isEmpty) {
segments.removeLast();
}

if (repository.startsWith('https://github.com/') ||
repository.startsWith('https://gitlab.com/')) {
if (segments.length >= 2 &&
segments[1].endsWith('.git') &&
segments[1].length > 4) {
segments[1] = segments[1].substring(0, segments[1].length - 4);
}

final extension = p.extension(relativePath).toLowerCase();
final isRaw = _imageExtensions.contains(extension);
final typeSegment = isRaw ? 'raw' : 'blob';

if (segments.length < 2) {
return null;
} else if (segments.length == 2) {
final newUrl = uri.replace(pathSegments: segments).toString();
return p.url.join(newUrl, typeSegment, branch, relativePath);
} else if (segments[2] == 'tree' || segments[2] == 'blob') {
segments[2] = typeSegment;
final newUrl = uri.replace(pathSegments: segments).toString();
return p.url.join(newUrl, relativePath);
} else {
return null;
}
}
} catch (_) {
return null;
}
return null;
}

/// Parses [url] and returns the [Uri] object only if the result Uri is valid
/// (e.g. is relative or has recognized scheme).
Uri? parseValidUrl(String? url) {
Expand Down
Loading
Loading