1+ // Licensed to the Apache Software Foundation (ASF) under one
2+ // or more contributor license agreements. See the NOTICE file
3+ // distributed with this work for additional information
4+ // regarding copyright ownership. The ASF licenses this file
5+ // to you under the Apache License, Version 2.0 (the
6+ // "License"); you may not use this file except in compliance
7+ // with the License. You may obtain a copy of the License at
8+ //
9+ // http://www.apache.org/licenses/LICENSE-2.0
10+ //
11+ // Unless required by applicable law or agreed to in writing,
12+ // software distributed under the License is distributed on an
13+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+ // KIND, either express or implied. See the License for the
15+ // specific language governing permissions and limitations
16+ // under the License.
17+
18+ package org .apache .cloudstack .dns ;
19+
20+ import static org .apache .cloudstack .dns .DnsRecord .RecordType .A ;
21+ import static org .apache .cloudstack .dns .DnsRecord .RecordType .AAAA ;
22+ import static org .apache .cloudstack .dns .DnsRecord .RecordType .CNAME ;
23+ import static org .apache .cloudstack .dns .DnsRecord .RecordType .MX ;
24+ import static org .apache .cloudstack .dns .DnsRecord .RecordType .NS ;
25+ import static org .apache .cloudstack .dns .DnsRecord .RecordType .PTR ;
26+ import static org .apache .cloudstack .dns .DnsRecord .RecordType .SRV ;
27+ import static org .apache .cloudstack .dns .DnsRecord .RecordType .TXT ;
28+ import static org .junit .Assert .assertEquals ;
29+ import static org .junit .Assert .fail ;
30+
31+ import java .util .Arrays ;
32+ import java .util .Collection ;
33+
34+ import org .junit .Test ;
35+ import org .junit .runner .RunWith ;
36+ import org .junit .runners .Parameterized ;
37+
38+ @ RunWith (Parameterized .class )
39+ public class NormalizeDnsRecordValueTest {
40+
41+ private final String description ;
42+ private final String input ;
43+ private final DnsRecord .RecordType recordType ;
44+ private final String expected ;
45+ private final boolean expectException ;
46+
47+ public NormalizeDnsRecordValueTest (String description , String input ,
48+ DnsRecord .RecordType recordType ,
49+ String expected , boolean expectException ) {
50+ this .description = description ;
51+ this .input = input ;
52+ this .recordType = recordType ;
53+ this .expected = expected ;
54+ this .expectException = expectException ;
55+ }
56+
57+ @ Parameterized .Parameters (name = "{0}" )
58+ public static Collection <Object []> data () {
59+ return Arrays .asList (new Object [][] {
60+
61+ // ----------------------------------------------------------------
62+ // Guard: blank/null value — all record types should throw
63+ // ----------------------------------------------------------------
64+ {"null value, A record" , null , A , null , true },
65+ {"empty value, A record" , "" , A , null , true },
66+ {"blank value, A record" , " " , A , null , true },
67+
68+ {"null value, AAAA record" , null , AAAA , null , true },
69+ {"null value, CNAME record" , null , CNAME , null , true },
70+ {"null value, MX record" , null , MX , null , true },
71+ {"null value, TXT record" , null , TXT , null , true },
72+ {"null value, SRV record" , null , SRV , null , true },
73+ {"null value, NS record" , null , NS , null , true },
74+ {"null value, PTR record" , null , PTR , null , true },
75+
76+ // ----------------------------------------------------------------
77+ // A record
78+ // ----------------------------------------------------------------
79+ {"A: valid IPv4" , "93.184.216.34" , A , "93.184.216.34" , false },
80+ {"A: valid IPv4 with whitespace" , " 93.184.216.34 " , A , "93.184.216.34" , false },
81+ {"A: loopback" , "127.0.0.1" , A , "127.0.0.1" , false },
82+ {"A: all-zeros" , "0.0.0.0" , A , "0.0.0.0" , false },
83+ {"A: broadcast" , "255.255.255.255" , A , "255.255.255.255" , false },
84+ {"A: private 10.x" , "10.0.0.1" , A , "10.0.0.1" , false },
85+ {"A: private 192.168.x" , "192.168.1.1" , A , "192.168.1.1" , false },
86+
87+ {"A: IPv6 rejected" , "2001:db8::1" , A , null , true },
88+ {"A: domain rejected" , "example.com" , A , null , true },
89+ {"A: partial IP rejected" , "192.168.1" , A , null , true },
90+ {"A: trailing dot rejected" , "93.184.216.34." , A , null , true },
91+ {"A: octet out of range rejected" , "256.0.0.1" , A , null , true },
92+
93+ // ----------------------------------------------------------------
94+ // AAAA record
95+ // ----------------------------------------------------------------
96+ {"AAAA: full IPv6" , "2001:0db8:0000:0000:0000:0000:0000:0001" , AAAA ,
97+ "2001:0db8:0000:0000:0000:0000:0000:0001" , false },
98+
99+ {"AAAA: compressed IPv6" , "2001:db8::1" , AAAA , "2001:db8::1" , false },
100+ {"AAAA: loopback" , "::1" , AAAA , "::1" , false },
101+ {"AAAA: all zeros" , "::" , AAAA , "::" , false },
102+ {"AAAA: whitespace" , " 2001:db8::1 " , AAAA , "2001:db8::1" , false },
103+
104+ {"AAAA: IPv4 rejected" , "93.184.216.34" , AAAA , null , true },
105+ {"AAAA: domain rejected" , "example.com" , AAAA , null , true },
106+ {"AAAA: invalid hex rejected" , "2001:db8::xyz" , AAAA , null , true },
107+
108+ // ----------------------------------------------------------------
109+ // CNAME record
110+ // ----------------------------------------------------------------
111+ {"CNAME: basic" , "target.example.com" , CNAME , "target.example.com." , false },
112+ {"CNAME: uppercase" , "TARGET.EXAMPLE.COM" , CNAME , "target.example.com." , false },
113+ {"CNAME: trailing dot" , "target.example.com." , CNAME , "target.example.com." , false },
114+ {"CNAME: whitespace" , " target.example.com " , CNAME , "target.example.com." , false },
115+ {"CNAME: subdomain" , "sub.target.example.com" , CNAME , "sub.target.example.com." , false },
116+
117+ {"CNAME: IP rejected" , "192.168.1.1" , CNAME , null , true },
118+ {"CNAME: invalid label" , "-bad.example.com" , CNAME , null , true },
119+
120+ // ----------------------------------------------------------------
121+ // NS record
122+ // ----------------------------------------------------------------
123+ {"NS: basic" , "ns1.example.com" , NS , "ns1.example.com." , false },
124+ {"NS: uppercase" , "NS1.EXAMPLE.COM" , NS , "ns1.example.com." , false },
125+ {"NS: trailing dot" , "ns1.example.com." , NS , "ns1.example.com." , false },
126+ {"NS: subdomain" , "ns1.sub.example.com" , NS , "ns1.sub.example.com." , false },
127+
128+ {"NS: IP rejected" , "8.8.8.8" , NS , null , true },
129+ {"NS: invalid label" , "ns1-.example.com" , NS , null , true },
130+
131+ // ----------------------------------------------------------------
132+ // PTR record
133+ // ----------------------------------------------------------------
134+ {"PTR: basic" , "host.example.com" , PTR , "host.example.com." , false },
135+ {"PTR: in-addr.arpa" , "1.168.192.in-addr.arpa" , PTR , "1.168.192.in-addr.arpa." , false },
136+ {"PTR: uppercase" , "HOST.EXAMPLE.COM" , PTR , "host.example.com." , false },
137+ {"PTR: trailing dot" , "host.example.com." , PTR , "host.example.com." , false },
138+
139+ {"PTR: IP rejected" , "192.168.1.1" , PTR , null , true },
140+ {"PTR: invalid label" , "-host.example.com" , PTR , null , true },
141+
142+ // ----------------------------------------------------------------
143+ // MX record
144+ // ----------------------------------------------------------------
145+ {"MX: standard" , "10 mail.example.com" , MX , "10 mail.example.com." , false },
146+ {"MX: zero priority" , "0 mail.example.com" , MX , "0 mail.example.com." , false },
147+ {"MX: max priority" , "65535 mail.example.com" , MX , "65535 mail.example.com." , false },
148+ {"MX: uppercase" , "10 MAIL.EXAMPLE.COM" , MX , "10 mail.example.com." , false },
149+ {"MX: trailing dot" , "10 mail.example.com." , MX , "10 mail.example.com." , false },
150+ {"MX: extra whitespace" , "10 mail.example.com" , MX , "10 mail.example.com." , false },
151+
152+ {"MX: missing domain" , "10" , MX , null , true },
153+ {"MX: priority out of range" , "65536 mail.example.com" , MX , null , true },
154+ {"MX: non-numeric priority" , "abc mail.example.com" , MX , null , true },
155+ {"MX: IP rejected" , "10 192.168.1.1" , MX , null , true },
156+
157+ // ----------------------------------------------------------------
158+ // SRV record
159+ // ----------------------------------------------------------------
160+ {"SRV: standard" , "10 20 443 target.example.com" , SRV , "10 20 443 target.example.com." , false },
161+ {"SRV: zeros" , "0 0 1 target.example.com" , SRV , "0 0 1 target.example.com." , false },
162+ {"SRV: max values" , "65535 65535 65535 target.example.com" , SRV , "65535 65535 65535 target.example.com." , false },
163+ {"SRV: uppercase" , "10 20 443 TARGET.EXAMPLE.COM" , SRV , "10 20 443 target.example.com." , false },
164+ {"SRV: trailing dot" , "10 20 443 target.example.com." , SRV , "10 20 443 target.example.com." , false },
165+
166+ {"SRV: missing target" , "10 20 443" , SRV , null , true },
167+ {"SRV: port 0" , "10 20 0 target.example.com" , SRV , null , true },
168+ {"SRV: priority out of range" , "65536 20 443 target.example.com" , SRV , null , true },
169+ {"SRV: IP rejected" , "10 20 443 192.168.1.1" , SRV , null , true },
170+
171+ // ----------------------------------------------------------------
172+ // TXT record
173+ // ----------------------------------------------------------------
174+ {"TXT: trim" , " hello world " , TXT , "hello world" , false },
175+ {"TXT: already clean" , "v=spf1 include:example.com ~all" , TXT , "v=spf1 include:example.com ~all" , false },
176+ {"TXT: special chars" , "v=DKIM1; k=rsa; p=MIGf" , TXT , "v=DKIM1; k=rsa; p=MIGf" , false },
177+ {"TXT: unicode" , "héllo wörld" , TXT , "héllo wörld" , false },
178+ {"TXT: multiple spaces" , "key=value with spaces" , TXT , "key=value with spaces" , false },
179+ {"TXT: quoted" , "\" quoted value\" " , TXT , "\" quoted value\" " , false },
180+ {"TXT: blank" , " " , TXT , null , true },
181+ {"TXT: newline" , "\n " , TXT , null , true },
182+ });
183+ }
184+
185+ @ Test
186+ public void testNormalizeDnsRecordValue () {
187+ if (expectException ) {
188+ try {
189+ DnsProviderUtil .normalizeDnsRecordValue (input , recordType );
190+ fail ("Expected IllegalArgumentException for [" + description + "] input='" + input + "'" );
191+ } catch (IllegalArgumentException ignored ) {}
192+ } else {
193+ String result = DnsProviderUtil .normalizeDnsRecordValue (input , recordType );
194+ assertEquals (description , expected , result );
195+ }
196+ }
197+ }
0 commit comments