forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			234 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
const CC = Components.Constructor;
 | 
						|
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
 | 
						|
                             "nsIBinaryInputStream",
 | 
						|
                             "setInputStream");
 | 
						|
Cu.importGlobalProperties(["DOMParser"]);
 | 
						|
 | 
						|
function handleRequest(req, res) {
 | 
						|
  try {
 | 
						|
    reallyHandleRequest(req, res);
 | 
						|
  } catch (ex) {
 | 
						|
    res.setStatusLine("1.0", 200, "AlmostOK");
 | 
						|
    let msg = "Error handling request: " + ex + "\n" + ex.stack;
 | 
						|
    log(msg);
 | 
						|
    res.write(msg);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function log(msg) {
 | 
						|
  // dump("BING-SERVER-MOCK: " + msg + "\n");
 | 
						|
}
 | 
						|
 | 
						|
const statusCodes = {
 | 
						|
  400: "Bad Request",
 | 
						|
  401: "Unauthorized",
 | 
						|
  403: "Forbidden",
 | 
						|
  404: "Not Found",
 | 
						|
  405: "Method Not Allowed",
 | 
						|
  500: "Internal Server Error",
 | 
						|
  501: "Not Implemented",
 | 
						|
  503: "Service Unavailable"
 | 
						|
};
 | 
						|
 | 
						|
function HTTPError(code = 500, message) {
 | 
						|
  this.code = code;
 | 
						|
  this.name = statusCodes[code] || "HTTPError";
 | 
						|
  this.message = message || this.name;
 | 
						|
}
 | 
						|
HTTPError.prototype = new Error();
 | 
						|
HTTPError.prototype.constructor = HTTPError;
 | 
						|
 | 
						|
function sendError(res, err) {
 | 
						|
  if (!(err instanceof HTTPError)) {
 | 
						|
    err = new HTTPError(typeof err == "number" ? err : 500,
 | 
						|
                        err.message || typeof err == "string" ? err : "");
 | 
						|
  }
 | 
						|
  res.setStatusLine("1.1", err.code, err.name);
 | 
						|
  res.write(err.message);
 | 
						|
}
 | 
						|
 | 
						|
function parseQuery(query) {
 | 
						|
  let ret = {};
 | 
						|
  for (let param of query.replace(/^[?&]/, "").split("&")) {
 | 
						|
    param = param.split("=");
 | 
						|
    if (!param[0])
 | 
						|
      continue;
 | 
						|
    ret[unescape(param[0])] = unescape(param[1]);
 | 
						|
  }
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
function getRequestBody(req) {
 | 
						|
  let avail;
 | 
						|
  let bytes = [];
 | 
						|
  let body = new BinaryInputStream(req.bodyInputStream);
 | 
						|
 | 
						|
  while ((avail = body.available()) > 0)
 | 
						|
    Array.prototype.push.apply(bytes, body.readByteArray(avail));
 | 
						|
 | 
						|
  return String.fromCharCode.apply(null, bytes);
 | 
						|
}
 | 
						|
 | 
						|
function sha1(str) {
 | 
						|
  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
 | 
						|
                    .createInstance(Ci.nsIScriptableUnicodeConverter);
 | 
						|
  converter.charset = "UTF-8";
 | 
						|
  // `result` is an out parameter, `result.value` will contain the array length.
 | 
						|
  let result = {};
 | 
						|
  // `data` is an array of bytes.
 | 
						|
  let data = converter.convertToByteArray(str, result);
 | 
						|
  let ch = Cc["@mozilla.org/security/hash;1"]
 | 
						|
             .createInstance(Ci.nsICryptoHash);
 | 
						|
  ch.init(ch.SHA1);
 | 
						|
  ch.update(data, data.length);
 | 
						|
  let hash = ch.finish(false);
 | 
						|
 | 
						|
  // Return the two-digit hexadecimal code for a byte.
 | 
						|
  function toHexString(charCode) {
 | 
						|
    return ("0" + charCode.toString(16)).slice(-2);
 | 
						|
  }
 | 
						|
 | 
						|
  // Convert the binary hash data to a hex string.
 | 
						|
  return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
 | 
						|
}
 | 
						|
 | 
						|
function parseXml(body) {
 | 
						|
  let parser = new DOMParser();
 | 
						|
  let xml = parser.parseFromString(body, "text/xml");
 | 
						|
  if (xml.documentElement.localName == "parsererror")
 | 
						|
    throw new Error("Invalid XML");
 | 
						|
  return xml;
 | 
						|
}
 | 
						|
 | 
						|
function getInputStream(path) {
 | 
						|
  let file = Cc["@mozilla.org/file/directory_service;1"]
 | 
						|
               .getService(Ci.nsIProperties)
 | 
						|
               .get("CurWorkD", Ci.nsIFile);
 | 
						|
  for (let part of path.split("/"))
 | 
						|
    file.append(part);
 | 
						|
  let fileStream  = Cc["@mozilla.org/network/file-input-stream;1"]
 | 
						|
                      .createInstance(Ci.nsIFileInputStream);
 | 
						|
  fileStream.init(file, 1, 0, false);
 | 
						|
  return fileStream;
 | 
						|
}
 | 
						|
 | 
						|
function checkAuth(req) {
 | 
						|
  let err = new Error("Authorization failed");
 | 
						|
  err.code = 401;
 | 
						|
 | 
						|
  if (!req.hasHeader("Authorization"))
 | 
						|
    throw new HTTPError(401, "No Authorization header provided.");
 | 
						|
 | 
						|
  let auth = req.getHeader("Authorization");
 | 
						|
  if (!auth.startsWith("Bearer "))
 | 
						|
    throw new HTTPError(401, "Invalid Authorization header content: '" + auth + "'");
 | 
						|
 | 
						|
  // Rejecting inactive subscriptions.
 | 
						|
  if (auth.includes("inactive")) {
 | 
						|
    const INACTIVE_STATE_RESPONSE = "<html><body><h1>TranslateApiException</h1><p>Method: TranslateArray()</p><p>Message: The Azure Market Place Translator Subscription associated with the request credentials is not in an active state.</p><code></code><p>message id=5641.V2_Rest.TranslateArray.48CC6470</p></body></html>";
 | 
						|
    throw new HTTPError(401, INACTIVE_STATE_RESPONSE);
 | 
						|
  }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
function reallyHandleRequest(req, res) {
 | 
						|
  log("method: " + req.method);
 | 
						|
  if (req.method != "POST") {
 | 
						|
    sendError(res, "Bing only deals with POST requests, not '" + req.method + "'.");
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  let body = getRequestBody(req);
 | 
						|
  log("body: " + body);
 | 
						|
 | 
						|
  // First, we'll see if we're dealing with an XML body:
 | 
						|
  let contentType = req.hasHeader("Content-Type") ? req.getHeader("Content-Type") : null;
 | 
						|
  log("contentType: " + contentType);
 | 
						|
 | 
						|
  if (contentType.startsWith("text/xml")) {
 | 
						|
    try {
 | 
						|
      // For all these requests the client needs to supply the correct
 | 
						|
      // authentication headers.
 | 
						|
      checkAuth(req);
 | 
						|
 | 
						|
      let xml = parseXml(body);
 | 
						|
      let method = xml.documentElement.localName;
 | 
						|
      log("invoking method: " + method);
 | 
						|
      // If the requested method is supported, delegate it to its handler.
 | 
						|
      if (methodHandlers[method])
 | 
						|
        methodHandlers[method](res, xml);
 | 
						|
      else
 | 
						|
        throw new HTTPError(501);
 | 
						|
    } catch (ex) {
 | 
						|
      sendError(res, ex, ex.code);
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    // Not XML, so it must be a query-string.
 | 
						|
    let params = parseQuery(body);
 | 
						|
 | 
						|
    // Delegate an authentication request to the correct handler.
 | 
						|
    if ("grant_type" in params && params.grant_type == "client_credentials")
 | 
						|
      methodHandlers.authenticate(res, params);
 | 
						|
    else
 | 
						|
      sendError(res, 501);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
const methodHandlers = {
 | 
						|
  authenticate: function(res, params) {
 | 
						|
    // Validate a few required parameters.
 | 
						|
    if (params.scope != "http://api.microsofttranslator.com") {
 | 
						|
      sendError(res, "Invalid scope.");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    if (!params.client_id) {
 | 
						|
      sendError(res, "Missing client_id param.");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    if (!params.client_secret) {
 | 
						|
      sendError(res, "Missing client_secret param.");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Defines the tokens for certain client ids.
 | 
						|
    const TOKEN_MAP = {
 | 
						|
      'testInactive'  : 'inactive',
 | 
						|
      'testClient'    : 'test'
 | 
						|
    };
 | 
						|
    let token = 'test'; // Default token.
 | 
						|
    if((params.client_id in TOKEN_MAP)){
 | 
						|
      token = TOKEN_MAP[params.client_id];
 | 
						|
    }
 | 
						|
    let content = JSON.stringify({
 | 
						|
      access_token: token,
 | 
						|
      expires_in: 600
 | 
						|
    });
 | 
						|
 | 
						|
    res.setStatusLine("1.1", 200, "OK");
 | 
						|
    res.setHeader("Content-Length", String(content.length));
 | 
						|
    res.setHeader("Content-Type", "application/json");
 | 
						|
    res.write(content);
 | 
						|
  },
 | 
						|
 | 
						|
  TranslateArrayRequest: function(res, xml, body) {
 | 
						|
    let from = xml.querySelector("From").firstChild.nodeValue;
 | 
						|
    let to = xml.querySelector("To").firstChild.nodeValue
 | 
						|
    log("translating from '" + from + "' to '" + to + "'");
 | 
						|
 | 
						|
    res.setStatusLine("1.1", 200, "OK");
 | 
						|
    res.setHeader("Content-Type", "text/xml");
 | 
						|
 | 
						|
    let hash = sha1(body).substr(0, 10);
 | 
						|
    log("SHA1 hash of content: " + hash);
 | 
						|
    let inputStream = getInputStream(
 | 
						|
      "browser/browser/components/translation/test/fixtures/result-" + hash + ".txt");
 | 
						|
    res.bodyOutputStream.writeFrom(inputStream, inputStream.available());
 | 
						|
    inputStream.close();
 | 
						|
  }
 | 
						|
};
 |