From b06e75eb45a5090abaf51a370ea05008885c6353 Mon Sep 17 00:00:00 2001 From: Remy Suen Date: Wed, 23 Jul 2025 13:39:52 -0400 Subject: [PATCH] Improve handling of malformed image attribute values in Compose Signed-off-by: Remy Suen --- CHANGELOG.md | 8 +++ internal/compose/documentLink.go | 40 ++++++----- internal/compose/documentLink_test.go | 98 ++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37c2d28..442d5fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to the Docker Language Server will be documented in this file. +## [Unreleased] + +### Fixed + +- Compose + - textDocument/documentLink + - improve handling of malformed image attribute values with registry prefixes ([#369](https://github.com/docker/docker-language-server/issues/369)) + ## [0.14.0] - 2025-07-16 ### Added diff --git a/internal/compose/documentLink.go b/internal/compose/documentLink.go index 0bb479c..f18039f 100644 --- a/internal/compose/documentLink.go +++ b/internal/compose/documentLink.go @@ -282,16 +282,24 @@ func DocumentLink(ctx context.Context, documentURI protocol.URI, doc document.Co return links, nil } +func extractNonDockerHubImageLink(nodeValue, prefix, uriPrefix string, startIndex uint) (string, string) { + if len(nodeValue) <= len(prefix)+1 { + return "", "" + } + idx := strings.LastIndex(nodeValue, ":") + lastSlashIdx := strings.LastIndex(nodeValue, "/") + if (idx != -1 && lastSlashIdx > idx) || strings.Index(nodeValue, "/") == lastSlashIdx { + return "", "" + } + if idx == -1 { + return nodeValue, fmt.Sprintf("%v%v", uriPrefix, nodeValue[startIndex:]) + } + return nodeValue[0:idx], fmt.Sprintf("%v%v", uriPrefix, nodeValue[startIndex:idx]) +} + func extractImageLink(nodeValue string) (string, string) { if strings.HasPrefix(nodeValue, "ghcr.io") { - if len(nodeValue) <= 8 { - return "", "" - } - idx := strings.LastIndex(nodeValue, ":") - if idx == -1 { - return nodeValue, fmt.Sprintf("https://%v", nodeValue) - } - return nodeValue[0:idx], fmt.Sprintf("https://%v", nodeValue[0:idx]) + return extractNonDockerHubImageLink(nodeValue, "ghcr.io", "https://", 0) } if strings.HasPrefix(nodeValue, "mcr.microsoft.com") { @@ -299,6 +307,13 @@ func extractImageLink(nodeValue string) (string, string) { return "", "" } idx := strings.LastIndex(nodeValue, ":") + if idx == 17 { + return "", "" + } + lastSlashIdx := strings.LastIndex(nodeValue, "/") + if lastSlashIdx == idx-1 || (idx != -1 && lastSlashIdx > idx) { + return "", "" + } if idx == -1 { return nodeValue, fmt.Sprintf("https://mcr.microsoft.com/artifact/mar/%v", nodeValue[18:]) } @@ -306,14 +321,7 @@ func extractImageLink(nodeValue string) (string, string) { } if strings.HasPrefix(nodeValue, "quay.io") { - if len(nodeValue) <= 8 { - return "", "" - } - idx := strings.LastIndex(nodeValue, ":") - if idx == -1 { - return nodeValue, fmt.Sprintf("https://quay.io/repository/%v", nodeValue[8:]) - } - return nodeValue[0:idx], fmt.Sprintf("https://quay.io/repository/%v", nodeValue[8:idx]) + return extractNonDockerHubImageLink(nodeValue, "quay.io", "https://quay.io/repository/", 8) } idx := strings.LastIndex(nodeValue, ":") diff --git a/internal/compose/documentLink_test.go b/internal/compose/documentLink_test.go index f691046..68629c9 100644 --- a/internal/compose/documentLink_test.go +++ b/internal/compose/documentLink_test.go @@ -517,12 +517,44 @@ services: image: ghcr.io:`, links: nil, }, + { + name: "image: ghcr.io:tag", + content: ` +services: + test: + image: ghcr.io:tag`, + links: []protocol.DocumentLink{}, + }, + { + name: "image: ghcr.io/:tag", + content: ` +services: + test: + image: ghcr.io/:tag`, + links: []protocol.DocumentLink{}, + }, + { + name: "image: ghcr.io/:tag", + content: ` +services: + test: + image: ghcr.io:tag/`, + links: []protocol.DocumentLink{}, + }, + { + name: "image: ghcr.io/:tag", + content: ` +services: + test: + image: ghcr.io/:tag/`, + links: []protocol.DocumentLink{}, + }, { name: "image: \"ghcr.io:\"", content: ` services: test: - image: "ghcr.io:"`, + image: "ghcr.io:tag"`, links: []protocol.DocumentLink{}, }, { @@ -617,6 +649,38 @@ services: image: mcr.microsoft.com:`, links: nil, }, + { + name: "image: mcr.microsoft.com:tag", + content: ` +services: + test: + image: mcr.microsoft.com:tag`, + links: []protocol.DocumentLink{}, + }, + { + name: "image: mcr.microsoft.com/:tag", + content: ` +services: + test: + image: mcr.microsoft.com/:tag`, + links: []protocol.DocumentLink{}, + }, + { + name: "image: mcr.microsoft.com:tag/", + content: ` +services: + test: + image: mcr.microsoft.com:tag/`, + links: []protocol.DocumentLink{}, + }, + { + name: "image: mcr.microsoft.com/:tag/", + content: ` +services: + test: + image: mcr.microsoft.com/:tag/`, + links: []protocol.DocumentLink{}, + }, { name: "image: \"mcr.microsoft.com:\"", content: ` @@ -683,6 +747,38 @@ services: image: quay.io:`, links: nil, }, + { + name: "image: quay.io:tag", + content: ` +services: + test: + image: quay.io:tag`, + links: []protocol.DocumentLink{}, + }, + { + name: "image: quay.io/:tag", + content: ` +services: + test: + image: quay.io/:tag`, + links: []protocol.DocumentLink{}, + }, + { + name: "image: quay.io:tag/", + content: ` +services: + test: + image: quay.io:tag/`, + links: []protocol.DocumentLink{}, + }, + { + name: "image: quay.io/:tag/", + content: ` +services: + test: + image: quay.io/:tag/`, + links: []protocol.DocumentLink{}, + }, { name: "image: \"quay.io:\"", content: `