From 9911b66aea076347cfb271b12c90bc31e4e5b5f8 Mon Sep 17 00:00:00 2001
From: Clar Fon <usr@ltdk.xyz>
Date: Mon, 7 Feb 2022 16:21:02 -0500
Subject: [PATCH] Be more lenient with label colors (#17752)

Accept 12-bit color specifications.
---
 models/issue_label.go       | 19 ++++++++++++++++++-
 models/issue_label_test.go  |  8 ++++++--
 services/forms/repo_form.go |  2 +-
 3 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/models/issue_label.go b/models/issue_label.go
index 53d28c0596..0aea620773 100644
--- a/models/issue_label.go
+++ b/models/issue_label.go
@@ -22,7 +22,7 @@ import (
 )
 
 // LabelColorPattern is a regexp witch can validate LabelColor
-var LabelColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")
+var LabelColorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
 
 // Label represents a label of repository for issues.
 type Label struct {
@@ -258,6 +258,23 @@ func NewLabel(label *Label) error {
 	if !LabelColorPattern.MatchString(label.Color) {
 		return fmt.Errorf("bad color code: %s", label.Color)
 	}
+
+	// normalize case
+	label.Color = strings.ToLower(label.Color)
+
+	// add leading hash
+	if label.Color[0] != '#' {
+		label.Color = "#" + label.Color
+	}
+
+	// convert 3-character shorthand into 6-character version
+	if len(label.Color) == 4 {
+		r := label.Color[1]
+		g := label.Color[2]
+		b := label.Color[3]
+		label.Color = fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b)
+	}
+
 	return newLabel(db.GetEngine(db.DefaultContext), label)
 }
 
diff --git a/models/issue_label_test.go b/models/issue_label_test.go
index 887f7f1425..68281dd7ad 100644
--- a/models/issue_label_test.go
+++ b/models/issue_label_test.go
@@ -38,11 +38,15 @@ func TestNewLabels(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	labels := []*Label{
 		{RepoID: 2, Name: "labelName2", Color: "#123456"},
-		{RepoID: 3, Name: "labelName3", Color: "#23456F"},
+		{RepoID: 3, Name: "labelName3", Color: "#123"},
+		{RepoID: 4, Name: "labelName4", Color: "ABCDEF"},
+		{RepoID: 5, Name: "labelName5", Color: "DEF"},
 	}
 	assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: ""}))
-	assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "123456"}))
+	assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "#45G"}))
 	assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "#12345G"}))
+	assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "45G"}))
+	assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "12345G"}))
 	for _, label := range labels {
 		unittest.AssertNotExistsBean(t, label)
 	}
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index e6bd088da4..b32bd3cafd 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -549,7 +549,7 @@ type CreateLabelForm struct {
 	ID          int64
 	Title       string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"`
 	Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"`
-	Color       string `binding:"Required;Size(7)" locale:"repo.issues.label_color"`
+	Color       string `binding:"Required;MaxSize(7)" locale:"repo.issues.label_color"`
 }
 
 // Validate validates the fields