| /* |
| * 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. |
| */ |
| |
| var utils = require('./../datajs.js').utils; |
| // Imports. |
| |
| var defined = utils.defined; |
| var delay = utils.delay; |
| |
| var ticks = 0; |
| |
| /* Checks whether the specified request can be satisfied with a JSONP request. |
| * @param request - Request object to check. |
| * @returns {Boolean} true if the request can be satisfied; false otherwise. |
| |
| * Requests that 'degrade' without changing their meaning by going through JSONP |
| * are considered usable. |
| * |
| * We allow data to come in a different format, as the servers SHOULD honor the Accept |
| * request but may in practice return content with a different MIME type. |
| */ |
| var canUseJSONP = function (request) { |
| |
| if (request.method && request.method !== "GET") { |
| return false; |
| } |
| |
| return true; |
| }; |
| |
| /** Creates an IFRAME tag for loading the JSONP script |
| * @param {String} url - The source URL of the script |
| * @returns {HTMLElement} The IFRAME tag |
| */ |
| var createIFrame = function (url) { |
| var iframe = window.document.createElement("IFRAME"); |
| iframe.style.display = "none"; |
| |
| var attributeEncodedUrl = url.replace(/&/g, "&").replace(/"/g, """).replace(/\</g, "<"); |
| var html = "<html><head><script type=\"text/javascript\" src=\"" + attributeEncodedUrl + "\"><\/script><\/head><body><\/body><\/html>"; |
| |
| var body = window.document.getElementsByTagName("BODY")[0]; |
| body.appendChild(iframe); |
| |
| writeHtmlToIFrame(iframe, html); |
| return iframe; |
| }; |
| |
| /** Creates a XmlHttpRequest object. |
| * @returns {XmlHttpRequest} XmlHttpRequest object. |
| */ |
| var createXmlHttpRequest = function () { |
| if (window.XMLHttpRequest) { |
| return new window.XMLHttpRequest(); |
| } |
| var exception; |
| if (window.ActiveXObject) { |
| try { |
| return new window.ActiveXObject("Msxml2.XMLHTTP.6.0"); |
| } catch (_) { |
| try { |
| return new window.ActiveXObject("Msxml2.XMLHTTP.3.0"); |
| } catch (e) { |
| exception = e; |
| } |
| } |
| } else { |
| exception = { message: "XMLHttpRequest not supported" }; |
| } |
| throw exception; |
| }; |
| |
| /** Checks whether the specified URL is an absolute URL. |
| * @param {String} url - URL to check. |
| * @returns {Boolean} true if the url is an absolute URL; false otherwise. |
| */ |
| var isAbsoluteUrl = function (url) { |
| return url.indexOf("http://") === 0 || |
| url.indexOf("https://") === 0 || |
| url.indexOf("file://") === 0; |
| }; |
| |
| /** Checks whether the specified URL is local to the current context. |
| * @param {String} url - URL to check. |
| * @returns {Boolean} true if the url is a local URL; false otherwise. |
| */ |
| var isLocalUrl = function (url) { |
| |
| if (!isAbsoluteUrl(url)) { |
| return true; |
| } |
| |
| // URL-embedded username and password will not be recognized as same-origin URLs. |
| var location = window.location; |
| var locationDomain = location.protocol + "//" + location.host + "/"; |
| return (url.indexOf(locationDomain) === 0); |
| }; |
| |
| /** Removes a callback used for a JSONP request. |
| * @param {String} name - Function name to remove. |
| * @param {Number} tick - Tick count used on the callback. |
| */ |
| var removeCallback = function (name, tick) { |
| try { |
| delete window[name]; |
| } catch (err) { |
| window[name] = undefined; |
| if (tick === ticks - 1) { |
| ticks -= 1; |
| } |
| } |
| }; |
| |
| /** Removes an iframe. |
| * @param {Object} iframe - The iframe to remove. |
| * @returns {Object} Null value to be assigned to iframe reference. |
| */ |
| var removeIFrame = function (iframe) { |
| if (iframe) { |
| writeHtmlToIFrame(iframe, ""); |
| iframe.parentNode.removeChild(iframe); |
| } |
| |
| return null; |
| }; |
| |
| /** Reads response headers into array. |
| * @param {XMLHttpRequest} xhr - HTTP request with response available. |
| * @param {Array} headers - Target array to fill with name/value pairs. |
| */ |
| var readResponseHeaders = function (xhr, headers) { |
| |
| var responseHeaders = xhr.getAllResponseHeaders().split(/\r?\n/); |
| var i, len; |
| for (i = 0, len = responseHeaders.length; i < len; i++) { |
| if (responseHeaders[i]) { |
| var header = responseHeaders[i].split(": "); |
| headers[header[0]] = header[1]; |
| } |
| } |
| }; |
| |
| /** Writes HTML to an IFRAME document. |
| * @param {HTMLElement} iframe - The IFRAME element to write to. |
| * @param {String} html - The HTML to write. |
| */ |
| var writeHtmlToIFrame = function (iframe, html) { |
| var frameDocument = (iframe.contentWindow) ? iframe.contentWindow.document : iframe.contentDocument.document; |
| frameDocument.open(); |
| frameDocument.write(html); |
| frameDocument.close(); |
| }; |
| |
| exports.defaultHttpClient = { |
| callbackParameterName: "$callback", |
| |
| formatQueryString: "$format=json", |
| |
| enableJsonpCallback: false, |
| |
| /** Performs a network request. |
| * @param {Object} request - Request description |
| * @param {Function} success - Success callback with the response object. |
| * @param {Function} error - Error callback with an error object. |
| * @returns {Object} Object with an 'abort' method for the operation. |
| */ |
| request: function (request, success, error) { |
| |
| var result = {}; |
| var xhr = null; |
| var done = false; |
| var iframe; |
| |
| result.abort = function () { |
| iframe = removeIFrame(iframe); |
| if (done) { |
| return; |
| } |
| |
| done = true; |
| if (xhr) { |
| xhr.abort(); |
| xhr = null; |
| } |
| |
| error({ message: "Request aborted" }); |
| }; |
| |
| var handleTimeout = function () { |
| iframe = removeIFrame(iframe); |
| if (!done) { |
| done = true; |
| xhr = null; |
| error({ message: "Request timed out" }); |
| } |
| }; |
| |
| var name; |
| var url = request.requestUri; |
| var enableJsonpCallback = defined(request.enableJsonpCallback, this.enableJsonpCallback); |
| var callbackParameterName = defined(request.callbackParameterName, this.callbackParameterName); |
| var formatQueryString = defined(request.formatQueryString, this.formatQueryString); |
| if (!enableJsonpCallback || isLocalUrl(url)) { |
| |
| xhr = createXmlHttpRequest(); |
| xhr.onreadystatechange = function () { |
| if (done || xhr === null || xhr.readyState !== 4) { |
| return; |
| } |
| |
| // Workaround for XHR behavior on IE. |
| var statusText = xhr.statusText; |
| var statusCode = xhr.status; |
| if (statusCode === 1223) { |
| statusCode = 204; |
| statusText = "No Content"; |
| } |
| |
| var headers = []; |
| readResponseHeaders(xhr, headers); |
| |
| var response = { requestUri: url, statusCode: statusCode, statusText: statusText, headers: headers, body: xhr.responseText }; |
| |
| done = true; |
| xhr = null; |
| if (statusCode >= 200 && statusCode <= 299) { |
| success(response); |
| } else { |
| error({ message: "HTTP request failed", request: request, response: response }); |
| } |
| }; |
| |
| xhr.open(request.method || "GET", url, true, request.user, request.password); |
| |
| // Set the name/value pairs. |
| if (request.headers) { |
| for (name in request.headers) { |
| xhr.setRequestHeader(name, request.headers[name]); |
| } |
| } |
| |
| // Set the timeout if available. |
| if (request.timeoutMS) { |
| xhr.timeout = request.timeoutMS; |
| xhr.ontimeout = handleTimeout; |
| } |
| |
| xhr.send(request.body); |
| } else { |
| if (!canUseJSONP(request)) { |
| throw { message: "Request is not local and cannot be done through JSONP." }; |
| } |
| |
| var tick = ticks; |
| ticks += 1; |
| var tickText = tick.toString(); |
| var succeeded = false; |
| var timeoutId; |
| name = "handleJSONP_" + tickText; |
| window[name] = function (data) { |
| iframe = removeIFrame(iframe); |
| if (!done) { |
| succeeded = true; |
| window.clearTimeout(timeoutId); |
| removeCallback(name, tick); |
| |
| // Workaround for IE8 and IE10 below where trying to access data.constructor after the IFRAME has been removed |
| // throws an "unknown exception" |
| if (window.ActiveXObject) { |
| data = window.JSON.parse(window.JSON.stringify(data)); |
| } |
| |
| |
| var headers; |
| if (!formatQueryString || formatQueryString == "$format=json") { |
| headers = { "Content-Type": "application/json;odata.metadata=minimal", "OData-Version": "4.0" }; |
| } else { |
| // the formatQueryString should be in the format of "$format=xxx", xxx should be one of the application/json;odata.metadata=minimal(none or full) |
| // set the content-type with the string xxx which stars from index 8. |
| headers = { "Content-Type": formatQueryString.substring(8), "OData-Version": "4.0" }; |
| } |
| |
| // Call the success callback in the context of the parent window, instead of the IFRAME |
| delay(function () { |
| removeIFrame(iframe); |
| success({ body: data, statusCode: 200, headers: headers }); |
| }); |
| } |
| }; |
| |
| // Default to two minutes before timing out, 1000 ms * 60 * 2 = 120000. |
| var timeoutMS = (request.timeoutMS) ? request.timeoutMS : 120000; |
| timeoutId = window.setTimeout(handleTimeout, timeoutMS); |
| |
| var queryStringParams = callbackParameterName + "=parent." + name; |
| if (formatQueryString) { |
| queryStringParams += "&" + formatQueryString; |
| } |
| |
| var qIndex = url.indexOf("?"); |
| if (qIndex === -1) { |
| url = url + "?" + queryStringParams; |
| } else if (qIndex === url.length - 1) { |
| url = url + queryStringParams; |
| } else { |
| url = url + "&" + queryStringParams; |
| } |
| |
| iframe = createIFrame(url); |
| } |
| |
| return result; |
| } |
| }; |
| |
| exports.canUseJSONP = canUseJSONP; |
| exports.isAbsoluteUrl = isAbsoluteUrl; |
| exports.isLocalUrl = isLocalUrl; |
| |