/*
 * Tests for bug 1241377: A channel with nsIFormPOSTActionChannel interface
 * should be able to accept form POST.
 */
"use strict";
const SCHEME = "x-bug1241377";
const FORM_BASE = SCHEME + "://dummy/form/";
const NORMAL_FORM_URI = FORM_BASE + "normal.html";
const UPLOAD_FORM_URI = FORM_BASE + "upload.html";
const POST_FORM_URI = FORM_BASE + "post.html";
const ACTION_BASE = SCHEME + "://dummy/action/";
const NORMAL_ACTION_URI = ACTION_BASE + "normal.html";
const UPLOAD_ACTION_URI = ACTION_BASE + "upload.html";
const POST_ACTION_URI = ACTION_BASE + "post.html";
function CustomProtocolHandler() {}
CustomProtocolHandler.prototype = {
  /** nsIProtocolHandler */
  get scheme() {
    return SCHEME;
  },
  get defaultPort() {
    return -1;
  },
  get protocolFlags() {
    return (
      Ci.nsIProtocolHandler.URI_NORELATIVE |
      Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE
    );
  },
  newChannel(aURI, aLoadInfo) {
    return new CustomChannel(aURI, aLoadInfo);
  },
  allowPort(port, scheme) {
    return port != -1;
  },
  /** nsIFactory */
  createInstance(aIID) {
    return this.QueryInterface(aIID);
  },
  /** nsISupports */
  QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler", "nsIFactory"]),
  classID: Components.ID("{16d594bc-d9d8-47ae-a139-ea714dc0c35c}"),
};
function CustomChannel(aURI, aLoadInfo) {
  this.uri = aURI;
  this.loadInfo = aLoadInfo;
  this._uploadStream = null;
  var interfaces = [Ci.nsIRequest, Ci.nsIChannel];
  if (this.uri.spec == POST_ACTION_URI) {
    interfaces.push(Ci.nsIFormPOSTActionChannel);
  } else if (this.uri.spec == UPLOAD_ACTION_URI) {
    interfaces.push(Ci.nsIUploadChannel);
  }
  this.QueryInterface = ChromeUtils.generateQI(interfaces);
}
CustomChannel.prototype = {
  /** nsIUploadChannel */
  get uploadStream() {
    return this._uploadStream;
  },
  set uploadStream(val) {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  },
  setUploadStream(aStream, aContentType, aContentLength) {
    this._uploadStream = aStream;
  },
  /** nsIChannel */
  get originalURI() {
    return this.uri;
  },
  get URI() {
    return this.uri;
  },
  owner: null,
  notificationCallbacks: null,
  get securityInfo() {
    return null;
  },
  get contentType() {
    return "text/html";
  },
  set contentType(val) {},
  contentCharset: "UTF-8",
  get contentLength() {
    return -1;
  },
  set contentLength(val) {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  },
  open() {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  },
  asyncOpen(aListener) {
    var data = `
test bug 1241377
`;
    if (this.uri.spec.startsWith(FORM_BASE)) {
      data += `
`;
    } else if (this.uri.spec.startsWith(ACTION_BASE)) {
      var postData = "";
      var headers = {};
      if (this._uploadStream) {
        var bstream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
          Ci.nsIBinaryInputStream
        );
        bstream.setInputStream(this._uploadStream);
        postData = bstream.readBytes(bstream.available());
        if (this._uploadStream instanceof Ci.nsIMIMEInputStream) {
          this._uploadStream.visitHeaders((name, value) => {
            headers[name] = value;
          });
        }
      }
      data += `
`;
    }
    data += `
`;
    var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
      Ci.nsIStringInputStream
    );
    stream.setData(data, data.length);
    var runnable = {
      run: () => {
        try {
          aListener.onStartRequest(this, null);
        } catch (e) {}
        try {
          aListener.onDataAvailable(this, stream, 0, stream.available());
        } catch (e) {}
        try {
          aListener.onStopRequest(this, null, Cr.NS_OK);
        } catch (e) {}
      },
    };
    Services.tm.dispatchToMainThread(runnable);
  },
  /** nsIRequest */
  get name() {
    return this.uri.spec;
  },
  isPending() {
    return false;
  },
  get status() {
    return Cr.NS_OK;
  },
  cancel(status) {},
  loadGroup: null,
  loadFlags:
    Ci.nsIRequest.LOAD_NORMAL |
    Ci.nsIRequest.INHIBIT_CACHING |
    Ci.nsIRequest.LOAD_BYPASS_CACHE,
};
function frameScript() {
  /* eslint-env mozilla/frame-script */
  /* eslint-disable mozilla/no-arbitrary-setTimeout */
  addMessageListener("Test:WaitForIFrame", function() {
    var check = function() {
      if (content) {
        var frame = content.document.getElementById("frame");
        if (frame) {
          var upload_stream = frame.contentDocument.getElementById(
            "upload_stream"
          );
          var post_data = frame.contentDocument.getElementById("post_data");
          var headers = frame.contentDocument.getElementById("upload_headers");
          if (upload_stream && post_data && headers) {
            sendAsyncMessage("Test:IFrameLoaded", [
              upload_stream.value,
              post_data.value,
              headers.value,
            ]);
            return;
          }
        }
      }
      setTimeout(check, 100);
    };
    check();
  });
  /* eslint-enable mozilla/no-arbitrary-setTimeout */
}
function loadTestTab(uri) {
  gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, uri);
  var browser = gBrowser.selectedBrowser;
  let manager = browser.messageManager;
  browser.messageManager.loadFrameScript(
    "data:,(" + frameScript.toString() + ")();",
    true
  );
  return new Promise(resolve => {
    function listener({ data: [hasUploadStream, postData, headers] }) {
      manager.removeMessageListener("Test:IFrameLoaded", listener);
      resolve([hasUploadStream, atob(postData), JSON.parse(headers)]);
    }
    manager.addMessageListener("Test:IFrameLoaded", listener);
    manager.sendAsyncMessage("Test:WaitForIFrame");
  });
}
add_task(async function() {
  var handler = new CustomProtocolHandler();
  var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
  registrar.registerFactory(
    handler.classID,
    "",
    "@mozilla.org/network/protocol;1?name=" + handler.scheme,
    handler
  );
  registerCleanupFunction(function() {
    registrar.unregisterFactory(handler.classID, handler);
  });
});
add_task(async function() {
  var [hasUploadStream] = await loadTestTab(NORMAL_FORM_URI);
  is(hasUploadStream, "no", "normal action should not have uploadStream");
  gBrowser.removeCurrentTab();
});
add_task(async function() {
  var [hasUploadStream] = await loadTestTab(UPLOAD_FORM_URI);
  is(hasUploadStream, "no", "upload action should not have uploadStream");
  gBrowser.removeCurrentTab();
});
add_task(async function() {
  var [hasUploadStream, postData, headers] = await loadTestTab(POST_FORM_URI);
  is(hasUploadStream, "yes", "post action should have uploadStream");
  is(postData, "foo=bar\r\n", "POST data is received correctly");
  is(headers["Content-Type"], "text/plain", "Content-Type header is correct");
  is(headers["Content-Length"], undefined, "Content-Length header is correct");
  gBrowser.removeCurrentTab();
});