/*
* 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.jmeter.protocol.http.modifier;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.jmeter.config.Argument;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.ConfigElement;
import org.apache.jmeter.processor.PreProcessor;
import org.apache.jmeter.protocol.http.parser.HtmlParsingUtils;
import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase;
import org.apache.jmeter.protocol.http.util.ConversionUtils;
import org.apache.jmeter.protocol.http.util.HTTPConstants;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.testelement.property.PropertyIterator;
import org.apache.jmeter.threads.JMeterContext;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
// For Unit tests, @see TestAnchorModifier
public class AnchorModifier extends AbstractTestElement implements PreProcessor, Serializable {
private static final Logger log = LoggerFactory.getLogger(AnchorModifier.class);
private static final long serialVersionUID = 240L;
public AnchorModifier() {
}
/**
* Modifies an Entry object based on HTML response text.
*/
@Override
public void process() {
JMeterContext context = getThreadContext();
Sampler sam = context.getCurrentSampler();
SampleResult res = context.getPreviousResult();
HTTPSamplerBase sampler;
HTTPSampleResult result;
if (!(sam instanceof HTTPSamplerBase) || !(res instanceof HTTPSampleResult)) {
log.info("Can't apply HTML Link Parser when the previous" + " sampler run is not an HTTP Request.");
return;
} else {
sampler = (HTTPSamplerBase) sam;
result = (HTTPSampleResult) res;
}
List<HTTPSamplerBase> potentialLinks = new ArrayList<>();
String responseText = result.getResponseDataAsString();
int index = responseText.indexOf('<'); // $NON-NLS-1$
if (index == -1) {
index = 0;
}
if (log.isDebugEnabled()) {
log.debug("Check for matches against: "+sampler.toString());
}
Document html = (Document) HtmlParsingUtils.getDOM(responseText.substring(index));
addAnchorUrls(html, result, sampler, potentialLinks);
addFormUrls(html, result, sampler, potentialLinks);
addFramesetUrls(html, result, sampler, potentialLinks);
if (!potentialLinks.isEmpty()) {
HTTPSamplerBase url = potentialLinks.get(ThreadLocalRandom.current().nextInt(potentialLinks.size()));
if (log.isDebugEnabled()) {
log.debug("Selected: "+url.toString());
}
sampler.setDomain(url.getDomain());
sampler.setPath(url.getPath());
if (url.getMethod().equals(HTTPConstants.POST)) {
for (JMeterProperty jMeterProperty : sampler.getArguments()) {
Argument arg = (Argument) jMeterProperty.getObjectValue();
modifyArgument(arg, url.getArguments());
}
} else {
sampler.setArguments(url.getArguments());
// config.parseArguments(url.getQueryString());
}
sampler.setProtocol(url.getProtocol());
} else {
log.debug("No matches found");
}
}
private void modifyArgument(Argument arg, Arguments args) {
if (log.isDebugEnabled()) {
log.debug("Modifying argument: " + arg);
}
List<Argument> possibleReplacements = new ArrayList<>();
PropertyIterator iter = args.iterator();
Argument replacementArg;
while (iter.hasNext()) {
replacementArg = (Argument) iter.next().getObjectValue();
try {
if (HtmlParsingUtils.isArgumentMatched(replacementArg, arg)) {
possibleReplacements.add(replacementArg);
}
} catch (Exception ex) {
log.error("Problem adding Argument", ex);
}
}
if (!possibleReplacements.isEmpty()) {
replacementArg = possibleReplacements.get(ThreadLocalRandom.current().nextInt(possibleReplacements.size()));
arg.setName(replacementArg.getName());
arg.setValue(replacementArg.getValue());
if (log.isDebugEnabled()) {
log.debug("Just set argument to values: " + arg.getName() + " = " + arg.getValue());
}
args.removeArgument(replacementArg);
}
}
public void addConfigElement(ConfigElement config) {
}
private void addFormUrls(Document html, HTTPSampleResult result, HTTPSamplerBase config,
List<HTTPSamplerBase> potentialLinks) {
NodeList rootList = html.getChildNodes();
List<HTTPSamplerBase> urls = new LinkedList<>();
for (int x = 0; x < rootList.getLength(); x++) {
urls.addAll(HtmlParsingUtils.createURLFromForm(rootList.item(x), result.getURL()));
}
for (HTTPSamplerBase newUrl : urls) {
newUrl.setMethod(HTTPConstants.POST);
if (log.isDebugEnabled()) {
log.debug("Potential Form match: " + newUrl.toString());
}
if (HtmlParsingUtils.isAnchorMatched(newUrl, config)) {
log.debug("Matched!");
potentialLinks.add(newUrl);
}
}
}
private void addAnchorUrls(Document html, HTTPSampleResult result, HTTPSamplerBase config,
List<HTTPSamplerBase> potentialLinks) {
String base = "";
NodeList baseList = html.getElementsByTagName("base"); // $NON-NLS-1$
if (baseList.getLength() > 0) {
base = baseList.item(0).getAttributes().getNamedItem("href").getNodeValue(); // $NON-NLS-1$
}
NodeList nodeList = html.getElementsByTagName("a"); // $NON-NLS-1$
for (int i = 0; i < nodeList.getLength(); i++) {
Node tempNode = nodeList.item(i);
NamedNodeMap nnm = tempNode.getAttributes();
Node namedItem = nnm.getNamedItem("href"); // $NON-NLS-1$
if (namedItem == null) {
continue;
}
String hrefStr = namedItem.getNodeValue();
if (hrefStr.startsWith("javascript:")) { // $NON-NLS-1$
continue; // No point trying these
}
try {
HTTPSamplerBase newUrl = HtmlParsingUtils.createUrlFromAnchor(hrefStr, ConversionUtils.makeRelativeURL(result.getURL(), base));
newUrl.setMethod(HTTPConstants.GET);
if (log.isDebugEnabled()) {
log.debug("Potential <a href> match: " + newUrl);
}
if (HtmlParsingUtils.isAnchorMatched(newUrl, config)) {
log.debug("Matched!");
potentialLinks.add(newUrl);
}
} catch (MalformedURLException e) {
log.warn("Bad URL "+e);
}
}
}
private void addFramesetUrls(Document html, HTTPSampleResult result,
HTTPSamplerBase config, List<HTTPSamplerBase> potentialLinks) {
String base = "";
NodeList baseList = html.getElementsByTagName("base"); // $NON-NLS-1$
if (baseList.getLength() > 0) {
base = baseList.item(0).getAttributes().getNamedItem("href") // $NON-NLS-1$
.getNodeValue();
}
NodeList nodeList = html.getElementsByTagName("frame"); // $NON-NLS-1$
for (int i = 0; i < nodeList.getLength(); i++) {
Node tempNode = nodeList.item(i);
NamedNodeMap nnm = tempNode.getAttributes();
Node namedItem = nnm.getNamedItem("src"); // $NON-NLS-1$
if (namedItem == null) {
continue;
}
String hrefStr = namedItem.getNodeValue();
try {
HTTPSamplerBase newUrl = HtmlParsingUtils.createUrlFromAnchor(
hrefStr, ConversionUtils.makeRelativeURL(result.getURL(), base));
newUrl.setMethod(HTTPConstants.GET);
if (log.isDebugEnabled()) {
log.debug("Potential <frame src> match: " + newUrl);
}
if (HtmlParsingUtils.isAnchorMatched(newUrl, config)) {
log.debug("Matched!");
potentialLinks.add(newUrl);
}
} catch (MalformedURLException e) {
log.warn("Bad URL "+e);
}
}
}
}