Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions core/src/avm2/globals/flash/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod net_connection;
pub mod net_stream;
pub mod object_encoding;
pub mod responder;
pub mod secure_socket;
pub mod shared_object;
pub mod socket;
pub mod url_loader;
Expand Down
29 changes: 29 additions & 0 deletions core/src/avm2/globals/flash/net/SecureSocket.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package flash.net {
import __ruffle__.stub_method;
import __ruffle__.stub_getter;

import flash.utils.ByteArray;

[API("662")]
public class SecureSocket extends Socket {
public function SecureSocket() {
super();
this.timeout = 20000;
}

public static native function get isSupported():Boolean;

public native function get serverCertificateStatus():String;

public function get serverCertificate():* {
stub_getter("flash.net.SecureSocket", "serverCertificate");
return null;
}

public function addBinaryChainBuildingCertificate(certificate:ByteArray, trusted:Boolean):void {
stub_method("flash.net.SecureSocket", "addBinaryChainBuildingCertificate");
}

public override native function connect(host:String, port:int):void;
}
}
59 changes: 59 additions & 0 deletions core/src/avm2/globals/flash/net/secure_socket.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use crate::avm2::error::make_error_2003;
use crate::avm2::parameters::ParametersExt;
use crate::avm2::string::AvmString;
use crate::avm2::{Activation, Error, Value};
use crate::context::UpdateContext;

/// Implements `SecureSocket.connect`
pub fn connect<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Value<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let this = this.as_object().unwrap();

let socket = match this.as_socket() {
Some(socket) => socket,
None => return Ok(Value::Undefined),

Check warning on line 17 in core/src/avm2/globals/flash/net/secure_socket.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (17)
};

let host = args.get_string(activation, 0);
let port = u16::try_from(args.get_i32(1))
.ok()
.filter(|&p| p >= 1)
.ok_or_else(|| make_error_2003(activation))?;

let UpdateContext {
sockets, navigator, ..
} = activation.context;

sockets.connect_avm2_secure(*navigator, socket, host.to_utf8_lossy().into_owned(), port);

Ok(Value::Undefined)
}

/// Implements `SecureSocket.isSupported`
pub fn get_is_supported<'gc>(
_activation: &mut Activation<'_, 'gc>,
_this: Value<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
// SecureSocket is supported in Ruffle (we use native TLS).
Ok(true.into())
}

/// Implements `SecureSocket.serverCertificateStatus`
pub fn get_server_certificate_status<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Value<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let this = this.as_object().unwrap();

if let Some(socket) = this.as_socket() {
let status = socket.certificate_status();
return Ok(AvmString::new_utf8(activation.gc(), &status).into());
}

Check warning on line 56 in core/src/avm2/globals/flash/net/secure_socket.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (56)

Ok(Value::Undefined)

Check warning on line 58 in core/src/avm2/globals/flash/net/secure_socket.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (58)
}
1 change: 1 addition & 0 deletions core/src/avm2/globals/globals.as
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ include "flash/net/Responder.as"
include "flash/net/SharedObject.as"
include "flash/net/SharedObjectFlushStatus.as"
include "flash/net/Socket.as"
include "flash/net/SecureSocket.as"
include "flash/net/URLLoader.as"
include "flash/net/URLLoaderDataFormat.as"
include "flash/net/URLRequest.as"
Expand Down
13 changes: 13 additions & 0 deletions core/src/avm2/object/socket_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
handle: Cell::new(None),
read_buffer: RefCell::new(VecDeque::new()),
write_buffer: RefCell::new(vec![]),
certificate_status: RefCell::new("unknown".to_string()),
},
))
.into())
Expand Down Expand Up @@ -83,6 +84,14 @@
self.0.handle.replace(Some(handle))
}

pub fn certificate_status(self) -> String {
self.0.certificate_status.borrow().clone()
}

pub fn set_certificate_status(self, status: String) {
*self.0.certificate_status.borrow_mut() = status;
}

Check warning on line 93 in core/src/avm2/object/socket_object.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (91–93)

pub fn read_buffer(&self) -> RefMut<'_, VecDeque<u8>> {
self.0.read_buffer.borrow_mut()
}
Expand Down Expand Up @@ -202,6 +211,10 @@

