@@ -58,13 +58,6 @@ type Normalization struct {
5858 RemovedExtraTrailingDots bool
5959}
6060
61- func (normalization Normalization ) combine (other Normalization ) Normalization {
62- return Normalization {
63- RemovedLeadingDots : normalization .RemovedLeadingDots || other .RemovedLeadingDots ,
64- RemovedExtraTrailingDots : normalization .RemovedExtraTrailingDots || other .RemovedExtraTrailingDots ,
65- }
66- }
67-
6861// normalizeBoundary removes compatibility dots at a name's boundaries. A
6962// single final root dot is silent; two or more final dots are recorded. An
7063// all-dot spelling is root cleanup, not leading-dot cleanup.
@@ -95,6 +88,15 @@ func hasEmptyInteriorLabel(ascii string) bool {
9588 return strings .HasPrefix (ascii , "." ) || strings .Contains (ascii , ".." )
9689}
9790
91+ // wildcardSuffix recognizes both a bare wildcard and a wildcard with a suffix
92+ // after whole-input normalization has exposed its canonical dot separators.
93+ func wildcardSuffix (ascii string ) (string , bool ) {
94+ if ascii == "*" {
95+ return "" , true
96+ }
97+ return strings .CutPrefix (ascii , "*." )
98+ }
99+
98100// ErrTooFewLabels means a domain name has fewer than two labels after
99101// normalization — a single label (com, localhost), the empty/root name (.),
100102// or a bare "*". Such a name cannot be a reasonable target domain name.
@@ -131,11 +133,18 @@ func New(input string) (Domain, Normalization, error) {
131133 ascii , err := profileKeepingLeadingDots .ToASCII (input )
132134 normalized , normalization := normalizeBoundary (ascii )
133135
134- if suffix , ok := strings .CutPrefix (normalized , "*." ); ok {
135- return newWildcard (suffix , normalization , strings .HasPrefix (suffix , "." ))
136- }
137- if normalized == "*" {
138- return Wildcard ("" ), normalization , ErrTooFewLabels
136+ if suffix , ok := wildcardSuffix (normalized ); ok {
137+ wildcard , wildcardErr := validateNormalizedWildcardSuffix (suffix )
138+ if wildcardErr != nil {
139+ if errors .Is (wildcardErr , ErrEmptyInteriorLabel ) {
140+ return nil , Normalization {}, wildcardErr
141+ }
142+ return wildcard , Normalization {}, wildcardErr
143+ }
144+ if wildcard == "" {
145+ return wildcard , normalization , ErrTooFewLabels
146+ }
147+ return wildcard , normalization , nil
139148 }
140149 if strings .IndexByte (normalized , '.' ) == - 1 {
141150 return FQDN (normalized ), normalization , ErrTooFewLabels
@@ -156,28 +165,20 @@ func New(input string) (Domain, Normalization, error) {
156165 return FQDN (normalized ), normalization , nil
157166}
158167
159- func newWildcard (
160- suffix string , outerNormalization Normalization , includesWildcardMarker bool ,
161- ) (Domain , Normalization , error ) {
168+ // validateNormalizedWildcardSuffix expects a suffix cut from a whole input
169+ // after boundary normalization. It re-runs IDNA without the wildcard marker so
170+ // the marker's own error does not mask errors in the suffix. Target-specific
171+ // wildcard policy belongs to the caller, so an empty suffix is valid here.
172+ func validateNormalizedWildcardSuffix (suffix string ) (Wildcard , error ) {
162173 ascii , err := profileKeepingLeadingDots .ToASCII (suffix )
163- normalized , normalization := normalizeBoundary (ascii )
164- normalization = outerNormalization .combine (normalization )
165174 if err != nil {
166- return Wildcard (normalized ), Normalization {
167- RemovedLeadingDots : false ,
168- RemovedExtraTrailingDots : false ,
169- }, err
175+ normalized , _ := normalizeBoundary (ascii )
176+ return Wildcard (normalized ), err
170177 }
171178 if hasEmptyInteriorLabel (suffix ) {
172- return nil , Normalization {
173- RemovedLeadingDots : false ,
174- RemovedExtraTrailingDots : false ,
175- }, newEmptyInteriorLabelError (includesWildcardMarker )
176- }
177- if normalized == "" {
178- return Wildcard ("" ), normalization , ErrTooFewLabels
179+ return "" , newEmptyInteriorLabelError (strings .HasPrefix (suffix , "." ))
179180 }
180- return Wildcard (normalized ), normalization , nil
181+ return Wildcard (ascii ) , nil
181182}
182183
183184// CompareDomain compares two domains by their ASCII representations.
0 commit comments