diff --git a/app/lib/frontend/handlers/cache_control.dart b/app/lib/frontend/handlers/cache_control.dart index af167d4770..e94c2240ac 100644 --- a/app/lib/frontend/handlers/cache_control.dart +++ b/app/lib/frontend/handlers/cache_control.dart @@ -55,6 +55,13 @@ final class CacheControl { public: true, ); + /// `Cache-Control` headers for API end-points returning slowly changing content, + /// without any hash in the URL. + static const mostlyStaticApi = CacheControl( + maxAge: Duration(hours: 8), + public: true, + ); + /// `Cache-Control` headers for API end-points returning completion data for /// use in IDE integrations. static const completionData = CacheControl( diff --git a/app/lib/frontend/handlers/misc.dart b/app/lib/frontend/handlers/misc.dart index aa9eb4136f..3915225aaa 100644 --- a/app/lib/frontend/handlers/misc.dart +++ b/app/lib/frontend/handlers/misc.dart @@ -31,6 +31,21 @@ import 'cache_control.dart'; final _log = Logger('pub.handlers.misc'); +/// Handles requests for /.well-known/security.txt +Future wellKnownSecurityTxtHandler( + shelf.Request request) async { + final expiresDate = + clock.now().add(Duration(days: 31)).toIso8601String().split('T').first; + final content = 'Contact: https://goo.gl/vulnz\n' + 'Policy: https://pub.dev/security\n' + 'Preferred-Languages: en\n' + 'Expires: ${expiresDate}T00:00:00z\n'; + return shelf.Response.ok( + content, + headers: CacheControl.mostlyStaticApi.headers, + ); +} + /// Handles requests for /help /// Handles requests for /help/
Future helpPageHandler( diff --git a/app/lib/frontend/handlers/routes.dart b/app/lib/frontend/handlers/routes.dart index f619128f6e..3de560db46 100644 --- a/app/lib/frontend/handlers/routes.dart +++ b/app/lib/frontend/handlers/routes.dart @@ -309,6 +309,11 @@ class PubSiteService { Future experimental(Request request) => experimentalHandler(request); + /// Renders the /.well-known/security.txt page + @Route.get('/.well-known/security.txt') + Future wellKnownSecurityTxt(Request request) => + wellKnownSecurityTxtHandler(request); + // **** // **** Account, authentication and user administration // **** diff --git a/app/lib/frontend/handlers/routes.g.dart b/app/lib/frontend/handlers/routes.g.dart index 5fd492e183..3a8cf0fefc 100644 --- a/app/lib/frontend/handlers/routes.g.dart +++ b/app/lib/frontend/handlers/routes.g.dart @@ -293,6 +293,11 @@ Router _$PubSiteServiceRouter(PubSiteService service) { r'/experimental', service.experimental, ); + router.add( + 'GET', + r'/.well-known/security.txt', + service.wellKnownSecurityTxt, + ); router.add( 'GET', r'/sign-in', diff --git a/pkg/pub_integration/lib/script/public_pages.dart b/pkg/pub_integration/lib/script/public_pages.dart index 9d0f3796bc..2700f92215 100644 --- a/pkg/pub_integration/lib/script/public_pages.dart +++ b/pkg/pub_integration/lib/script/public_pages.dart @@ -28,6 +28,7 @@ class PublicPagesScript { await _atomFeed(); await _searchPage(); await _sitemaps(); + await _wellKnownFiles(); await _customApis(); await _badRequest(); } finally { @@ -81,6 +82,10 @@ class PublicPagesScript { await _pubClient.getContent('/sitemap-2.txt'); } + Future _wellKnownFiles() async { + await _pubClient.getContent('/.well-known/security.txt'); + } + Future _customApis() async { final packageNames = await _pubClient.apiPackageNames(); if (!packageNames.contains('retry')) {