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}