001/*
002    Licensed to the Apache Software Foundation (ASF) under one
003    or more contributor license agreements.  See the NOTICE file
004    distributed with this work for additional information
005    regarding copyright ownership.  The ASF licenses this file
006    to you under the Apache License, Version 2.0 (the
007    "License"); you may not use this file except in compliance
008    with the License.  You may obtain a copy of the License at
009
010       http://www.apache.org/licenses/LICENSE-2.0
011
012    Unless required by applicable law or agreed to in writing,
013    software distributed under the License is distributed on an
014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015    KIND, either express or implied.  See the License for the
016    specific language governing permissions and limitations
017    under the License.
018 */
019package org.apache.wiki.parser;
020
021import org.apache.log4j.Logger;
022import org.apache.oro.text.regex.Pattern;
023import org.apache.oro.text.regex.Perl5Matcher;
024import org.apache.wiki.api.core.Context;
025import org.apache.wiki.api.exceptions.ProviderException;
026
027import java.util.Arrays;
028import java.util.Comparator;
029import java.util.List;
030
031
032/**
033 * Link parsing operations.
034 *
035 * @since 2.10.3
036 */
037public class LinkParsingOperations {
038
039    private static final Logger log = Logger.getLogger( LinkParsingOperations.class );
040    private final Context wikiContext;
041
042    /**
043     *  This list contains all IANA registered URI protocol types as of September 2004 + a few well-known extra types.
044     *
045     *  JSPWiki recognises all of them as external links.
046     *
047     *  This array is sorted during class load, so you can just dump here whatever you want in whatever order you want.
048     */
049    static final String[] EXTERNAL_LINKS = {
050        "http:", "ftp:", "https:", "mailto:",
051        "news:", "file:", "rtsp:", "mms:", "ldap:",
052        "gopher:", "nntp:", "telnet:", "wais:",
053        "prospero:", "z39.50s", "z39.50r", "vemmi:",
054        "imap:", "nfs:", "acap:", "tip:", "pop:",
055        "dav:", "opaquelocktoken:", "sip:", "sips:",
056        "tel:", "fax:", "modem:", "soap.beep:", "soap.beeps",
057        "xmlrpc.beep", "xmlrpc.beeps", "urn:", "go:",
058        "h323:", "ipp:", "tftp:", "mupdate:", "pres:",
059        "im:", "mtqp", "smb:"
060    };
061
062    static {
063        Arrays.sort( EXTERNAL_LINKS );
064    }
065
066    public LinkParsingOperations( final Context wikiContext ) {
067        this.wikiContext = wikiContext;
068    }
069
070    /**
071     *  Returns true, if the link in question is an access rule.
072     *
073     * @param link The link text
074     * @return {@code true}, if this represents an access rule.
075     */
076    public boolean isAccessRule( final String link ) {
077        return link.startsWith("{ALLOW") || link.startsWith("{DENY");
078    }
079
080    /**
081     *  Returns true if the link is really command to insert a plugin.
082     *  <P>
083     *  Currently we just check if the link starts with "{INSERT", or just plain "{" but not "{$".
084     *
085     *  @param link Link text, i.e. the contents of text between [].
086     *  @return True, if this link seems to be a command to insert a plugin here.
087     */
088    public boolean isPluginLink( final String link ) {
089        return link.startsWith( "{INSERT" ) ||
090               ( link.startsWith( "{" ) && !link.startsWith( "{$" ) );
091    }
092
093    /**
094     * Returns true if the link is a metadata link.
095     *
096     * @param link The link text
097     * @return {@code true}, if this represents a metadata link.
098     */
099    public boolean isMetadata( final String link ) {
100        return link.startsWith( "{SET" );
101    }
102
103    /**
104     * Returns true if the link is really command to insert a variable.
105     * <P>
106     * Currently we just check if the link starts with "{$".
107     *
108     * @param link The link text
109     * @return {@code true}, if this represents a variable link.
110     */
111    public boolean isVariableLink( final String link ) {
112        return link.startsWith( "{$" );
113    }
114
115    /**
116     * Returns true, if this Link represents an InterWiki link (of the form wiki:page).
117     *
118     * @return {@code true}, if this Link represents an InterWiki link, {@code false} otherwise.
119     */
120    public boolean isInterWikiLink( final String page ) {
121        return interWikiLinkAt( page ) != -1;
122    }
123
124    /**
125     * Returns true, if this Link represents an InterWiki link (of the form wiki:page).
126     *
127     * @return {@code true}, if this Link represents an InterWiki link, {@code false} otherwise.
128     */
129    public int interWikiLinkAt( final String page ) {
130        return page.indexOf( ':' );
131    }
132
133    /**
134     * Figures out if a link is an off-site link.  This recognizes the most common protocols by checking how it starts.
135     *
136     * @param page The link to check.
137     * @return true, if this is a link outside of this wiki.
138     */
139    public boolean isExternalLink( final String page ) {
140        final int idx = Arrays.binarySearch( EXTERNAL_LINKS, page, new StartingComparator() );
141
142        // We need to check here once again; otherwise we might get a match for something like "h".
143        return idx >= 0 && page.startsWith( EXTERNAL_LINKS[ idx ] );
144    }
145
146    /**
147     *  Matches the given link to the list of image name patterns to determine whether it should be treated as an inline image or not.
148     */
149    public boolean isImageLink( String link, final boolean isImageInlining, final List< Pattern > inlineImagePatterns ) {
150        if( isImageInlining ) {
151            link = link.toLowerCase();
152            for( final Pattern p : inlineImagePatterns ) {
153                if( new Perl5Matcher().matches( link, p ) ) {
154                    return true;
155                }
156            }
157        }
158
159        return false;
160    }
161
162    /**
163     * Returns {@code true}, if the link name exists; otherwise it returns {@code false}.
164     *
165     * @param page link name
166     * @return {@code true}, if the link name exists; otherwise it returns {@code false}.
167     */
168    public boolean linkExists( final String page ) {
169        if( page == null || page.length() == 0 ) {
170            return false;
171        }
172        try {
173            return wikiContext.getEngine().getFinalPageName( page ) != null;
174        } catch( final ProviderException e ) {
175            log.warn( "TranslatorReader got a faulty page name [" + page + "]!", e );
176            return false;
177        }
178    }
179
180    /**
181     * Returns link name, if it exists; otherwise it returns {@code null}.
182     *
183     * @param page link name
184     * @return link name, if it exists; otherwise it returns {@code null}.
185     */
186    public String linkIfExists( final String page ) {
187        if( page == null || page.length() == 0 ) {
188            return null;
189        }
190        try {
191            return wikiContext.getEngine().getFinalPageName( page );
192        } catch( final ProviderException e ) {
193            log.warn( "TranslatorReader got a faulty page name [" + page + "]!", e );
194            return null;
195        }
196    }
197
198    /**
199     * Compares two Strings, and if one starts with the other, then returns 0. Otherwise just like the normal Comparator for strings.
200     */
201    private static class StartingComparator implements Comparator< String > {
202
203        /**
204         * {@inheritDoc}
205         *
206         * @see Comparator#compare(Object, Object)
207         */
208        @Override
209        public int compare( final String s1, final String s2 ) {
210            if( s1.length() > s2.length() ) {
211                if( s1.startsWith( s2 ) && s2.length() > 1 ) {
212                    return 0;
213                }
214            } else if( s2.startsWith( s1 ) && s1.length() > 1 ) {
215                return 0;
216            }
217
218            return s1.compareTo( s2 );
219        }
220
221    }
222
223}