summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit/test_unix_domain.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /netwerk/test/unit/test_unix_domain.js
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/test/unit/test_unix_domain.js')
-rw-r--r--netwerk/test/unit/test_unix_domain.js699
1 files changed, 699 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_unix_domain.js b/netwerk/test/unit/test_unix_domain.js
new file mode 100644
index 0000000000..ba1b28cf21
--- /dev/null
+++ b/netwerk/test/unit/test_unix_domain.js
@@ -0,0 +1,699 @@
+// Exercise Unix domain sockets.
+"use strict";
+
+var CC = Components.Constructor;
+
+const UnixServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "initWithFilename"
+);
+const UnixAbstractServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "initWithAbstractAddress"
+);
+
+const ScriptableInputStream = CC(
+ "@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream",
+ "init"
+);
+
+const socketTransportService = Cc[
+ "@mozilla.org/network/socket-transport-service;1"
+].getService(Ci.nsISocketTransportService);
+
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+
+const allPermissions = parseInt("777", 8);
+
+function run_test() {
+ // If we're on Windows, simply check for graceful failure.
+ if (mozinfo.os == "win") {
+ test_not_supported();
+ return;
+ }
+
+ // The xpcshell temp directory on Android doesn't seem to let us create
+ // Unix domain sockets. (Perhaps it's a FAT filesystem?)
+ if (mozinfo.os != "android") {
+ add_test(test_echo);
+ add_test(test_name_too_long);
+ add_test(test_no_directory);
+ add_test(test_no_such_socket);
+ add_test(test_address_in_use);
+ add_test(test_file_in_way);
+ add_test(test_create_permission);
+ add_test(test_connect_permission);
+ add_test(test_long_socket_name);
+ add_test(test_keep_when_offline);
+ }
+
+ if (mozinfo.os == "android" || mozinfo.os == "linux") {
+ add_test(test_abstract_address_socket);
+ }
+
+ run_next_test();
+}
+
+// Check that creating a Unix domain socket fails gracefully on Windows.
+function test_not_supported() {
+ let socketName = do_get_tempdir();
+ socketName.append("socket");
+ info("creating socket: " + socketName.path);
+
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED"
+ );
+
+ do_check_throws_nsIException(
+ () => socketTransportService.createUnixDomainTransport(socketName),
+ "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED"
+ );
+}
+
+// Actually exchange data with Unix domain sockets.
+function test_echo() {
+ let log = "";
+
+ let socketName = do_get_tempdir();
+ socketName.append("socket");
+
+ // Create a server socket, listening for connections.
+ info("creating socket: " + socketName.path);
+ let server = new UnixServerSocket(socketName, allPermissions, -1);
+ server.asyncListen({
+ onSocketAccepted(aServ, aTransport) {
+ info("called test_echo's onSocketAccepted");
+ log += "a";
+
+ Assert.equal(aServ, server);
+
+ let connection = aTransport;
+
+ // Check the server socket's self address.
+ let connectionSelfAddr = connection.getScriptableSelfAddr();
+ Assert.equal(connectionSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ Assert.equal(connectionSelfAddr.address, socketName.path);
+
+ // The client socket is anonymous, so the server transport should
+ // have an empty peer address.
+ Assert.equal(connection.host, "");
+ Assert.equal(connection.port, 0);
+ let connectionPeerAddr = connection.getScriptablePeerAddr();
+ Assert.equal(connectionPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ Assert.equal(connectionPeerAddr.address, "");
+
+ let serverAsyncInput = connection
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ let serverOutput = connection.openOutputStream(0, 0, 0);
+
+ serverAsyncInput.asyncWait(
+ function (aStream) {
+ info("called test_echo's server's onInputStreamReady");
+ let serverScriptableInput = new ScriptableInputStream(aStream);
+
+ // Receive data from the client, and send back a response.
+ Assert.equal(
+ serverScriptableInput.readBytes(17),
+ "Mervyn Murgatroyd"
+ );
+ info("server has read message from client");
+ serverOutput.write("Ruthven Murgatroyd", 18);
+ info("server has written to client");
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+ },
+
+ onStopListening(aServ, aStatus) {
+ info("called test_echo's onStopListening");
+ log += "s";
+
+ Assert.equal(aServ, server);
+ Assert.equal(log, "acs");
+
+ run_next_test();
+ },
+ });
+
+ // Create a client socket, and connect to the server.
+ let client = socketTransportService.createUnixDomainTransport(socketName);
+ Assert.equal(client.host, socketName.path);
+ Assert.equal(client.port, 0);
+
+ let clientAsyncInput = client
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ let clientInput = new ScriptableInputStream(clientAsyncInput);
+ let clientOutput = client.openOutputStream(0, 0, 0);
+
+ clientOutput.write("Mervyn Murgatroyd", 17);
+ info("client has written to server");
+
+ clientAsyncInput.asyncWait(
+ function (aStream) {
+ info("called test_echo's client's onInputStreamReady");
+ log += "c";
+
+ Assert.equal(aStream, clientAsyncInput);
+
+ // Now that the connection has been established, we can check the
+ // transport's self and peer addresses.
+ let clientSelfAddr = client.getScriptableSelfAddr();
+ Assert.equal(clientSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ Assert.equal(clientSelfAddr.address, "");
+
+ Assert.equal(client.host, socketName.path); // re-check, but hey
+ let clientPeerAddr = client.getScriptablePeerAddr();
+ Assert.equal(clientPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ Assert.equal(clientPeerAddr.address, socketName.path);
+
+ Assert.equal(clientInput.readBytes(18), "Ruthven Murgatroyd");
+ info("client has read message from server");
+
+ server.close();
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+}
+
+// Create client and server sockets using a path that's too long.
+function test_name_too_long() {
+ let socketName = do_get_tempdir();
+ // The length limits on all the systems NSPR supports are a bit past 100.
+ socketName.append(new Array(1000).join("x"));
+
+ // The length must be checked before we ever make any system calls --- we
+ // have to create the sockaddr first --- so it's unambiguous which error
+ // we should get here.
+
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, 0, -1),
+ "NS_ERROR_FILE_NAME_TOO_LONG"
+ );
+
+ // Unlike most other client socket errors, this one gets reported
+ // immediately, as we can't even initialize the sockaddr with the given
+ // name.
+ do_check_throws_nsIException(
+ () => socketTransportService.createUnixDomainTransport(socketName),
+ "NS_ERROR_FILE_NAME_TOO_LONG"
+ );
+
+ run_next_test();
+}
+
+// Try creating a socket in a directory that doesn't exist.
+function test_no_directory() {
+ let socketName = do_get_tempdir();
+ socketName.append("missing");
+ socketName.append("socket");
+
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, 0, -1),
+ "NS_ERROR_FILE_NOT_FOUND"
+ );
+
+ run_next_test();
+}
+
+// Try connecting to a server socket that isn't there.
+function test_no_such_socket() {
+ let socketName = do_get_tempdir();
+ socketName.append("nonexistent-socket");
+
+ let client = socketTransportService.createUnixDomainTransport(socketName);
+ let clientAsyncInput = client
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ clientAsyncInput.asyncWait(
+ function (aStream) {
+ info("called test_no_such_socket's onInputStreamReady");
+
+ Assert.equal(aStream, clientAsyncInput);
+
+ // nsISocketTransport puts off actually creating sockets as long as
+ // possible, so the error in connecting doesn't actually show up until
+ // this point.
+ do_check_throws_nsIException(
+ () => clientAsyncInput.available(),
+ "NS_ERROR_FILE_NOT_FOUND"
+ );
+
+ clientAsyncInput.close();
+ client.close(Cr.NS_OK);
+
+ run_next_test();
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+}
+
+// Creating a socket with a name that another socket is already using is an
+// error.
+function test_address_in_use() {
+ let socketName = do_get_tempdir();
+ socketName.append("socket-in-use");
+
+ // Create one server socket.
+ new UnixServerSocket(socketName, allPermissions, -1);
+
+ // Now try to create another with the same name.
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_SOCKET_ADDRESS_IN_USE"
+ );
+
+ run_next_test();
+}
+
+// Creating a socket with a name that is already a file is an error.
+function test_file_in_way() {
+ let socketName = do_get_tempdir();
+ socketName.append("file_in_way");
+
+ // Create a file with the given name.
+ socketName.create(Ci.nsIFile.NORMAL_FILE_TYPE, allPermissions);
+
+ // Try to create a socket with the same name.
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_SOCKET_ADDRESS_IN_USE"
+ );
+
+ // Try to create a socket under a name that uses that as a parent directory.
+ socketName.append("socket");
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, 0, -1),
+ "NS_ERROR_FILE_NOT_DIRECTORY"
+ );
+
+ run_next_test();
+}
+
+// It is not permitted to create a socket in a directory which we are not
+// permitted to execute, or create files in.
+function test_create_permission() {
+ let dirName = do_get_tempdir();
+ dirName.append("unfriendly");
+
+ let socketName = dirName.clone();
+ socketName.append("socket");
+
+ // The test harness has difficulty cleaning things up if we don't make
+ // everything writable before we're done.
+ try {
+ // Create a directory which we are not permitted to search.
+ dirName.create(Ci.nsIFile.DIRECTORY_TYPE, 0);
+
+ // Try to create a socket in that directory. Because Linux returns EACCES
+ // when a 'connect' fails because of a local firewall rule,
+ // nsIServerSocket returns NS_ERROR_CONNECTION_REFUSED in this case.
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_CONNECTION_REFUSED"
+ );
+
+ // Grant read and execute permission, but not write permission on the directory.
+ dirName.permissions = parseInt("0555", 8);
+
+ // This should also fail; we need write permission.
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_CONNECTION_REFUSED"
+ );
+ } finally {
+ // Make the directory writable, so the test harness can clean it up.
+ dirName.permissions = allPermissions;
+ }
+
+ // This should succeed, since we now have all the permissions on the
+ // directory we could want.
+ do_check_instanceof(
+ new UnixServerSocket(socketName, allPermissions, -1),
+ Ci.nsIServerSocket
+ );
+
+ run_next_test();
+}
+
+// To connect to a Unix domain socket, we need search permission on the
+// directories containing it, and some kind of permission or other on the
+// socket itself.
+function test_connect_permission() {
+ // This test involves a lot of callbacks, but they're written out so that
+ // the actual control flow proceeds from top to bottom.
+ let log = "";
+
+ // Create a directory which we are permitted to search - at first.
+ let dirName = do_get_tempdir();
+ dirName.append("inhospitable");
+ dirName.create(Ci.nsIFile.DIRECTORY_TYPE, allPermissions);
+
+ let socketName = dirName.clone();
+ socketName.append("socket");
+
+ // Create a server socket in that directory, listening for connections,
+ // and accessible.
+ let server = new UnixServerSocket(socketName, allPermissions, -1);
+ server.asyncListen({
+ onSocketAccepted: socketAccepted,
+ onStopListening: stopListening,
+ });
+
+ // Make the directory unsearchable.
+ dirName.permissions = 0;
+
+ let client3;
+
+ let client1 = socketTransportService.createUnixDomainTransport(socketName);
+ let client1AsyncInput = client1
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ client1AsyncInput.asyncWait(
+ function (aStream) {
+ info("called test_connect_permission's client1's onInputStreamReady");
+ log += "1";
+
+ // nsISocketTransport puts off actually creating sockets as long as
+ // possible, so the error doesn't actually show up until this point.
+ do_check_throws_nsIException(
+ () => client1AsyncInput.available(),
+ "NS_ERROR_CONNECTION_REFUSED"
+ );
+
+ client1AsyncInput.close();
+ client1.close(Cr.NS_OK);
+
+ // Make the directory searchable, but make the socket inaccessible.
+ dirName.permissions = allPermissions;
+ socketName.permissions = 0;
+
+ let client2 =
+ socketTransportService.createUnixDomainTransport(socketName);
+ let client2AsyncInput = client2
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ client2AsyncInput.asyncWait(
+ function (aStream) {
+ info("called test_connect_permission's client2's onInputStreamReady");
+ log += "2";
+
+ do_check_throws_nsIException(
+ () => client2AsyncInput.available(),
+ "NS_ERROR_CONNECTION_REFUSED"
+ );
+
+ client2AsyncInput.close();
+ client2.close(Cr.NS_OK);
+
+ // Now make everything accessible, and try one last time.
+ socketName.permissions = allPermissions;
+
+ client3 =
+ socketTransportService.createUnixDomainTransport(socketName);
+
+ let client3Output = client3.openOutputStream(0, 0, 0);
+ client3Output.write("Hanratty", 8);
+
+ let client3AsyncInput = client3
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ client3AsyncInput.asyncWait(
+ client3InputStreamReady,
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+
+ function socketAccepted(aServ, aTransport) {
+ info("called test_connect_permission's onSocketAccepted");
+ log += "a";
+
+ let serverInput = aTransport
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ let serverOutput = aTransport.openOutputStream(0, 0, 0);
+
+ serverInput.asyncWait(
+ function (aStream) {
+ info(
+ "called test_connect_permission's socketAccepted's onInputStreamReady"
+ );
+ log += "i";
+
+ // Receive data from the client, and send back a response.
+ let serverScriptableInput = new ScriptableInputStream(serverInput);
+ Assert.equal(serverScriptableInput.readBytes(8), "Hanratty");
+ serverOutput.write("Ferlingatti", 11);
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+ }
+
+ function client3InputStreamReady(aStream) {
+ info("called client3's onInputStreamReady");
+ log += "3";
+
+ let client3Input = new ScriptableInputStream(aStream);
+
+ Assert.equal(client3Input.readBytes(11), "Ferlingatti");
+
+ client3.close(Cr.NS_OK);
+ server.close();
+ }
+
+ function stopListening(aServ, aStatus) {
+ info("called test_connect_permission's server's stopListening");
+ log += "s";
+
+ Assert.equal(log, "12ai3s");
+
+ run_next_test();
+ }
+}
+
+// Creating a socket with a long filename doesn't crash.
+function test_long_socket_name() {
+ let socketName = do_get_tempdir();
+ socketName.append(new Array(10000).join("long"));
+
+ // Try to create a server socket with the long name.
+ do_check_throws_nsIException(
+ () => new UnixServerSocket(socketName, allPermissions, -1),
+ "NS_ERROR_FILE_NAME_TOO_LONG"
+ );
+
+ // Try to connect to a socket with the long name.
+ do_check_throws_nsIException(
+ () => socketTransportService.createUnixDomainTransport(socketName),
+ "NS_ERROR_FILE_NAME_TOO_LONG"
+ );
+
+ run_next_test();
+}
+
+// Going offline should not shut down Unix domain sockets.
+function test_keep_when_offline() {
+ let log = "";
+
+ let socketName = do_get_tempdir();
+ socketName.append("keep-when-offline");
+
+ // Create a listening socket.
+ let listener = new UnixServerSocket(socketName, allPermissions, -1);
+ listener.asyncListen({ onSocketAccepted: onAccepted, onStopListening });
+
+ // Connect a client socket to the listening socket.
+ let client = socketTransportService.createUnixDomainTransport(socketName);
+ let clientOutput = client.openOutputStream(0, 0, 0);
+ let clientInput = client.openInputStream(0, 0, 0);
+ clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread);
+ let clientScriptableInput = new ScriptableInputStream(clientInput);
+
+ let server, serverInput, serverScriptableInput, serverOutput;
+
+ // How many times has the server invited the client to go first?
+ let count = 0;
+
+ // The server accepted connection callback.
+ function onAccepted(aListener, aServer) {
+ info("test_keep_when_offline: onAccepted called");
+ log += "a";
+ Assert.equal(aListener, listener);
+ server = aServer;
+
+ // Prepare to receive messages from the client.
+ serverInput = server.openInputStream(0, 0, 0);
+ serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread);
+ serverScriptableInput = new ScriptableInputStream(serverInput);
+
+ // Start a conversation with the client.
+ serverOutput = server.openOutputStream(0, 0, 0);
+ serverOutput.write("After you, Alphonse!", 20);
+ count++;
+ }
+
+ // The client has seen its end of the socket close.
+ function clientReady(aStream) {
+ log += "c";
+ info("test_keep_when_offline: clientReady called: " + log);
+ Assert.equal(aStream, clientInput);
+
+ // If the connection has been closed, end the conversation and stop listening.
+ let available;
+ try {
+ available = clientInput.available();
+ } catch (ex) {
+ do_check_instanceof(ex, Ci.nsIException);
+ Assert.equal(ex.result, Cr.NS_BASE_STREAM_CLOSED);
+
+ info("client received end-of-stream; closing client output stream");
+ log += ")";
+
+ client.close(Cr.NS_OK);
+
+ // Now both output streams have been closed, and both input streams
+ // have received the close notification. Stop listening for
+ // connections.
+ listener.close();
+ }
+
+ if (available) {
+ // Check the message from the server.
+ Assert.equal(clientScriptableInput.readBytes(20), "After you, Alphonse!");
+
+ // Write our response to the server.
+ clientOutput.write("No, after you, Gaston!", 22);
+
+ // Ask to be called again, when more input arrives.
+ clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread);
+ }
+ }
+
+ function serverReady(aStream) {
+ log += "s";
+ info("test_keep_when_offline: serverReady called: " + log);
+ Assert.equal(aStream, serverInput);
+
+ // Check the message from the client.
+ Assert.equal(serverScriptableInput.readBytes(22), "No, after you, Gaston!");
+
+ // This should not shut things down: Unix domain sockets should
+ // remain open in offline mode.
+ if (count == 5) {
+ Services.io.offline = true;
+ log += "o";
+ }
+
+ if (count < 10) {
+ // Insist.
+ serverOutput.write("After you, Alphonse!", 20);
+ count++;
+
+ // As long as the input stream is open, always ask to be called again
+ // when more input arrives.
+ serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread);
+ } else if (count == 10) {
+ // After sending ten times and receiving ten replies, we're not
+ // going to send any more. Close the server's output stream; the
+ // client's input stream should see this.
+ info("closing server transport");
+ server.close(Cr.NS_OK);
+ log += "(";
+ }
+ }
+
+ // We have stopped listening.
+ function onStopListening(aServ, aStatus) {
+ info("test_keep_when_offline: onStopListening called");
+ log += "L";
+ Assert.equal(log, "acscscscscsocscscscscs(c)L");
+
+ Assert.equal(aServ, listener);
+ Assert.equal(aStatus, Cr.NS_BINDING_ABORTED);
+
+ run_next_test();
+ }
+}
+
+function test_abstract_address_socket() {
+ const socketname = "abstractsocket";
+ let server = new UnixAbstractServerSocket(socketname, -1);
+ server.asyncListen({
+ onSocketAccepted: (aServ, aTransport) => {
+ let serverInput = aTransport
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ let serverOutput = aTransport.openOutputStream(0, 0, 0);
+
+ serverInput.asyncWait(
+ aStream => {
+ info(
+ "called test_abstract_address_socket's onSocketAccepted's onInputStreamReady"
+ );
+
+ // Receive data from the client, and send back a response.
+ let serverScriptableInput = new ScriptableInputStream(serverInput);
+ Assert.equal(serverScriptableInput.readBytes(9), "ping ping");
+ serverOutput.write("pong", 4);
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+ },
+ onStopListening: (aServ, aTransport) => {},
+ });
+
+ let client =
+ socketTransportService.createUnixDomainAbstractAddressTransport(socketname);
+ Assert.equal(client.host, socketname);
+ Assert.equal(client.port, 0);
+ let clientInput = client
+ .openInputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ let clientOutput = client.openOutputStream(0, 0, 0);
+
+ clientOutput.write("ping ping", 9);
+
+ clientInput.asyncWait(
+ aStream => {
+ let clientScriptInput = new ScriptableInputStream(clientInput);
+ let available = clientScriptInput.available();
+ if (available) {
+ Assert.equal(clientScriptInput.readBytes(4), "pong");
+
+ client.close(Cr.NS_OK);
+ server.close(Cr.NS_OK);
+
+ run_next_test();
+ }
+ },
+ 0,
+ 0,
+ threadManager.currentThread
+ );
+}