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