com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequest Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of htmlunit Show documentation
Show all versions of htmlunit Show documentation
A headless browser intended for use in testing web-based applications.
/*
* Copyright (c) 2002-2022 Gargoyle Software Inc.
*
* 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
* https://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 com.gargoylesoftware.htmlunit.javascript.host.xml;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_ALL_RESPONSE_HEADERS_APPEND_SEPARATOR;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_ALL_RESPONSE_HEADERS_SEPARATE_BY_LF;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_FIRE_STATE_OPENED_AGAIN_IN_ASYNC_MODE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_HANDLE_SYNC_NETWORK_ERRORS;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_LENGTH_COMPUTABLE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_LOAD_ALWAYS_AFTER_DONE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_LOAD_START_ASYNC;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_NO_CROSS_ORIGIN_TO_ABOUT;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_OPEN_ALLOW_EMTPY_URL;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_PROGRESS_ON_NETWORK_ERROR_ASYNC;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_RESPONSE_TEXT_EMPTY_UNSENT;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_RESPONSE_TYPE_THROWS_UNSENT;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_SEND_NETWORK_ERROR_IF_ABORTED;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.XHR_USE_CONTENT_CHARSET;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF_ESR;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.TreeMap;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.NoHttpResponseException;
import org.apache.http.auth.UsernamePasswordCredentials;
import com.gargoylesoftware.htmlunit.AjaxController;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.FormEncodingType;
import com.gargoylesoftware.htmlunit.HttpHeader;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebRequest.HttpHint;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.WebWindow;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine;
import com.gargoylesoftware.htmlunit.javascript.background.BackgroundJavaScriptFactory;
import com.gargoylesoftware.htmlunit.javascript.background.JavaScriptJob;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstant;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
import com.gargoylesoftware.htmlunit.javascript.host.URLSearchParams;
import com.gargoylesoftware.htmlunit.javascript.host.Window;
import com.gargoylesoftware.htmlunit.javascript.host.dom.DOMParser;
import com.gargoylesoftware.htmlunit.javascript.host.event.Event;
import com.gargoylesoftware.htmlunit.javascript.host.event.ProgressEvent;
import com.gargoylesoftware.htmlunit.javascript.host.file.Blob;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLDocument;
import com.gargoylesoftware.htmlunit.util.EncodingSniffer;
import com.gargoylesoftware.htmlunit.util.MimeType;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import com.gargoylesoftware.htmlunit.util.UrlUtils;
import com.gargoylesoftware.htmlunit.util.WebResponseWrapper;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.ContextAction;
import net.sourceforge.htmlunit.corejs.javascript.ContextFactory;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
import net.sourceforge.htmlunit.corejs.javascript.Undefined;
import net.sourceforge.htmlunit.corejs.javascript.json.JsonParser;
import net.sourceforge.htmlunit.corejs.javascript.json.JsonParser.ParseException;
import net.sourceforge.htmlunit.corejs.javascript.typedarrays.NativeArrayBuffer;
import net.sourceforge.htmlunit.corejs.javascript.typedarrays.NativeArrayBufferView;
/**
* A JavaScript object for an {@code XMLHttpRequest}.
*
* @author Daniel Gredler
* @author Marc Guillemot
* @author Ahmed Ashour
* @author Stuart Begg
* @author Ronald Brill
* @author Sebastian Cato
* @author Frank Danek
* @author Jake Cobb
* @author Thorsten Wendelmuth
*
* @see W3C XMLHttpRequest
* @see Safari documentation
*/
@JsxClass
public class XMLHttpRequest extends XMLHttpRequestEventTarget {
private static final Log LOG = LogFactory.getLog(XMLHttpRequest.class);
/** The object has been created, but not initialized (the open() method has not been called). */
@JsxConstant
public static final int UNSENT = 0;
/** The object has been created, but the send() method has not been called. */
@JsxConstant
public static final int OPENED = 1;
/** The send() method has been called, but the status and headers are not yet available. */
@JsxConstant
public static final int HEADERS_RECEIVED = 2;
/** Some data has been received. */
@JsxConstant
public static final int LOADING = 3;
/** All the data has been received; the complete data is available in responseBody and responseText. */
@JsxConstant
public static final int DONE = 4;
private static final String RESPONSE_TYPE_DEFAULT = "";
private static final String RESPONSE_TYPE_ARRAYBUFFER = "arraybuffer";
private static final String RESPONSE_TYPE_BLOB = "blob";
private static final String RESPONSE_TYPE_DOCUMENT = "document";
private static final String RESPONSE_TYPE_JSON = "json";
private static final String RESPONSE_TYPE_TEXT = "text";
private static final String ALLOW_ORIGIN_ALL = "*";
private static final String[] ALL_PROPERTIES_ = {"onreadystatechange", "readyState", "responseText", "responseXML",
"status", "statusText", "abort", "getAllResponseHeaders", "getResponseHeader", "open", "send",
"setRequestHeader"};
private static final HashSet PROHIBITED_HEADERS_ = new HashSet<>(Arrays.asList(
"accept-charset", HttpHeader.ACCEPT_ENCODING_LC,
HttpHeader.CONNECTION_LC, HttpHeader.CONTENT_LENGTH_LC, HttpHeader.COOKIE_LC, "cookie2",
"content-transfer-encoding", "date", "expect",
HttpHeader.HOST_LC, "keep-alive", HttpHeader.REFERER_LC, "te", "trailer", "transfer-encoding",
"upgrade", HttpHeader.USER_AGENT_LC, "via"));
private int state_;
private WebRequest webRequest_;
private boolean async_;
private int jobID_;
private WebResponse webResponse_;
private String overriddenMimeType_;
private final boolean caseSensitiveProperties_;
private boolean withCredentials_;
private boolean isSameOrigin_;
private int timeout_;
private boolean aborted_;
private String responseType_;
/**
* Creates a new instance.
*/
@JsxConstructor
public XMLHttpRequest() {
this(true);
}
/**
* Creates a new instance.
* @param caseSensitiveProperties if properties and methods are case sensitive
*/
public XMLHttpRequest(final boolean caseSensitiveProperties) {
caseSensitiveProperties_ = caseSensitiveProperties;
state_ = UNSENT;
responseType_ = RESPONSE_TYPE_DEFAULT;
}
/**
* Sets the state as specified and invokes the state change handler if one has been set.
* @param state the new state
*/
private void setState(final int state) {
if (state == UNSENT
|| state == OPENED
|| state == HEADERS_RECEIVED
|| state == LOADING
|| state == DONE) {
state_ = state;
if (LOG.isDebugEnabled()) {
LOG.debug("State changed to : " + state);
}
return;
}
LOG.error("Received an unknown state " + state
+ ", the state is not implemented, please check setState() implementation.");
}
private void fireJavascriptEvent(final String eventName) {
if (aborted_) {
if (LOG.isDebugEnabled()) {
LOG.debug("Firing javascript XHR event: " + eventName + " for an already aborted request - ignored.");
}
return;
}
fireJavascriptEventIgnoreAbort(eventName);
}
private void fireJavascriptEventIgnoreAbort(final String eventName) {
if (LOG.isDebugEnabled()) {
LOG.debug("Firing javascript XHR event: " + eventName);
}
final boolean isReadyStateChange = Event.TYPE_READY_STATE_CHANGE.equalsIgnoreCase(eventName);
final Event event;
if (isReadyStateChange) {
event = new Event(this, Event.TYPE_READY_STATE_CHANGE);
}
else {
final ProgressEvent progressEvent = new ProgressEvent(this, eventName);
final boolean lengthComputable = getBrowserVersion().hasFeature(XHR_LENGTH_COMPUTABLE);
if (lengthComputable) {
progressEvent.setLengthComputable(true);
}
if (webResponse_ != null) {
final long contentLength = webResponse_.getContentLength();
progressEvent.setLoaded(contentLength);
if (lengthComputable) {
progressEvent.setTotal(contentLength);
}
}
event = progressEvent;
}
executeEventLocally(event);
}
/**
* Returns the current state of the HTTP request. The possible values are:
*
* - 0 = unsent
* - 1 = opened
* - 2 = headers_received
* - 3 = loading
* - 4 = done
*
* @return the current state of the HTTP request
*/
@JsxGetter
public int getReadyState() {
return state_;
}
/**
* @return the {@code responseType} property
*/
@JsxGetter
public String getResponseType() {
return responseType_;
}
/**
* Sets the {@code responseType} property.
* @param responseType the {@code responseType} property.
*/
@JsxSetter
public void setResponseType(final String responseType) {
if (state_ == LOADING || state_ == DONE) {
throw Context.reportRuntimeError("InvalidStateError");
}
if (state_ == UNSENT && getBrowserVersion().hasFeature(XHR_RESPONSE_TYPE_THROWS_UNSENT)) {
throw Context.reportRuntimeError("InvalidStateError");
}
if (RESPONSE_TYPE_DEFAULT.equals(responseType)
|| RESPONSE_TYPE_ARRAYBUFFER.equals(responseType)
|| RESPONSE_TYPE_BLOB.equals(responseType)
|| RESPONSE_TYPE_DOCUMENT.equals(responseType)
|| (RESPONSE_TYPE_JSON.equals(responseType)
&& !getBrowserVersion().hasFeature(XHR_RESPONSE_TYPE_THROWS_UNSENT))
|| RESPONSE_TYPE_TEXT.equals(responseType)) {
if (state_ == OPENED && !async_ && !getBrowserVersion().hasFeature(XHR_RESPONSE_TYPE_THROWS_UNSENT)) {
throw Context.reportRuntimeError(
"InvalidAccessError: synchronous XMLHttpRequests do not support responseType");
}
responseType_ = responseType;
}
}
/**
* @return returns the response's body content as an ArrayBuffer, Blob, Document, JavaScript Object,
* or DOMString, depending on the value of the request's responseType property.
*/
@JsxGetter
public Object getResponse() {
if (RESPONSE_TYPE_DEFAULT.equals(responseType_) || RESPONSE_TYPE_TEXT.equals(responseType_)) {
return getResponseText();
}
if (state_ != DONE) {
return null;
}
if (webResponse_ instanceof NetworkErrorWebResponse) {
if (LOG.isDebugEnabled()) {
LOG.debug("XMLHttpRequest.responseXML returns of a network error ("
+ ((NetworkErrorWebResponse) webResponse_).getError() + ")");
}
return null;
}
if (RESPONSE_TYPE_ARRAYBUFFER.equals(responseType_)) {
long contentLength = webResponse_.getContentLength();
NativeArrayBuffer nativeArrayBuffer = new NativeArrayBuffer(contentLength);
try {
final int bufferLength = Math.min(1024, (int) contentLength);
final byte[] buffer = new byte[bufferLength];
int offset = 0;
try (InputStream inputStream = webResponse_.getContentAsStream()) {
int readLen;
while ((readLen = inputStream.read(buffer, 0, bufferLength)) != -1) {
final long newLength = offset + readLen;
// gzip content and the unzipped content is larger
if (newLength > contentLength) {
final NativeArrayBuffer expanded = new NativeArrayBuffer(newLength);
System.arraycopy(nativeArrayBuffer.getBuffer(), 0,
expanded.getBuffer(), 0, (int) contentLength);
contentLength = newLength;
nativeArrayBuffer = expanded;
}
System.arraycopy(buffer, 0, nativeArrayBuffer.getBuffer(), offset, readLen);
offset = (int) newLength;
}
}
// for small responses the gzipped content might be larger than the original
if (offset < contentLength) {
final NativeArrayBuffer shrinked = new NativeArrayBuffer(offset);
System.arraycopy(nativeArrayBuffer.getBuffer(), 0, shrinked.getBuffer(), 0, offset);
nativeArrayBuffer = shrinked;
}
nativeArrayBuffer.setParentScope(getParentScope());
nativeArrayBuffer.setPrototype(
ScriptableObject.getClassPrototype(getWindow(), nativeArrayBuffer.getClassName()));
return nativeArrayBuffer;
}
catch (final IOException e) {
webResponse_ = new NetworkErrorWebResponse(webRequest_, e);
return null;
}
}
else if (RESPONSE_TYPE_BLOB.equals(responseType_)) {
try {
if (webResponse_ != null) {
try (InputStream inputStream = webResponse_.getContentAsStream()) {
final Blob blob = new Blob(IOUtils.toByteArray(inputStream), webResponse_.getContentType());
blob.setParentScope(getParentScope());
blob.setPrototype(ScriptableObject.getClassPrototype(getWindow(), blob.getClassName()));
return blob;
}
}
}
catch (final IOException e) {
webResponse_ = new NetworkErrorWebResponse(webRequest_, e);
return null;
}
}
else if (RESPONSE_TYPE_DOCUMENT.equals(responseType_)) {
if (webResponse_ != null) {
try {
final Charset encoding = webResponse_.getContentCharset();
if (encoding == null) {
return "";
}
final String content = webResponse_.getContentAsString(encoding);
if (content == null) {
return "";
}
return DOMParser.parseFromString(this, content, webResponse_.getContentType());
}
catch (final IOException e) {
webResponse_ = new NetworkErrorWebResponse(webRequest_, e);
return null;
}
}
}
else if (RESPONSE_TYPE_JSON.equals(responseType_)) {
if (webResponse_ != null) {
final Charset encoding = webResponse_.getContentCharset();
if (encoding == null) {
return null;
}
final String content = webResponse_.getContentAsString(encoding);
if (content == null) {
return null;
}
try {
return new JsonParser(Context.getCurrentContext(), this).parseValue(content);
}
catch (final ParseException e) {
webResponse_ = new NetworkErrorWebResponse(webRequest_, new IOException(e));
return null;
}
}
}
return "";
}
/**
* Returns a string version of the data retrieved from the server.
* @return a string version of the data retrieved from the server
*/
@JsxGetter
public String getResponseText() {
if ((state_ == UNSENT || state_ == OPENED) && getBrowserVersion().hasFeature(XHR_RESPONSE_TEXT_EMPTY_UNSENT)) {
return "";
}
if (!RESPONSE_TYPE_DEFAULT.equals(responseType_) && !RESPONSE_TYPE_TEXT.equals(responseType_)) {
throw Context.reportRuntimeError(
"InvalidStateError: Failed to read the 'responseText' property from 'XMLHttpRequest': "
+ "The value is only accessible if the object's 'responseType' is '' or 'text' "
+ "(was '" + getResponseType() + "').");
}
if (state_ == UNSENT || state_ == OPENED) {
return "";
}
if (webResponse_ instanceof NetworkErrorWebResponse) {
if (LOG.isDebugEnabled()) {
LOG.debug("XMLHttpRequest.responseXML returns of a network error ("
+ ((NetworkErrorWebResponse) webResponse_).getError() + ")");
}
return null;
}
if (webResponse_ != null) {
final Charset encoding = webResponse_.getContentCharset();
if (encoding == null) {
return "";
}
final String content = webResponse_.getContentAsString(encoding);
if (content == null) {
return "";
}
return content;
}
if (LOG.isDebugEnabled()) {
LOG.debug("XMLHttpRequest.responseText was retrieved before the response was available.");
}
return "";
}
/**
* Returns a DOM-compatible document object version of the data retrieved from the server.
* @return a DOM-compatible document object version of the data retrieved from the server
*/
@JsxGetter
public Object getResponseXML() {
if (webResponse_ == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("XMLHttpRequest.responseXML returns null because there "
+ "in no web resonse so far (has send() been called?)");
}
return null;
}
if (webResponse_ instanceof NetworkErrorWebResponse) {
if (LOG.isDebugEnabled()) {
LOG.debug("XMLHttpRequest.responseXML returns of a network error ("
+ ((NetworkErrorWebResponse) webResponse_).getError() + ")");
}
return null;
}
final String contentType = webResponse_.getContentType();
if (contentType.isEmpty() || contentType.contains("xml")) {
final Window w = getWindow();
try {
final XmlPage page = new XmlPage(webResponse_, w.getWebWindow());
final XMLDocument document = new XMLDocument();
document.setPrototype(getPrototype(document.getClass()));
document.setParentScope(w);
document.setDomNode(page);
return document;
}
catch (final IOException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Failed parsing XML document " + webResponse_.getWebRequest().getUrl() + ": "
+ e.getMessage());
}
return null;
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("XMLHttpRequest.responseXML was called but the response is "
+ webResponse_.getContentType());
}
return null;
}
/**
* Returns the numeric status returned by the server, such as 404 for "Not Found"
* or 200 for "OK".
* @return the numeric status returned by the server
*/
@JsxGetter
public int getStatus() {
if (state_ == UNSENT || state_ == OPENED) {
return 0;
}
if (webResponse_ != null) {
return webResponse_.getStatusCode();
}
if (LOG.isErrorEnabled()) {
LOG.error("XMLHttpRequest.status was retrieved without a response available (readyState: "
+ state_ + ").");
}
return 0;
}
/**
* Returns the string message accompanying the status code, such as "Not Found" or "OK".
* @return the string message accompanying the status code
*/
@JsxGetter
public String getStatusText() {
if (state_ == UNSENT || state_ == OPENED) {
return "";
}
if (webResponse_ != null) {
return webResponse_.getStatusMessage();
}
if (LOG.isErrorEnabled()) {
LOG.error("XMLHttpRequest.statusText was retrieved without a response available (readyState: "
+ state_ + ").");
}
return null;
}
/**
* Cancels the current HTTP request.
*/
@JsxFunction
public void abort() {
getWindow().getWebWindow().getJobManager().stopJob(jobID_);
if (state_ == OPENED
|| state_ == HEADERS_RECEIVED
|| state_ == LOADING) {
setState(DONE);
webResponse_ = new NetworkErrorWebResponse(webRequest_, null);
fireJavascriptEvent(Event.TYPE_READY_STATE_CHANGE);
fireJavascriptEvent(Event.TYPE_ABORT);
fireJavascriptEvent(Event.TYPE_LOAD_END);
}
// ScriptRuntime.constructError("NetworkError",
// "Failed to execute 'send' on 'XMLHttpRequest': Failed to load '" + webRequest_.getUrl() + "'");
setState(UNSENT);
webResponse_ = new NetworkErrorWebResponse(webRequest_, null);
aborted_ = true;
}
/**
* Returns the labels and values of all the HTTP headers.
* @return the labels and values of all the HTTP headers
*/
@JsxFunction
public String getAllResponseHeaders() {
if (state_ == UNSENT || state_ == OPENED) {
return "";
}
if (webResponse_ != null) {
final StringBuilder builder = new StringBuilder();
for (final NameValuePair header : webResponse_.getResponseHeaders()) {
builder.append(header.getName()).append(": ").append(header.getValue());
if (!getBrowserVersion().hasFeature(XHR_ALL_RESPONSE_HEADERS_SEPARATE_BY_LF)) {
builder.append('\r');
}
builder.append('\n');
}
if (getBrowserVersion().hasFeature(XHR_ALL_RESPONSE_HEADERS_APPEND_SEPARATOR)) {
if (!getBrowserVersion().hasFeature(XHR_ALL_RESPONSE_HEADERS_SEPARATE_BY_LF)) {
builder.append('\r');
}
builder.append('\n');
}
return builder.toString();
}
if (LOG.isErrorEnabled()) {
LOG.error("XMLHttpRequest.getAllResponseHeaders() was called without a response available (readyState: "
+ state_ + ").");
}
return null;
}
/**
* Retrieves the value of an HTTP header from the response body.
* @param headerName the (case-insensitive) name of the header to retrieve
* @return the value of the specified HTTP header
*/
@JsxFunction
public String getResponseHeader(final String headerName) {
if (state_ == UNSENT || state_ == OPENED) {
return null;
}
if (webResponse_ != null) {
return webResponse_.getResponseHeaderValue(headerName);
}
if (LOG.isErrorEnabled()) {
LOG.error("XMLHttpRequest.getAllResponseHeaders(..) was called without a response available (readyState: "
+ state_ + ").");
}
return null;
}
/**
* Assigns the destination URL, method and other optional attributes of a pending request.
* @param method the method to use to send the request to the server (GET, POST, etc)
* @param urlParam the URL to send the request to
* @param asyncParam Whether or not to send the request to the server asynchronously, defaults to {@code true}
* @param user If authentication is needed for the specified URL, the username to use to authenticate
* @param password If authentication is needed for the specified URL, the password to use to authenticate
*/
@JsxFunction
public void open(final String method, final Object urlParam, final Object asyncParam,
final Object user, final Object password) {
if ((urlParam == null || "".equals(urlParam)) && !getBrowserVersion().hasFeature(XHR_OPEN_ALLOW_EMTPY_URL)) {
throw Context.reportRuntimeError("URL for XHR.open can't be empty!");
}
// async defaults to true if not specified
boolean async = true;
if (!Undefined.isUndefined(asyncParam)) {
async = ScriptRuntime.toBoolean(asyncParam);
}
final String url = Context.toString(urlParam);
// (URL + Method + User + Password) become a WebRequest instance.
final HtmlPage containingPage = (HtmlPage) getWindow().getWebWindow().getEnclosedPage();
try {
final URL fullUrl = containingPage.getFullyQualifiedUrl(url);
if (!isAllowCrossDomainsFor(fullUrl)) {
throw Context.reportRuntimeError("Access to restricted URI denied");
}
final WebRequest request = new WebRequest(fullUrl, getBrowserVersion().getXmlHttpRequestAcceptHeader(),
getBrowserVersion().getAcceptEncodingHeader());
request.setCharset(UTF_8);
request.setRefererlHeader(containingPage.getUrl());
try {
request.setHttpMethod(HttpMethod.valueOf(method.toUpperCase(Locale.ROOT)));
}
catch (final IllegalArgumentException e) {
if (LOG.isInfoEnabled()) {
LOG.info("Incorrect HTTP Method '" + method + "'");
}
return;
}
final URL pageRequestUrl = containingPage.getUrl();
isSameOrigin_ = isSameOrigin(pageRequestUrl, fullUrl);
final boolean alwaysAddOrigin = !getBrowserVersion().hasFeature(XHR_NO_CROSS_ORIGIN_TO_ABOUT)
&& HttpMethod.GET != request.getHttpMethod()
&& HttpMethod.PATCH != request.getHttpMethod()
&& HttpMethod.HEAD != request.getHttpMethod();
if (alwaysAddOrigin || !isSameOrigin_) {
final StringBuilder origin = new StringBuilder().append(pageRequestUrl.getProtocol()).append("://")
.append(pageRequestUrl.getHost());
if (pageRequestUrl.getPort() != -1) {
origin.append(':').append(pageRequestUrl.getPort());
}
request.setAdditionalHeader(HttpHeader.ORIGIN, origin.toString());
}
// password is ignored if no user defined
if (user != null && !Undefined.isUndefined(user)) {
final String userCred = user.toString();
String passwordCred = "";
if (password != null && !Undefined.isUndefined(password)) {
passwordCred = password.toString();
}
request.setCredentials(new UsernamePasswordCredentials(userCred, passwordCred));
}
webRequest_ = request;
}
catch (final MalformedURLException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Unable to initialize XMLHttpRequest using malformed URL '" + url + "'.");
}
return;
}
// Async stays a boolean.
async_ = async;
// Change the state!
setState(OPENED);
fireJavascriptEvent(Event.TYPE_READY_STATE_CHANGE);
}
private boolean isAllowCrossDomainsFor(final URL newUrl) {
return !(getBrowserVersion().hasFeature(XHR_NO_CROSS_ORIGIN_TO_ABOUT)
&& UrlUtils.ABOUT.equals(newUrl.getProtocol()));
}
private static boolean isSameOrigin(final URL originUrl, final URL newUrl) {
if (!originUrl.getHost().equals(newUrl.getHost())) {
return false;
}
int originPort = originUrl.getPort();
if (originPort == -1) {
originPort = originUrl.getDefaultPort();
}
int newPort = newUrl.getPort();
if (newPort == -1) {
newPort = newUrl.getDefaultPort();
}
return originPort == newPort;
}
/**
* Sends the specified content to the server in an HTTP request and receives the response.
* @param content the body of the message being sent with the request
*/
@JsxFunction
public void send(final Object content) {
if (webRequest_ == null) {
return;
}
if (!async_ && timeout_ > 0) {
Context.throwAsScriptRuntimeEx(new RuntimeException("Synchronous requests must not set a timeout."));
return;
}
prepareRequestContent(content);
if (timeout_ > 0) {
webRequest_.setTimeout(timeout_);
}
final Window w = getWindow();
final WebWindow ww = w.getWebWindow();
final WebClient client = ww.getWebClient();
final AjaxController ajaxController = client.getAjaxController();
final HtmlPage page = (HtmlPage) ww.getEnclosedPage();
final boolean synchron = ajaxController.processSynchron(page, webRequest_, async_);
if (synchron) {
doSend();
}
else {
// Create and start a thread in which to execute the request.
final ContextFactory cf = ((JavaScriptEngine) client.getJavaScriptEngine()).getContextFactory();
final ContextAction
© 2015 - 2024 Weber Informatics LLC | Privacy Policy