Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RedirectManager: support combining query string in the target with query string in the request #3510

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ in https://github.com/Adobe-Consulting-Services/acs-aem-commons/releases.

The format is based on [Keep a Changelog](http://keepachangelog.com)

<!-- Keep this up to date! After a release, change the tag name to the latest release -->-
<!-- Keep this up to date! After a release, change the tag name to the latest release -->-

## Unreleased ([details][unreleased changes details])

### Changed

- #3494 - Remove offline instrumentation with Jacoco
- #3509 - Redirect Manager: support combining query string in the target with query string in the request

### Fixed

Expand All @@ -21,7 +22,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com)

## 6.9.10 - 2024-12-13

### Added
### Added
- #3484 - Redirect Manager: A servlet to export redirects to a TXT file to use with pipeline-free redirects
- #3480 - AEM Sites Copy Publish URLs

Expand Down
2 changes: 1 addition & 1 deletion all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<parent>
<groupId>com.adobe.acs</groupId>
<artifactId>acs-aem-commons</artifactId>
<version>6.10.1-SNAPSHOT</version>
<version>6.11.0-SNAPSHOT</version>
</parent>

<!-- ====================================================================== -->
Expand Down
2 changes: 1 addition & 1 deletion bundle-cloud/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<parent>
<groupId>com.adobe.acs</groupId>
<artifactId>acs-aem-commons</artifactId>
<version>6.10.1-SNAPSHOT</version>
<version>6.11.0-SNAPSHOT</version>
</parent>

<!-- ====================================================================== -->
Expand Down
2 changes: 1 addition & 1 deletion bundle-onprem/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<parent>
<groupId>com.adobe.acs</groupId>
<artifactId>acs-aem-commons</artifactId>
<version>6.10.1-SNAPSHOT</version>
<version>6.11.0-SNAPSHOT</version>
</parent>

<!-- ====================================================================== -->
Expand Down
2 changes: 1 addition & 1 deletion bundle/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<parent>
<groupId>com.adobe.acs</groupId>
<artifactId>acs-aem-commons</artifactId>
<version>6.10.1-SNAPSHOT</version>
<version>6.11.0-SNAPSHOT</version>
</parent>

<!-- ====================================================================== -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package com.adobe.acs.commons.redirects.filter;

import com.adobe.acs.commons.redirects.LocationHeaderAdjuster;
import com.adobe.acs.commons.redirects.models.HandleQueryString;
import com.adobe.acs.commons.redirects.models.RedirectConfiguration;
import com.adobe.acs.commons.redirects.models.RedirectMatch;
import com.adobe.acs.commons.redirects.models.RedirectRule;
Expand Down Expand Up @@ -149,7 +150,7 @@ public class RedirectFilter extends AnnotatedStandardMBean
@AttributeDefinition(name = "Request Paths", description = "List of paths for which redirection is allowed", type = AttributeType.STRING)
String[] paths() default {"/content"};

@AttributeDefinition(name = "Preserve Query String", description = "Preserve query string in redirects", type = AttributeType.BOOLEAN)
@AttributeDefinition(name = "Preserve Query String", description = "Preserve query string in redirects. Since v6.11 you can manage handling query string in Redirect Properties.", type = AttributeType.BOOLEAN)
boolean preserveQueryString() default true;

