3131use Symfony \Component \HttpFoundation \JsonResponse ;
3232use Symfony \Component \HttpFoundation \Request ;
3333use Symfony \Component \HttpFoundation \Response ;
34+ use Symfony \Component \HttpKernel \EventListener \AbstractSessionListener ;
3435use Symfony \Component \HttpKernel \Exception \BadRequestHttpException ;
3536use Symfony \Component \HttpKernel \Exception \NotFoundHttpException ;
3637use Symfony \Component \Routing \Attribute \Route ;
@@ -49,6 +50,8 @@ enum ApiType
4950 */
5051class ApiController extends Controller
5152{
53+ private const COMPOSER_VERSIONS_URL = 'https://getcomposer.org/versions ' ;
54+
5255 private const REGEXES = [
5356 'gitlab ' => '{^(?:ssh://git@|https?://|git://|git@)?(?P<host>[a-z0-9.-]+)(?::[0-9]+/|[:/])(?P<path>[\w.-]+(?:/[\w.-]+?)+)(?:\.git|/)?$}i ' ,
5457 'any ' => '{^(?:ssh://git@|https?://|git://|git@)?(?P<host>[a-z0-9.-]+)(?::[0-9]+/|[:/])(?P<path>[\w.-]+(?:/[\w.-]+?)*)(?:\.git|/)?$}i ' ,
@@ -82,6 +85,34 @@ public function packagesAction(string $webDir): Response
8285 return new Response ('Horrible misconfiguration or the dumper script messed up, you need to use bin/console packagist:dump-v2 ' , 404 );
8386 }
8487
88+ /**
89+ * Proxies https://getcomposer.org/versions so that systems which can only reach
90+ * packagist.org are still able to read Composer's version metadata.
91+ *
92+ * The response is cached at the CDN edge via s-maxage as this data changes rarely.
93+ */
94+ #[Route(path: '/api/composer-versions ' , name: 'composer_versions ' , defaults: ['_format ' => 'json ' ], methods: ['GET ' ])]
95+ public function composerVersionsAction (): Response
96+ {
97+ try {
98+ $ upstream = $ this ->httpClient ->request ('GET ' , self ::COMPOSER_VERSIONS_URL );
99+ if ($ upstream ->getStatusCode () !== 200 ) {
100+ throw new \RuntimeException ('Unexpected status code ' .$ upstream ->getStatusCode ());
101+ }
102+ $ contents = $ upstream ->getContent ();
103+ } catch (\Throwable $ e ) {
104+ $ this ->logger ->error ('Failed to proxy ' .self ::COMPOSER_VERSIONS_URL , ['exception ' => $ e ]);
105+
106+ return new JsonResponse (['status ' => 'error ' , 'message ' => 'Failed to fetch upstream version data, please try again later. ' ], 502 );
107+ }
108+
109+ $ response = new Response ($ contents , 200 , ['Content-Type ' => 'application/json ' ]);
110+ $ response ->setSharedMaxAge (900 );
111+ $ response ->headers ->set (AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER , 'true ' );
112+
113+ return $ response ;
114+ }
115+
85116 #[Route(path: '/api/create-package ' , name: 'generic_create ' , defaults: ['_format ' => 'json ' ], methods: ['POST ' ])]
86117 public function createPackageAction (Request $ request , ProviderManager $ providerManager , GitHubUserMigrationWorker $ githubUserMigrationWorker , RouterInterface $ router , ValidatorInterface $ validator ): JsonResponse
87118 {
0 commit comments