read_buffer: RefCell<VecDeque<u8>>,
write_buffer: RefCell<Vec<u8>>,

/// The server certificate status (used by SecureSocket).
#[collect(require_static)]
certificate_status: RefCell<String>,
}

impl fmt::Debug for SocketObject<'_> {
Expand Down
16 changes: 16 additions & 0 deletions core/src/backend/navigator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,22 @@ pub trait NavigatorBackend: Any {
receiver: Receiver<Vec<u8>>,
sender: Sender<SocketAction>,
);

/// Handle a secure (TLS) Socket connection request.
///
/// This is used by SecureSocket. By default it delegates to [connect_socket].
/// Backends that support TLS should override this to establish a TLS connection.
fn connect_secure_socket(
&mut self,
host: String,
port: u16,
timeout: Duration,
handle: SocketHandle,
receiver: Receiver<Vec<u8>>,
sender: Sender<SocketAction>,
) {
self.connect_socket(host, port, timeout, handle, receiver, sender);
}
}

#[cfg(not(target_family = "wasm"))]
Expand Down
39 changes: 39 additions & 0 deletions core/src/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
Connect(SocketHandle, ConnectionState),
Data(SocketHandle, Vec<u8>),
Close(SocketHandle),
/// Sets the TLS certificate status string on the target socket.
/// Must be sent before the corresponding Connect action.
CertificateStatus(SocketHandle, String),
}

/// Manages the collection of Sockets.
Expand Down Expand Up @@ -112,6 +115,35 @@
}
}

pub fn connect_avm2_secure(
&mut self,
backend: &mut dyn NavigatorBackend,
target: SocketObject<'gc>,
host: String,
port: u16,
) {
let (sender, receiver) = unbounded();

let socket = Socket::new(SocketKind::Avm2(target), sender);
let handle = self.sockets.insert(socket);

// NOTE: This call will send SocketAction::Connect to sender with connection status.
backend.connect_secure_socket(
sanitize_host(&host).to_string(),
port,
Duration::from_millis(target.timeout().into()),
handle,
receiver,
self.sender.clone(),
);

if let Some(existing_handle) = target.set_handle(handle) {
// As written in the AS3 docs, we are supposed to close the existing connection,
// when a new one is created.
self.close(existing_handle)

Check warning on line 143 in core/src/socket.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (143)
}
}

pub fn connect_avm1(
&mut self,
backend: &mut dyn NavigatorBackend,
Expand Down Expand Up @@ -353,6 +385,13 @@
}
}
}
SocketAction::CertificateStatus(handle, status) => {
if let Some(socket) = context.sockets.sockets.get(handle)
&& let SocketKind::Avm2(target) = socket.target
{
target.set_certificate_status(status);
}

Check warning on line 393 in core/src/socket.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (388–393)
}
SocketAction::Close(handle) => {
let target = match context.sockets.sockets.remove(handle) {
Some(socket) => {
Expand Down
10 changes: 9 additions & 1 deletion frontend-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ workspace = true
[features]
cpal = ["dep:cpal", "dep:bytemuck", "ruffle_core/audio"]
fs = []
navigator = ["fs", "dep:async-io", "dep:tokio"]
navigator = ["fs", "dep:async-io", "dep:tokio", "dep:rustls", "dep:tokio-rustls", "dep:rustls-native-certs", "dep:rustls-pki-types"]
rustls = ["dep:rustls"]
tokio-rustls = ["dep:tokio-rustls"]
rustls-native-certs = ["dep:rustls-native-certs"]
rustls-pki-types = ["dep:rustls-pki-types"]

[dependencies]
toml_edit = { version = "0.23.6", features = ["parse"] }
Expand All @@ -38,6 +42,10 @@ reqwest = { version = "0.12.28", default-features = false, features = [
tokio = { workspace = true, features = ["net", "macros"], optional = true }
cpal = { workspace = true, optional = true }
bytemuck = { workspace = true, optional = true }
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "logging", "tls12"], optional = true }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "logging", "tls12"], optional = true }
rustls-native-certs = { version = "0.8", optional = true }
rustls-pki-types = { version = "1.0", optional = true }

[dev-dependencies]
tempfile = { workspace = true }
Expand Down
Loading
Loading