@AttributeDefinition(name = "Preserve Extension", description = "Whether to preserve extensions. "
Expand Down Expand Up @@ -184,7 +185,6 @@ public class RedirectFilter extends AnnotatedStandardMBean
private ServiceRegistration<?> listenerRegistration;
private boolean enabled;
private boolean mapUrls;
private boolean preserveQueryString;
private List<Header> onDeliveryHeaders = Collections.emptyList();
private Collection<String> methods = Arrays.asList("GET", "HEAD");
private Collection<String> exts = Collections.emptySet();
Expand Down Expand Up @@ -229,8 +229,7 @@ protected final void activate(Configuration config, BundleContext context) {
String value = kv.substring(idx + 1).trim();
onDeliveryHeaders.add(new BasicHeader(name, value));
}
preserveQueryString = config.preserveQueryString();
log.debug("exts: {}, paths: {}, rewriteUrls: {}",
log.debug("exts: {}, paths: {}, rewriteUrls: {}",
exts, paths, mapUrls);
executor = Executors.newSingleThreadExecutor();

Expand Down Expand Up @@ -415,7 +414,7 @@ String evaluate(RedirectMatch match, SlingHttpServletRequest slingRequest){
if (StringUtils.startsWith(location, "/") && !StringUtils.startsWith(location, "//")) {
String ext = pathInfo.getExtension();
if (ext != null && config.preserveExtension() && !location.endsWith(ext)) {
location += "." + ext;
location = preserveExtension(location, ext);
}
if (mapUrls()) {
location = mapUrl(location, slingRequest);
Expand All @@ -424,27 +423,104 @@ String evaluate(RedirectMatch match, SlingHttpServletRequest slingRequest){
location = urlAdjuster.adjust(slingRequest, location);
}
}
if (preserveQueryString) {
String queryString = slingRequest.getQueryString();
if (queryString != null) {
location = preserveQueryString(location, queryString);
}
HandleQueryString pqs = getPreserveQueryString(match.getRule());
String queryString = slingRequest.getQueryString();
if (pqs != HandleQueryString.IGNORE && queryString != null) {
location = preserveQueryString(location, queryString, pqs == HandleQueryString.COMBINE);
}
return location;
}

String preserveQueryString(String location, String queryString){
int idx = location.indexOf('?');
if (idx == -1) {
idx = location.indexOf('#');
HandleQueryString getPreserveQueryString(RedirectRule rule){
HandleQueryString mode;
if(rule.getPreserveQueryString() == null) {
mode = config.preserveQueryString() ? HandleQueryString.COMBINE : HandleQueryString.IGNORE;
} else {
mode = HandleQueryString.valueOf(rule.getPreserveQueryString());
}

return mode;
}

String preserveExtension(String location, String ext) {
int locationQueryIndex = location.indexOf('?');
String baseLocation;
String locationQuery;
if (locationQueryIndex != -1) {
baseLocation = location.substring(0, locationQueryIndex);
locationQuery = location.substring(locationQueryIndex + 1);
} else {
baseLocation = location;
locationQuery = null;
}
if (idx != -1) {
location = location.substring(0, idx);
StringBuilder finalUrl = new StringBuilder(baseLocation);
finalUrl.append('.').append(ext);
if(locationQuery != null){
finalUrl.append('?').append(locationQuery);
}
return finalUrl.toString();
}

location += "?" + queryString;
/**
* Handles query string preservation in redirects
* @param location The target location URL
* @param queryString The request's query string
* @param combine If true, combines query parameters from both sources; if false, request query string replaces target's query string
* @return The final URL with processed query string
*/
String preserveQueryString(String location, String queryString, boolean combine) {
// Split location into base URL and query string (if any)
String baseLocation;
String locationQuery;
int locationQueryIndex = location.indexOf('?');
int fragmentIndex = location.indexOf('#');
if (locationQueryIndex != -1) {
baseLocation = location.substring(0, locationQueryIndex);
locationQuery = location.substring(locationQueryIndex + 1, fragmentIndex == -1 ? location.length() : fragmentIndex);
} else {
baseLocation = location;
locationQuery = null;
}

return location;
// Remove any fragment, store it for later
String fragment = "";
if (fragmentIndex != -1) {
fragment = location.substring(fragmentIndex);
}

// Handle query parameters based on combine flag
StringBuilder finalQuery = new StringBuilder();
if (combine) {
// Add location query parameters first
if (locationQuery != null && !locationQuery.isEmpty()) {
finalQuery.append(locationQuery);
}

// Add request query parameters
if (queryString != null && !queryString.isEmpty()) {
if (finalQuery.length() > 0) {
finalQuery.append('&');
}
finalQuery.append(queryString);
}
} else {
// Replace with request query string if it exists
if (queryString != null && !queryString.isEmpty()) {
finalQuery.append(queryString);
} else if (locationQuery != null && !locationQuery.isEmpty()) {
// Keep location query if request query is empty
finalQuery.append(locationQuery);
}
}

// Build final URL
StringBuilder finalUrl = new StringBuilder(baseLocation);
if (finalQuery.length() > 0) {
finalUrl.append('?').append(finalQuery);
}
finalUrl.append(fragment);

return finalUrl.toString();
}

String mapUrl(String url, SlingHttpServletRequest slingRequest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public enum ExportColumn {
CREATED_BY("Created By", RedirectRule.CREATED_BY_PROPERTY_NAME, String.class, false),
MODIFIED("Modified", RedirectRule.MODIFIED_PROPERTY_NAME, Calendar.class, false),
MODIFIED_BY("Modified By", RedirectRule.MODIFIED_BY_PROPERTY_NAME, String.class, false),
CACHE_CONTROL("Cache-Control", RedirectRule.CACHE_CONTROL_HEADER_NAME, String.class, false);
CACHE_CONTROL("Cache-Control", RedirectRule.CACHE_CONTROL_HEADER_NAME, String.class, false),
PRESERVE_QUERY_STRING("Query String", RedirectRule.PRESERVE_QUERY_STRING, String.class, true);

private final String title;
private final String propertyName;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*-
* #%L
* ACS AEM Commons Bundle
* %%
* Copyright (C) 2013 - 2025 Adobe
* %%
* Licensed 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.
* #L%
*/
package com.adobe.acs.commons.redirects.models;

/**
* how to handle query strings in the redirect
*/
public enum HandleQueryString {
/**
* ignore, i.e. don't append query string from the request
*/
IGNORE,

/**
* Replace query string in the target with query string in the request
*/
REPLACE,

/**
* Combine query string in the target with query string in the request
*/
COMBINE
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public class RedirectRule {
public static final String CACHE_CONTROL_HEADER_NAME = "cacheControlHeader";
public static final String CASE_INSENSITIVE_PROPERTY_NAME = "caseInsensitive";
public static final String REDIRECT_RESOURCE_REQUEST_ATTRIBUTE = "redirectResource";
public static final String PRESERVE_QUERY_STRING = "preserveQueryString";

@ValueMapValue
private String source;
Expand Down Expand Up @@ -108,6 +109,9 @@ public class RedirectRule {
@ValueMapValue(name = CASE_INSENSITIVE_PROPERTY_NAME)
private boolean caseInsensitive;

@ValueMapValue(name = PRESERVE_QUERY_STRING)
private String preserveQueryString;

@Self
private Resource resource;

Expand Down Expand Up @@ -140,6 +144,7 @@ protected void init() {
created = resource.getValueMap().get(CREATED_PROPERTY_NAME, Calendar.class);
cacheControlHeader = resource.getValueMap().get(CACHE_CONTROL_HEADER_NAME, String.class);
caseInsensitive = resource.getValueMap().get(CASE_INSENSITIVE_PROPERTY_NAME, false);
preserveQueryString = resource.getValueMap().get(PRESERVE_QUERY_STRING, String.class);
}

if (StringUtils.isBlank(source) || StringUtils.isBlank(target) || statusCode == null) {
Expand Down Expand Up @@ -336,4 +341,9 @@ public boolean isPublished() {
&& ((modified != null && modified.after(lastReplicated)) || (created != null && created.after(lastReplicated)));
return isPublished && !modifiedAfterPublication;
}

public String getPreserveQueryString() {
return preserveQueryString;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@org.osgi.annotation.versioning.Version("6.1.0")
@org.osgi.annotation.versioning.Version("6.11.0")
package com.adobe.acs.commons.redirects.models;
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ static XSSFWorkbook export(Collection<RedirectRule> rules) {
headerRow.createCell(11).setCellValue(ExportColumn.MODIFIED.getTitle());
headerRow.createCell(12).setCellValue(ExportColumn.MODIFIED_BY.getTitle());
headerRow.createCell(13).setCellValue(ExportColumn.CACHE_CONTROL.getTitle());
headerRow.createCell(14).setCellValue(ExportColumn.PRESERVE_QUERY_STRING.getTitle());

// column width in POI is measured in 1/256th of the default character width
sheet.setColumnWidth(0, 256 * 50);
Expand All @@ -133,6 +134,7 @@ static XSSFWorkbook export(Collection<RedirectRule> rules) {
sheet.setColumnWidth(11, 256 * 12);
sheet.setColumnWidth(12, 256 * 30);
sheet.setColumnWidth(13, 256 * 30);
sheet.setColumnWidth(14, 256 * 12);

for (Cell cell : headerRow) {
cell.setCellStyle(headerStyle);
Expand Down Expand Up @@ -185,6 +187,9 @@ static XSSFWorkbook export(Collection<RedirectRule> rules) {

Cell cell11 = row.createCell(13);
cell11.setCellValue(rule.getCacheControlHeader());

Cell cell14 = row.createCell(14);
cell14.setCellValue(rule.getPreserveQueryString());
}
sheet.setAutoFilter(new CellRangeAddress(0, rownum - 1, 0, 13));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ public RedirectResourceBuilder setCaseInsensitive(boolean nc) {
return this;
}

public RedirectResourceBuilder setPreserveQueryString(String value) {
props.put(PRESERVE_QUERY_STRING, value);
return this;
}

public Resource build() throws PersistenceException {
ContentBuilder cb = context.create();
Resource configResource = ResourceUtil.getOrCreateResource(
Expand Down
Loading
Loading