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