/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.wicket.resource; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.css.ICssCompressor; import org.apache.wicket.request.Url; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.resource.PackageResourceReference; import org.apache.wicket.util.image.ImageUtil; /** * This compressor is used to replace URLs within CSS files with URLs created from * PackageResourceReferences that belongs to their corresponding resources (e.g images).The scope of * the CSS file is used to create the PackageResourceReferences. The compress method is not * compressing any content, but replacing the URLs with Wicket representatives.<br> * <br> * Usage: * * <pre> * this.getResourceSettings().setCssCompressor(new CssUrlReplacer()); * </pre> * * @since 6.20.0 * @author Tobias Soloschenko */ public class CssUrlReplacer implements IScopeAwareTextResourceProcessor, ICssCompressor { // The pattern to find URLs in CSS resources private static final Pattern URL_PATTERN = Pattern .compile("url\\([ ]*['|\"]?([^ ]*?)['|\"]?[ ]*\\)"); /** * Used to be append to CSS URLs (background-image: url('Beer.gif?embedBase64');). The * CssUrlReplacer embeds the base64 content instead of using an URL. */ public static final String EMBED_BASE64 = "embedBase64"; private final Set<String> excludes = new LinkedHashSet<>(); /** * Creates a css url replacer */ public CssUrlReplacer() { } /** * Creates a css url replacer * * @param excludes * css file names to be excluded */ public CssUrlReplacer(Collection<String> excludes) { this.excludes.addAll(excludes); } /** * Replaces the URLs of CSS resources with Wicket representatives. */ @Override public String process(String input, Class<?> scope, String name) { // filter out the excluded css files for (String excludeName : excludes) { if(name.endsWith(excludeName)){ return input; } } RequestCycle cycle = RequestCycle.get(); Url cssUrl = Url.parse(name); Matcher matcher = URL_PATTERN.matcher(input); StringBuffer output = new StringBuffer(); while (matcher.find()) { Url imageCandidateUrl = Url.parse(matcher.group(1)); CharSequence processedUrl; boolean embedded = false; if (imageCandidateUrl.isFull()) { processedUrl = imageCandidateUrl.toString(Url.StringMode.FULL); } else if (imageCandidateUrl.isContextAbsolute()) { processedUrl = imageCandidateUrl.toString(); } else if (imageCandidateUrl.isDataUrl()) { embedded = true; processedUrl = imageCandidateUrl.toString(); } else { // relativize against the url for the containing CSS file Url cssUrlCopy = new Url(cssUrl); cssUrlCopy.resolveRelative(imageCandidateUrl); // if the image should be processed as URL or base64 embedded if (cssUrlCopy.getQueryString() != null && cssUrlCopy.getQueryString().contains(EMBED_BASE64)) { embedded = true; PackageResourceReference imageReference = new PackageResourceReference(scope, cssUrlCopy.toString().replace("?" + EMBED_BASE64, "")); try { processedUrl = ImageUtil.createBase64EncodedImage(imageReference, true); } catch (Exception e) { throw new WicketRuntimeException( "Error while embedding an image into the css: " + imageReference, e); } } else { PackageResourceReference imageReference = new PackageResourceReference(scope, cssUrlCopy.toString()); processedUrl = cycle.urlFor(imageReference, null); } } // embedded data urls don't need single quotes, but regular urls do: matcher.appendReplacement(output, embedded ? "url(" + processedUrl + ")" : "url('" + processedUrl + "')"); } matcher.appendTail(output); return output.toString(); } @Override public String compress(String original) { throw new UnsupportedOperationException( CssUrlReplacer.class.getSimpleName() + ".process() should be used instead!"); } /** * Gets excluded css file names * * @return a list with css file names to be excluded */ public Collection<String> getExcludes() { return excludes; } /** * Sets a list of css file names to be excluded * * @param excludes * a list with css file names to be excluded */ public void setExcludes(Collection<String> excludes) { this.excludes.clear(); this.excludes.addAll(excludes); } }