1/*
2* Conditions Of Use
3*
4* This software was developed by employees of the National Institute of
5* Standards and Technology (NIST), an agency of the Federal Government.
6* Pursuant to title 15 Untied States Code Section 105, works of NIST
7* employees are not subject to copyright protection in the United States
8* and are considered to be in the public domain.  As a result, a formal
9* license is not needed to use the software.
10*
11* This software is provided by NIST as a service and is expressly
12* provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
13* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
14* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
15* AND DATA ACCURACY.  NIST does not warrant or make any representations
16* regarding the use of the software or the results thereof, including but
17* not limited to the correctness, accuracy, reliability or usefulness of
18* the software.
19*
20* Permission to use this software is contingent upon your acceptance
21* of the terms of this agreement
22*
23* .
24*
25*/
26/*
27 *
28 * IPv6 Support added by Emil Ivov (emil_ivov@yahoo.com)<br/>
29 * Network Research Team (http://www-r2.u-strasbg.fr))<br/>
30 * Louis Pasteur University - Strasbourg - France<br/>
31 *
32 *Bug fixes for corner cases were contributed by Thomas Froment.
33 */
34package gov.nist.core;
35
36// BEGIN android-deleted
37//import gov.nist.javax.sdp.parser.Lexer;
38// END android-deleted
39
40import java.text.ParseException;
41
42/**
43 * Parser for host names.
44 *
45 *@version 1.2
46 *
47 *@author M. Ranganathan
48 */
49
50public class HostNameParser extends ParserCore {
51// BEGIN android-added
52    private static LexerCore Lexer;
53// END android-added
54
55    /**
56     * Determines whether or not we should tolerate and strip address scope
57     * zones from IPv6 addresses. Address scope zones are sometimes returned
58     * at the end of IPv6 addresses generated by InetAddress.getHostAddress().
59     * They are however not part of the SIP semantics so basically this method
60     * determines whether or not the parser should be stripping them (as
61     * opposed simply being blunt and throwing an exception).
62     */
63    private boolean stripAddressScopeZones = false;
64
65    public HostNameParser(String hname) {
66        this.lexer = new LexerCore("charLexer", hname);
67
68        stripAddressScopeZones
69            = Boolean.getBoolean("gov.nist.core.STRIP_ADDR_SCOPES");
70    }
71
72    /**
73     * The lexer is initialized with the buffer.
74     */
75    public HostNameParser(LexerCore lexer) {
76        this.lexer = lexer;
77        lexer.selectLexer("charLexer");
78
79        stripAddressScopeZones
80            = Boolean.getBoolean("gov.nist.core.STRIP_ADDR_SCOPES");
81    }
82
83    private static final char[] VALID_DOMAIN_LABEL_CHAR =
84        new char[] {LexerCore.ALPHADIGIT_VALID_CHARS, '-', '.'};
85    protected void consumeDomainLabel() throws ParseException {
86        if (debug)
87            dbg_enter("domainLabel");
88        try {
89            lexer.consumeValidChars(VALID_DOMAIN_LABEL_CHAR);
90        } finally {
91            if (debug)
92                dbg_leave("domainLabel");
93        }
94    }
95
96    protected String ipv6Reference() throws ParseException {
97        StringBuffer retval = new StringBuffer();
98        if (debug)
99            dbg_enter("ipv6Reference");
100
101        try {
102
103            if(stripAddressScopeZones){
104                while (lexer.hasMoreChars()) {
105                    char la = lexer.lookAhead(0);
106                    //'%' is ipv6 address scope zone. see detail at
107                    //java.sun.com/j2se/1.5.0/docs/api/java/net/Inet6Address.html
108                    if (LexerCore.isHexDigit(la) || la == '.' || la == ':'
109                            || la == '[' ) {
110                        lexer.consume(1);
111                        retval.append(la);
112                    } else if (la == ']') {
113                        lexer.consume(1);
114                        retval.append(la);
115                        return retval.toString();
116                    } else if (la == '%'){
117                        //we need to strip the address scope zone.
118                        lexer.consume(1);
119
120                        String rest = lexer.getRest();
121
122                        if(rest == null || rest.length() == 0){
123                            //head for the parse exception
124                            break;
125                        }
126
127                        //we strip everything until either the end of the string
128                        //or a closing square bracket (])
129                        int stripLen = rest.indexOf(']');
130
131                        if (stripLen == -1){
132                            //no square bracket -> not a valid ipv6 reference
133                            break;
134                        }
135
136                        lexer.consume(stripLen+1);
137                        retval.append("]");
138                        return retval.toString();
139
140                    } else
141                        break;
142                }
143            }
144            else
145            {
146                while (lexer.hasMoreChars())
147                {
148                    char la = lexer.lookAhead(0);
149                    if (LexerCore.isHexDigit(la) || la == '.'
150                            || la == ':' || la == '[') {
151                        lexer.consume(1);
152                        retval.append(la);
153                    } else if (la == ']') {
154                        lexer.consume(1);
155                        retval.append(la);
156                        return retval.toString();
157                    } else
158                    break;
159                }
160            }
161
162            throw new ParseException(
163                lexer.getBuffer() + ": Illegal Host name ",
164                lexer.getPtr());
165        } finally {
166            if (debug)
167                dbg_leave("ipv6Reference");
168        }
169    }
170
171    public Host host() throws ParseException {
172        if (debug)
173            dbg_enter("host");
174        try {
175            String hostname;
176
177            //IPv6 referene
178            if (lexer.lookAhead(0) == '[') {
179                hostname = ipv6Reference();
180            }
181            //IPv6 address (i.e. missing square brackets)
182            else if( isIPv6Address(lexer.getRest()) )
183            {
184                int startPtr = lexer.getPtr();
185                lexer.consumeValidChars(
186                        new char[] {LexerCore.ALPHADIGIT_VALID_CHARS, ':'});
187                hostname
188                    = new StringBuffer("[").append(
189                        lexer.getBuffer().substring(startPtr, lexer.getPtr()))
190                        .append("]").toString();
191            }
192            //IPv4 address or hostname
193            else {
194                int startPtr = lexer.getPtr();
195                consumeDomainLabel();
196                hostname = lexer.getBuffer().substring(startPtr, lexer.getPtr());
197            }
198
199            if (hostname.length() == 0)
200                throw new ParseException(
201                    lexer.getBuffer() + ": Missing host name",
202                    lexer.getPtr());
203            else
204                return new Host(hostname);
205        } finally {
206            if (debug)
207                dbg_leave("host");
208        }
209    }
210
211    /**
212     * Tries to determine whether the address in <tt>uriHeader</tt> could be
213     * an IPv6 address by counting the number of colons that appear in it.
214     *
215     * @param uriHeader the string (supposedly the value of a URI header) that
216     * we have received for parsing.
217     *
218     * @return true if the host part of <tt>uriHeader</tt> could be an IPv6
219     * address (i.e. contains at least two colons) and false otherwise.
220     */
221    private boolean isIPv6Address(String uriHeader)
222    {
223        // approximately detect the end the host part.
224        //first check if we have an uri param
225        int hostEnd = uriHeader.indexOf(Lexer.QUESTION);
226
227        //if not or if it appears after a semi-colon then the end of the
228        //address would be a header param.
229        int semiColonIndex = uriHeader.indexOf(Lexer.SEMICOLON);
230        if ( hostEnd == -1
231            || (semiColonIndex!= -1 && hostEnd > semiColonIndex) )
232            hostEnd = semiColonIndex;
233
234        //if there was no header param either the address
235        //continues until the end of the string
236        if ( hostEnd == -1 )
237            hostEnd = uriHeader.length();
238
239        //extract the address
240        String host = uriHeader.substring(0, hostEnd);
241
242        int firstColonIndex = host.indexOf(Lexer.COLON);
243
244        if(firstColonIndex == -1)
245            return false;
246
247        int secondColonIndex = host.indexOf(Lexer.COLON, firstColonIndex + 1);
248
249        if(secondColonIndex == -1)
250            return false;
251
252        return true;
253    }
254    /**
255     * Parses a host:port string
256     *
257     * @param allowWS - whether whitespace is allowed around ':', only true for Via headers
258     * @return
259     * @throws ParseException
260     */
261    public HostPort hostPort( boolean allowWS ) throws ParseException {
262        if (debug)
263            dbg_enter("hostPort");
264        try {
265            Host host = this.host();
266            HostPort hp = new HostPort();
267            hp.setHost(host);
268            // Has a port?
269            if (allowWS) lexer.SPorHT(); // white space before ":port" should be accepted
270            if (lexer.hasMoreChars()) {
271                char la = lexer.lookAhead(0);
272                switch (la)
273                {
274                case ':':
275                    lexer.consume(1);
276                    if (allowWS) lexer.SPorHT(); // white space before port number should be accepted
277                    try {
278                        String port = lexer.number();
279                        hp.setPort(Integer.parseInt(port));
280                    } catch (NumberFormatException nfe) {
281                        throw new ParseException(
282                            lexer.getBuffer() + " :Error parsing port ",
283                            lexer.getPtr());
284                    }
285                    break;
286
287                case ',':	// allowed in case of multi-headers, e.g. Route
288                			// Could check that current header is a multi hdr
289
290                case ';':   // OK, can appear in URIs (parameters)
291                case '?':   // same, header parameters
292                case '>':   // OK, can appear in headers
293                case ' ':   // OK, allow whitespace
294                case '\t':
295                case '\r':
296                case '\n':
297                case '/':   // e.g. http://[::1]/xyz.html
298                    break;
299                case '%':
300                    if(stripAddressScopeZones){
301                        break;//OK,allow IPv6 address scope zone
302                    }
303
304                default:
305                    if (!allowWS) {
306                        throw new ParseException( lexer.getBuffer() +
307                                " Illegal character in hostname:" + lexer.lookAhead(0),
308                                lexer.getPtr() );
309                    }
310                }
311            }
312            return hp;
313        } finally {
314            if (debug)
315                dbg_leave("hostPort");
316        }
317    }
318
319    public static void main(String args[]) throws ParseException {
320        String hostNames[] =
321            {
322                "foo.bar.com:1234",
323                "proxima.chaplin.bt.co.uk",
324                "129.6.55.181:2345",
325                ":1234",
326                "foo.bar.com:         1234",
327                "foo.bar.com     :      1234   ",
328                "MIK_S:1234"
329            };
330
331        for (int i = 0; i < hostNames.length; i++) {
332            try {
333                HostNameParser hnp = new HostNameParser(hostNames[i]);
334                HostPort hp = hnp.hostPort(true);
335                System.out.println("["+hp.encode()+"]");
336            } catch (ParseException ex) {
337                System.out.println("exception text = " + ex.getMessage());
338            }
339        }
340
341    }
342}
343