From b80a1d004848ae90af77b8462ebcd5a73181bfbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Videb=C3=A6k=20Fredsgaard?= Date: Mon, 13 Apr 2026 10:38:36 +0200 Subject: [PATCH 1/2] feat: add User-Agent header to HTTP/HTTPS schema fetch requests Include a well-formed RFC 9110-compliant User-Agent on all direct xhr calls when fetching schemas over HTTP or HTTPS: yaml-language-server/ (RedHat) node/ () The server version is read from YAML_LANGUAGE_SERVER_VERSION, the Node.js version from process.versions.node, and the platform from process.platform. All three are guarded with typeof-process checks for browser/web worker safety, falling back to 'unknown' for the version and omitting the runtime and platform tokens entirely in non-Node environments. --- src/languageservice/services/schemaRequestHandler.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/languageservice/services/schemaRequestHandler.ts b/src/languageservice/services/schemaRequestHandler.ts index a13c22d77..44d822109 100644 --- a/src/languageservice/services/schemaRequestHandler.ts +++ b/src/languageservice/services/schemaRequestHandler.ts @@ -88,7 +88,13 @@ export const schemaRequestHandler = async ( } // Send the HTTP(S) schema content request and return the result - const headers = { 'Accept-Encoding': 'gzip, deflate' }; + const version = (typeof process !== 'undefined' && process.env.YAML_LANGUAGE_SERVER_VERSION) || 'unknown'; + const nodeVersion = typeof process !== 'undefined' && process.versions?.node ? ` node/${process.versions.node}` : ''; + const platform = typeof process !== 'undefined' && process.platform ? ` (${process.platform})` : ''; + const headers = { + 'Accept-Encoding': 'gzip, deflate', + 'User-Agent': `yaml-language-server/${version} (RedHat)${nodeVersion}${platform}`, + }; return xhr({ url: uri, followRedirects: 5, headers }).then( (response) => { return response.responseText; From 52b0650ca1121de748bc1e3e5e1975cbe1d46027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Videb=C3=A6k=20Fredsgaard?= Date: Mon, 13 Apr 2026 13:26:33 +0200 Subject: [PATCH 2/2] test: add unit tests for User-Agent header on HTTP(S) schema fetch Cover version from env var, 'unknown' fallback, http:// parity, Accept-Encoding preservation, successful response handling, and xhr error rejection. --- test/schemaRequestHandler.test.ts | 83 +++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/test/schemaRequestHandler.test.ts b/test/schemaRequestHandler.test.ts index e2ce07251..eb75c1d05 100644 --- a/test/schemaRequestHandler.test.ts +++ b/test/schemaRequestHandler.test.ts @@ -5,6 +5,8 @@ import { schemaRequestHandler } from '../src/languageservice/services/schemaRequestHandler'; import * as sinon from 'sinon'; +import * as request from 'request-light'; +import { XHRResponse } from 'request-light'; import { Connection } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; import * as chai from 'chai'; @@ -66,4 +68,85 @@ describe('Schema Request Handler Tests', () => { expect(result).to.be.equal('{some: "json"}'); }); }); + + describe('HTTP(S) schema requests', () => { + const sandbox = sinon.createSandbox(); + let xhrStub: sinon.SinonStub; + const connection = {} as Connection; + + beforeEach(() => { + xhrStub = sandbox.stub(request, 'xhr'); + xhrStub.resolves({ responseText: '{"$schema":"http://json-schema.org/draft-07/schema"}', status: 200 } as XHRResponse); + }); + + afterEach(() => { + sandbox.restore(); + delete process.env.YAML_LANGUAGE_SERVER_VERSION; + }); + + it('should send correct User-Agent with version, Node runtime and platform', async () => { + process.env.YAML_LANGUAGE_SERVER_VERSION = '1.0.0-test'; + await schemaRequestHandler(connection, 'https://example.com/schema.json', [], URI.parse(''), false, testFileSystem, false); + + expect(xhrStub).calledOnce; + const { headers } = xhrStub.firstCall.args[0]; + expect(headers['User-Agent']).to.equal( + `yaml-language-server/1.0.0-test (RedHat) node/${process.versions.node} (${process.platform})` + ); + }); + + it('should fall back to "unknown" version when YAML_LANGUAGE_SERVER_VERSION is not set', async () => { + delete process.env.YAML_LANGUAGE_SERVER_VERSION; + await schemaRequestHandler(connection, 'https://example.com/schema.json', [], URI.parse(''), false, testFileSystem, false); + + const { headers } = xhrStub.firstCall.args[0]; + expect(headers['User-Agent']).to.match(/^yaml-language-server\/unknown \(RedHat\)/); + }); + + it('should send User-Agent on http:// URIs as well as https://', async () => { + process.env.YAML_LANGUAGE_SERVER_VERSION = '2.0.0'; + await schemaRequestHandler(connection, 'http://example.com/schema.json', [], URI.parse(''), false, testFileSystem, false); + + const { headers } = xhrStub.firstCall.args[0]; + expect(headers['User-Agent']).to.match(/^yaml-language-server\/2\.0\.0 \(RedHat\)/); + }); + + it('should preserve Accept-Encoding header alongside User-Agent', async () => { + await schemaRequestHandler(connection, 'https://example.com/schema.json', [], URI.parse(''), false, testFileSystem, false); + + const { headers } = xhrStub.firstCall.args[0]; + expect(headers['Accept-Encoding']).to.equal('gzip, deflate'); + }); + + it('should return the response text on success', async () => { + const result = await schemaRequestHandler( + connection, + 'https://example.com/schema.json', + [], + URI.parse(''), + false, + testFileSystem, + false + ); + expect(result).to.equal('{"$schema":"http://json-schema.org/draft-07/schema"}'); + }); + + it('should reject with responseText on xhr error', async () => { + xhrStub.rejects({ responseText: 'Not Found', status: 404 } as XHRResponse); + try { + await schemaRequestHandler( + connection, + 'https://example.com/schema.json', + [], + URI.parse(''), + false, + testFileSystem, + false + ); + expect.fail('Expected promise to be rejected'); + } catch (err) { + expect(err).to.equal('Not Found'); + } + }); + }); });