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