001    /*******************************************************************************
002     * Portions created by Sebastian Thomschke are copyright (c) 2005-2015 Sebastian
003     * Thomschke.
004     *
005     * All Rights Reserved. This program and the accompanying materials
006     * are made available under the terms of the Eclipse Public License v1.0
007     * which accompanies this distribution, and is available at
008     * http://www.eclipse.org/legal/epl-v10.html
009     *
010     * Contributors:
011     *     Sebastian Thomschke - initial implementation.
012     *     Makkari - live connect support.
013     *******************************************************************************/
014    package net.sf.oval.constraint;
015    
016    import static net.sf.oval.Validator.*;
017    
018    import java.io.IOException;
019    import java.net.HttpURLConnection;
020    import java.net.URI;
021    import java.net.URL;
022    import java.net.URLConnection;
023    import java.util.List;
024    
025    import net.sf.oval.ConstraintTarget;
026    import net.sf.oval.Validator;
027    import net.sf.oval.configuration.annotation.AbstractAnnotationCheck;
028    import net.sf.oval.context.OValContext;
029    import net.sf.oval.internal.Log;
030    import net.sf.oval.internal.util.ArrayUtils;
031    
032    /**
033     * @author Sebastian Thomschke
034     */
035    public class AssertURLCheck extends AbstractAnnotationCheck<AssertURL>
036    {
037            /**
038             * http://en.wikipedia.org/wiki/URI_scheme
039             *
040             * @author Sebastian Thomschke
041             *
042             */
043            public static enum URIScheme
044            {
045                    FTP("ftp"),
046                    HTTP("http"),
047                    HTTPS("https");
048    
049                    private final String scheme;
050    
051                    private URIScheme(final String scheme)
052                    {
053                            this.scheme = scheme;
054                    }
055    
056                    /**
057                     * @return the scheme
058                     */
059                    public String getScheme()
060                    {
061                            return scheme;
062                    }
063    
064                    @Override
065                    public String toString()
066                    {
067                            return scheme;
068                    }
069            }
070    
071            private static final long serialVersionUID = 1L;
072    
073            private static final Log LOG = Log.getLog(AssertURLCheck.class);
074    
075            private static boolean canConnect(final String url)
076            {
077                    try
078                    {
079                            final URL theURL = new URL(url);
080                            final URLConnection conn = theURL.openConnection();
081                            conn.connect();
082                            conn.getInputStream().close();
083                            if (conn instanceof HttpURLConnection)
084                            {
085                                    final HttpURLConnection httpConnection = (HttpURLConnection) conn;
086                                    final int rc = httpConnection.getResponseCode();
087    
088                                    if (rc < HttpURLConnection.HTTP_BAD_REQUEST) return true;
089                                    LOG.debug("Connecting failed with HTTP response code " + rc);
090                                    return false;
091                            }
092                            return true;
093                    }
094                    catch (final IOException ex)
095                    {
096                            LOG.debug("Connecting failed with exception", ex);
097                            return false;
098                    }
099            }
100    
101            /**
102             * Specifies if a connection to the URL should be attempted to verify its validity.
103             */
104            private boolean connect = false;
105    
106            /**
107             * Specifies the allowed URL schemes.
108             */
109            private final List<URIScheme> permittedURISchemes = getCollectionFactory().createList(2);
110    
111            /**
112             * {@inheritDoc}
113             */
114            @Override
115            public void configure(final AssertURL constraintAnnotation)
116            {
117                    super.configure(constraintAnnotation);
118                    setConnect(constraintAnnotation.connect());
119                    setPermittedURISchemes(constraintAnnotation.permittedURISchemes());
120            }
121    
122            /**
123             * {@inheritDoc}
124             */
125            @Override
126            protected ConstraintTarget[] getAppliesToDefault()
127            {
128                    return new ConstraintTarget[]{ConstraintTarget.VALUES};
129            }
130    
131            /**
132             * Gets the allowed URL schemes.
133             * @return the permittedURISchemes
134             */
135            public URIScheme[] getPermittedURISchemes()
136            {
137                    return permittedURISchemes.size() == 0 ? null : permittedURISchemes.toArray(new URIScheme[permittedURISchemes.size()]);
138            }
139    
140            /**
141             * Specifies if a connection to the URL should be attempted to verify its validity.
142             *
143             * @return the connect
144             */
145            public boolean isConnect()
146            {
147                    return connect;
148            }
149    
150            /**
151             * {@inheritDoc}
152             */
153            public boolean isSatisfied(final Object validatedObject, final Object valueToValidate, final OValContext context,
154                            final Validator validator)
155            {
156                    if (valueToValidate == null) return true;
157    
158                    final String uriString = valueToValidate.toString();
159    
160                    try
161                    {
162                            // By constructing a java.net.URI object, the string representing the URI will be parsed against RFC 2396.
163                            // In case of non compliance a java.net.URISyntaxException will be thrown
164                            final URI uri = new URI(uriString);
165    
166                            // Make sure that the URI contains: [scheme; scheme-specific-part]
167                            final String scheme = uri.getScheme();
168                            if (scheme == null || uri.getRawSchemeSpecificPart() == null)
169                            {
170                                    LOG.debug("URI scheme or scheme-specific-part not specified");
171                                    return false;
172                            }
173    
174                            // Check whether the URI scheme is supported
175                            if (!isURISchemeValid(scheme.toLowerCase(Validator.getLocaleProvider().getLocale()))) return false;
176    
177                            // If the connect flag is true then attempt to connect to the URL
178                            if (connect) return canConnect(uriString);
179                    }
180                    catch (final java.net.URISyntaxException ex)
181                    {
182                            LOG.debug("URI scheme or scheme-specific-part not specified", ex);
183                            return false;
184                    }
185    
186                    return true;
187            }
188    
189            private boolean isURISchemeValid(final String url)
190            {
191                    for (final URIScheme scheme : permittedURISchemes)
192                            if (url.startsWith(scheme.getScheme())) return true;
193                    return false;
194            }
195    
196            /**
197             * Specifies if a connection to the URL should be attempted to verify its validity.
198             *
199             * @param connect the connect to set
200             */
201            public void setConnect(final boolean connect)
202            {
203                    this.connect = connect;
204            }
205    
206            /**
207             * Specifies the allowed URL schemes.
208             *
209             * @param permittedURISchemes the permittedURISchemes to set
210             */
211            public void setPermittedURISchemes(final URIScheme[] permittedURISchemes)
212            {
213                    this.permittedURISchemes.clear();
214                    ArrayUtils.addAll(this.permittedURISchemes, permittedURISchemes);
215            }
216    }