001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.parser.filter; 018 019import java.text.ParseException; 020import java.util.Iterator; 021 022import org.apache.wicket.Component; 023import org.apache.wicket.MarkupContainer; 024import org.apache.wicket.behavior.Behavior; 025import org.apache.wicket.markup.ComponentTag; 026import org.apache.wicket.markup.ComponentTag.IAutoComponentFactory; 027import org.apache.wicket.markup.Markup; 028import org.apache.wicket.markup.MarkupElement; 029import org.apache.wicket.markup.MarkupResourceStream; 030import org.apache.wicket.markup.MarkupStream; 031import org.apache.wicket.markup.WicketTag; 032import org.apache.wicket.markup.html.TransparentWebMarkupContainer; 033import org.apache.wicket.markup.parser.AbstractMarkupFilter; 034import org.apache.wicket.markup.resolver.IComponentResolver; 035import org.apache.wicket.request.UrlUtils; 036import org.apache.wicket.request.cycle.RequestCycle; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040/** 041 * The purpose of this filter is to make all "href", "src" and "background" attributes found in the 042 * markup which contain a relative URL like "myDir/myPage.gif" actually resolve in the output HTML, 043 * by prefixing them with with an appropriate path to make the link work properly, even if the 044 * current page is being displayed at a mounted URL or whatever. It is applied to all non wicket 045 * component tags, except for auto-linked tags. 046 * 047 * It achieves this by being both an IMarkupFilter and IComponentResolver, and works similarly to 048 * the <wicket:message> code. For each tag, we look to see if the path in "href", "src" and 049 * "background" attributes is relative. If it is, we assume it's relative to the context path and we 050 * should prefix it appropriately so that it resolves correctly for the current request, even if 051 * that's for something that's not at the context root. This is done for ServletWebRequests by 052 * prepending with "../" tokens, for example. 053 * 054 * 055 * @author Al Maw 056 */ 057public final class RelativePathPrefixHandler extends AbstractMarkupFilter 058 implements 059 IComponentResolver 060{ 061 private static final long serialVersionUID = 1L; 062 063 /** Logging */ 064 private static final Logger log = LoggerFactory.getLogger(RelativePathPrefixHandler.class); 065 066 /** 067 * The id automatically assigned to tags without an id which we need to prepend a relative path 068 * to. 069 */ 070 public static final String WICKET_RELATIVE_PATH_PREFIX_CONTAINER_ID = "_relative_path_prefix_"; 071 072 /** List of attribute names considered */ 073 private static final String attributeNames[] = new String[] { "href", "src", "background", 074 "action" }; 075 076 /** 077 * Behavior that adds a prefix to src, href and background attributes to make them 078 * context-relative 079 */ 080 public static final Behavior RELATIVE_PATH_BEHAVIOR = new Behavior() 081 { 082 private static final long serialVersionUID = 1L; 083 084 @Override 085 public void onComponentTag(Component component, ComponentTag tag) 086 { 087 // Modify all relevant attributes 088 for (String attrName : attributeNames) 089 { 090 String attrValue = tag.getAttributes().getString(attrName); 091 092 if ((attrValue != null) && (attrValue.startsWith("/") == false) 093 && (!attrValue.contains(":")) && !(attrValue.startsWith("#"))) 094 { 095 tag.getAttributes().put(attrName, 096 UrlUtils.rewriteToContextRelative(attrValue, RequestCycle.get())); 097 } 098 } 099 } 100 }; 101 102 private static final IAutoComponentFactory FACTORY = new IAutoComponentFactory() 103 { 104 @Override 105 public Component newComponent(MarkupContainer container, ComponentTag tag) 106 { 107 return new TransparentWebMarkupContainer(tag.getId()); 108 } 109 }; 110 111 /** 112 * Constructor for the IComponentResolver role. 113 */ 114 public RelativePathPrefixHandler() 115 { 116 this(null); 117 } 118 119 /** 120 * Constructor for the IMarkupFilter role 121 * 122 * @param markup 123 * The markup created by reading the markup file 124 */ 125 public RelativePathPrefixHandler(final MarkupResourceStream markup) 126 { 127 super(markup); 128 } 129 130 @Override 131 protected final MarkupElement onComponentTag(ComponentTag tag) throws ParseException 132 { 133 if (tag.isClose()) 134 { 135 return tag; 136 } 137 138 String wicketIdAttr = getWicketNamespace() + ":" + "id"; 139 140 // Don't touch any wicket:id component and any auto-components 141 if ((tag instanceof WicketTag) || (tag.isAutolinkEnabled() == true) 142 || (tag.getAttributes().get(wicketIdAttr) != null)) 143 { 144 return tag; 145 } 146 147 // Work out whether we have any attributes that require us to add a 148 // behavior that prepends the relative path. 149 for (String attrName : attributeNames) 150 { 151 String attrValue = tag.getAttributes().getString(attrName); 152 if ((attrValue != null) && (attrValue.startsWith("/") == false) 153 && (!attrValue.contains(":")) && !(attrValue.startsWith("#"))) 154 { 155 if (tag.getId() == null) 156 { 157 tag.setId(getWicketRelativePathPrefix(null) 158 + getRequestUniqueId()); 159 tag.setAutoComponentTag(true); 160 } 161 162 tag.addBehavior(RELATIVE_PATH_BEHAVIOR); 163 tag.setModified(true); 164 165 break; 166 } 167 } 168 169 return tag; 170 } 171 172 @Override 173 public Component resolve(final MarkupContainer container, final MarkupStream markupStream, 174 final ComponentTag tag) 175 { 176 if ((tag != null) && (tag.getId().startsWith(getWicketRelativePathPrefix(markupStream)))) 177 { 178 // we do not want to mess with the hierarchy, so the container has to be 179 // transparent as it may have wicket components inside. for example a raw anchor tag 180 // that contains a label. 181 return new TransparentWebMarkupContainer(tag.getId()); 182 } 183 return null; 184 } 185 186 @Override 187 public void postProcess(Markup markup) 188 { 189 /** 190 * https://issues.apache.org/jira/browse/WICKET-5724 191 * 192 * Transparent component inside page body must allow queued children components. 193 */ 194 Iterator<MarkupElement> markupIterator = markup.iterator(); 195 while (markupIterator.hasNext()) 196 { 197 MarkupElement next = markupIterator.next(); 198 199 if (next instanceof ComponentTag) 200 { 201 ComponentTag componentTag = (ComponentTag)next; 202 203 /** 204 * if component tag is for a transparent component and contains "wicket:id", must be 205 * queueable. 206 */ 207 if (componentTag.containsWicketId() 208 && componentTag.getId().startsWith(getWicketRelativePathPrefix(null))) 209 { 210 componentTag.setAutoComponentFactory(FACTORY); 211 } 212 } 213 } 214 } 215 216 private String getWicketRelativePathPrefix(final MarkupStream markupStream) 217 { 218 return getWicketNamespace(markupStream) + WICKET_RELATIVE_PATH_PREFIX_CONTAINER_ID; 219 } 220}