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}