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}