mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Align socket and socketserver with Python 3.15 (#9156)
Import the canonical socket libraries and tests through update_lib, preserving the existing AF_ALG expected failures. Match setsockopt index/buffer conversion and error ordering, mask optlen with the final CPython deprecation warning, reject oversized Windows buffers before the host call, and preserve arbitrary-precision getaddrinfo ports and empty canonical names. Assisted-by: OpenAI Codex:model version unavailable
This commit is contained in:
1 parent
1ee9a29062
commit
fd5b30eb5e
5 files changed
+318
-102
No files matched your search
Vendored
+79
-70
@@ -56,6 +56,7 @@ import io
|
||||
import os
|
||||
import sys
|
||||
from enum import IntEnum, IntFlag
|
||||
from functools import partial
|
||||
|
||||
try:
|
||||
import errno
|
||||
@@ -122,7 +123,7 @@ if sys.platform.lower().startswith("win"):
|
||||
10004: "The operation was interrupted.",
|
||||
10009: "A bad file handle was passed.",
|
||||
10013: "Permission denied.",
|
||||
10014: "A fault occurred on the network??",
|
||||
10014: "An invalid pointer was passed.",
|
||||
10022: "An invalid operation was attempted.",
|
||||
10024: "Too many open files.",
|
||||
10035: "The socket operation would block.",
|
||||
@@ -180,7 +181,7 @@ if sys.platform.lower().startswith("win"):
|
||||
11001: "Host not found.",
|
||||
11002: "Nonauthoritative host not found.",
|
||||
11003: "This is a nonrecoverable error.",
|
||||
11004: "Valid name, no data record requested type.",
|
||||
11004: "Valid name, no data record of requested type.",
|
||||
11005: "QoS receivers.",
|
||||
11006: "QoS senders.",
|
||||
11007: "No QoS senders.",
|
||||
@@ -196,7 +197,7 @@ if sys.platform.lower().startswith("win"):
|
||||
11017: "QoS flowspec error.",
|
||||
11018: "Invalid QoS provider buffer.",
|
||||
11019: "Invalid QoS filter style.",
|
||||
11020: "Invalid QoS filter style.",
|
||||
11020: "Invalid QoS filter type.",
|
||||
11021: "Incorrect QoS filter count.",
|
||||
11022: "Invalid QoS object length.",
|
||||
11023: "Incorrect QoS flow count.",
|
||||
@@ -348,75 +349,83 @@ class socket(_socket.socket):
|
||||
text.mode = mode
|
||||
return text
|
||||
|
||||
def _sendfile_zerocopy(self, zerocopy_func, giveup_exc_type, file,
|
||||
offset=0, count=None):
|
||||
"""
|
||||
Send a file using a zero-copy function.
|
||||
"""
|
||||
import selectors
|
||||
|
||||
self._check_sendfile_params(file, offset, count)
|
||||
sockno = self.fileno()
|
||||
try:
|
||||
fileno = file.fileno()
|
||||
except (AttributeError, io.UnsupportedOperation) as err:
|
||||
raise giveup_exc_type(err) # not a regular file
|
||||
try:
|
||||
fsize = os.fstat(fileno).st_size
|
||||
except OSError as err:
|
||||
raise giveup_exc_type(err) # not a regular file
|
||||
if not fsize:
|
||||
return 0 # empty file
|
||||
# Truncate to 1GiB to avoid OverflowError, see bpo-38319.
|
||||
blocksize = min(count or fsize, 2 ** 30)
|
||||
timeout = self.gettimeout()
|
||||
if timeout == 0:
|
||||
raise ValueError("non-blocking sockets are not supported")
|
||||
# poll/select have the advantage of not requiring any
|
||||
# extra file descriptor, contrarily to epoll/kqueue
|
||||
# (also, they require a single syscall).
|
||||
if hasattr(selectors, 'PollSelector'):
|
||||
selector = selectors.PollSelector()
|
||||
else:
|
||||
selector = selectors.SelectSelector()
|
||||
selector.register(sockno, selectors.EVENT_WRITE)
|
||||
|
||||
total_sent = 0
|
||||
# localize variable access to minimize overhead
|
||||
selector_select = selector.select
|
||||
try:
|
||||
while True:
|
||||
if timeout and not selector_select(timeout):
|
||||
raise TimeoutError('timed out')
|
||||
if count:
|
||||
blocksize = min(count - total_sent, blocksize)
|
||||
if blocksize <= 0:
|
||||
break
|
||||
try:
|
||||
sent = zerocopy_func(fileno, offset, blocksize)
|
||||
except BlockingIOError:
|
||||
if not timeout:
|
||||
# Block until the socket is ready to send some
|
||||
# data; avoids hogging CPU resources.
|
||||
selector_select()
|
||||
continue
|
||||
except OSError as err:
|
||||
if total_sent == 0:
|
||||
# We can get here for different reasons, the main
|
||||
# one being 'file' is not a regular mmap(2)-like
|
||||
# file, in which case we'll fall back on using
|
||||
# plain send().
|
||||
raise giveup_exc_type(err)
|
||||
raise err from None
|
||||
else:
|
||||
if sent == 0:
|
||||
break # EOF
|
||||
offset += sent
|
||||
total_sent += sent
|
||||
return total_sent
|
||||
finally:
|
||||
if total_sent > 0 and hasattr(file, 'seek'):
|
||||
file.seek(offset)
|
||||
|
||||
if hasattr(os, 'sendfile'):
|
||||
|
||||
def _sendfile_use_sendfile(self, file, offset=0, count=None):
|
||||
# Lazy import to improve module import time
|
||||
import selectors
|
||||
|
||||
self._check_sendfile_params(file, offset, count)
|
||||
sockno = self.fileno()
|
||||
try:
|
||||
fileno = file.fileno()
|
||||
except (AttributeError, io.UnsupportedOperation) as err:
|
||||
raise _GiveupOnSendfile(err) # not a regular file
|
||||
try:
|
||||
fsize = os.fstat(fileno).st_size
|
||||
except OSError as err:
|
||||
raise _GiveupOnSendfile(err) # not a regular file
|
||||
if not fsize:
|
||||
return 0 # empty file
|
||||
# Truncate to 1GiB to avoid OverflowError, see bpo-38319.
|
||||
blocksize = min(count or fsize, 2 ** 30)
|
||||
timeout = self.gettimeout()
|
||||
if timeout == 0:
|
||||
raise ValueError("non-blocking sockets are not supported")
|
||||
# poll/select have the advantage of not requiring any
|
||||
# extra file descriptor, contrarily to epoll/kqueue
|
||||
# (also, they require a single syscall).
|
||||
if hasattr(selectors, 'PollSelector'):
|
||||
selector = selectors.PollSelector()
|
||||
else:
|
||||
selector = selectors.SelectSelector()
|
||||
selector.register(sockno, selectors.EVENT_WRITE)
|
||||
|
||||
total_sent = 0
|
||||
# localize variable access to minimize overhead
|
||||
selector_select = selector.select
|
||||
os_sendfile = os.sendfile
|
||||
try:
|
||||
while True:
|
||||
if timeout and not selector_select(timeout):
|
||||
raise TimeoutError('timed out')
|
||||
if count:
|
||||
blocksize = min(count - total_sent, blocksize)
|
||||
if blocksize <= 0:
|
||||
break
|
||||
try:
|
||||
sent = os_sendfile(sockno, fileno, offset, blocksize)
|
||||
except BlockingIOError:
|
||||
if not timeout:
|
||||
# Block until the socket is ready to send some
|
||||
# data; avoids hogging CPU resources.
|
||||
selector_select()
|
||||
continue
|
||||
except OSError as err:
|
||||
if total_sent == 0:
|
||||
# We can get here for different reasons, the main
|
||||
# one being 'file' is not a regular mmap(2)-like
|
||||
# file, in which case we'll fall back on using
|
||||
# plain send().
|
||||
raise _GiveupOnSendfile(err)
|
||||
raise err from None
|
||||
else:
|
||||
if sent == 0:
|
||||
break # EOF
|
||||
offset += sent
|
||||
total_sent += sent
|
||||
return total_sent
|
||||
finally:
|
||||
if total_sent > 0 and hasattr(file, 'seek'):
|
||||
file.seek(offset)
|
||||
return self._sendfile_zerocopy(
|
||||
partial(os.sendfile, self.fileno()),
|
||||
_GiveupOnSendfile,
|
||||
file, offset, count,
|
||||
)
|
||||
else:
|
||||
def _sendfile_use_sendfile(self, file, offset=0, count=None):
|
||||
raise _GiveupOnSendfile(
|
||||
|
||||
Vendored
+10
-4
@@ -120,9 +120,6 @@ BaseServer:
|
||||
|
||||
# Author of the BaseServer patch: Luke Kenneth Casson Leighton
|
||||
|
||||
__version__ = "0.4"
|
||||
|
||||
|
||||
import socket
|
||||
import selectors
|
||||
import os
|
||||
@@ -441,7 +438,7 @@ class TCPServer(BaseServer):
|
||||
|
||||
socket_type = socket.SOCK_STREAM
|
||||
|
||||
request_queue_size = 5
|
||||
request_queue_size = getattr(socket, "SOMAXCONN", 5)
|
||||
|
||||
allow_reuse_address = False
|
||||
|
||||
@@ -861,3 +858,12 @@ class DatagramRequestHandler(BaseRequestHandler):
|
||||
|
||||
def finish(self):
|
||||
self.socket.sendto(self.wfile.getvalue(), self.client_address)
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "__version__":
|
||||
from warnings import _deprecated
|
||||
|
||||
_deprecated("__version__", remove=(3, 20))
|
||||
return "0.4" # Do not change
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
Vendored
+88
-3
@@ -8,7 +8,9 @@ from test.support.import_helper import ensure_lazy_imports
|
||||
import _thread as thread
|
||||
import array
|
||||
import contextlib
|
||||
import decimal
|
||||
import errno
|
||||
import fractions
|
||||
import gc
|
||||
import io
|
||||
import itertools
|
||||
@@ -1349,10 +1351,20 @@ class GeneralModuleTests(unittest.TestCase):
|
||||
self.assertEqual(s.gettimeout(), None)
|
||||
|
||||
# Set the default timeout to 10, and see if it propagates
|
||||
with socket_setdefaulttimeout(10):
|
||||
self.assertEqual(socket.getdefaulttimeout(), 10)
|
||||
with socket_setdefaulttimeout(10.125):
|
||||
self.assertEqual(socket.getdefaulttimeout(), 10.125)
|
||||
with socket.socket() as sock:
|
||||
self.assertEqual(sock.gettimeout(), 10)
|
||||
self.assertEqual(sock.gettimeout(), 10.125)
|
||||
|
||||
socket.setdefaulttimeout(decimal.Decimal('11.125'))
|
||||
self.assertEqual(socket.getdefaulttimeout(), 11.125)
|
||||
with socket.socket() as sock:
|
||||
self.assertEqual(sock.gettimeout(), 11.125)
|
||||
|
||||
socket.setdefaulttimeout(fractions.Fraction(97, 8))
|
||||
self.assertEqual(socket.getdefaulttimeout(), 12.125)
|
||||
with socket.socket() as sock:
|
||||
self.assertEqual(sock.gettimeout(), 12.125)
|
||||
|
||||
# Reset the default timeout to None, and see if it propagates
|
||||
socket.setdefaulttimeout(None)
|
||||
@@ -1580,6 +1592,40 @@ class GeneralModuleTests(unittest.TestCase):
|
||||
reuse = sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)
|
||||
self.assertFalse(reuse == 0, "failed to set reuse mode")
|
||||
|
||||
def test_setsockopt_errors(self):
|
||||
# See issue #107546.
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.addCleanup(sock.close)
|
||||
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # No error expected.
|
||||
|
||||
with self.assertRaises(OverflowError):
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 2 ** 100)
|
||||
|
||||
with self.assertRaises(OverflowError):
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, - 2 ** 100)
|
||||
|
||||
with self.assertRaises(OverflowError):
|
||||
sock.setsockopt(socket.SOL_SOCKET, 2 ** 100, 1)
|
||||
|
||||
with self.assertRaises(OverflowError):
|
||||
sock.setsockopt(2 ** 100, socket.SO_REUSEADDR, 1)
|
||||
|
||||
with self.assertRaisesRegex(TypeError, "socket option should be int, bytes-like object or None"):
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, dict())
|
||||
|
||||
with self.assertRaisesRegex(TypeError, "requires 4 arguments when the third argument is None"):
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, None)
|
||||
|
||||
with self.assertRaisesRegex(TypeError, "only takes 4 arguments when the third argument is None"):
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1, 2)
|
||||
|
||||
with self.assertRaisesRegex(TypeError, "takes at least 3 arguments"):
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)
|
||||
|
||||
with self.assertRaisesRegex(TypeError, "takes at most 4 arguments"):
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1, 2, 3)
|
||||
|
||||
def testSendAfterClose(self):
|
||||
# testing send() after close() with timeout
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
@@ -2435,6 +2481,45 @@ class ISOTPTest(unittest.TestCase):
|
||||
socket.CAN_ISOTP
|
||||
socket.SOCK_DGRAM
|
||||
|
||||
@unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"),
|
||||
"missing <linux/can/isotp.h>")
|
||||
def testISOTP(self):
|
||||
socket.SOL_CAN_ISOTP
|
||||
|
||||
socket.CAN_ISOTP_OPTS
|
||||
socket.CAN_ISOTP_RECV_FC
|
||||
|
||||
socket.CAN_ISOTP_TX_STMIN
|
||||
socket.CAN_ISOTP_RX_STMIN
|
||||
socket.CAN_ISOTP_LL_OPTS
|
||||
|
||||
socket.CAN_ISOTP_LISTEN_MODE
|
||||
socket.CAN_ISOTP_EXTEND_ADDR
|
||||
socket.CAN_ISOTP_TX_PADDING
|
||||
socket.CAN_ISOTP_RX_PADDING
|
||||
socket.CAN_ISOTP_CHK_PAD_LEN
|
||||
socket.CAN_ISOTP_CHK_PAD_DATA
|
||||
socket.CAN_ISOTP_HALF_DUPLEX
|
||||
socket.CAN_ISOTP_FORCE_TXSTMIN
|
||||
socket.CAN_ISOTP_FORCE_RXSTMIN
|
||||
socket.CAN_ISOTP_RX_EXT_ADDR
|
||||
socket.CAN_ISOTP_WAIT_TX_DONE
|
||||
# This constant is not always available
|
||||
# socket.CAN_ISOTP_SF_BROADCAST
|
||||
|
||||
socket.CAN_ISOTP_DEFAULT_FLAGS
|
||||
socket.CAN_ISOTP_DEFAULT_EXT_ADDRESS
|
||||
socket.CAN_ISOTP_DEFAULT_PAD_CONTENT
|
||||
socket.CAN_ISOTP_DEFAULT_FRAME_TXTIME
|
||||
socket.CAN_ISOTP_DEFAULT_RECV_BS
|
||||
socket.CAN_ISOTP_DEFAULT_EXT_ADDRESS
|
||||
socket.CAN_ISOTP_DEFAULT_RECV_STMIN
|
||||
socket.CAN_ISOTP_DEFAULT_RECV_WFTMAX
|
||||
|
||||
socket.CAN_ISOTP_DEFAULT_LL_MTU
|
||||
socket.CAN_ISOTP_DEFAULT_LL_TX_DL
|
||||
socket.CAN_ISOTP_DEFAULT_LL_TX_FLAGS
|
||||
|
||||
def testCreateSocket(self):
|
||||
with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
|
||||
pass
|
||||
|
||||
Vendored
+18
@@ -17,6 +17,7 @@ from test.support import reap_children, verbose
|
||||
from test.support import os_helper
|
||||
from test.support import socket_helper
|
||||
from test.support import threading_helper
|
||||
from test.support import warnings_helper
|
||||
|
||||
|
||||
test.support.requires("network")
|
||||
@@ -43,6 +44,7 @@ def receive(sock, n, timeout=test.support.SHORT_TIMEOUT):
|
||||
raise RuntimeError("timed out on %r" % (sock,))
|
||||
|
||||
|
||||
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
|
||||
@test.support.requires_fork()
|
||||
@contextlib.contextmanager
|
||||
def simple_subprocess(testcase):
|
||||
@@ -173,6 +175,7 @@ class SocketServerTest(unittest.TestCase):
|
||||
socketserver.StreamRequestHandler,
|
||||
self.stream_examine)
|
||||
|
||||
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
|
||||
@requires_forking
|
||||
def test_ForkingTCPServer(self):
|
||||
with simple_subprocess(self):
|
||||
@@ -192,6 +195,7 @@ class SocketServerTest(unittest.TestCase):
|
||||
socketserver.StreamRequestHandler,
|
||||
self.stream_examine)
|
||||
|
||||
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
|
||||
@requires_unix_sockets
|
||||
@requires_forking
|
||||
def test_ForkingUnixStreamServer(self):
|
||||
@@ -210,6 +214,7 @@ class SocketServerTest(unittest.TestCase):
|
||||
socketserver.DatagramRequestHandler,
|
||||
self.dgram_examine)
|
||||
|
||||
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
|
||||
@requires_forking
|
||||
def test_ForkingUDPServer(self):
|
||||
with simple_subprocess(self):
|
||||
@@ -233,6 +238,7 @@ class SocketServerTest(unittest.TestCase):
|
||||
socketserver.DatagramRequestHandler,
|
||||
self.dgram_examine)
|
||||
|
||||
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
|
||||
@requires_unix_sockets
|
||||
@requires_forking
|
||||
def test_ForkingUnixDatagramServer(self):
|
||||
@@ -318,11 +324,13 @@ class ErrorHandlerTest(unittest.TestCase):
|
||||
|
||||
self.assertIs(cm.exc_type, SystemExit)
|
||||
|
||||
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
|
||||
@requires_forking
|
||||
def test_forking_handled(self):
|
||||
ForkingErrorTestServer(ValueError)
|
||||
self.check_result(handled=True)
|
||||
|
||||
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
|
||||
@requires_forking
|
||||
def test_forking_not_handled(self):
|
||||
ForkingErrorTestServer(SystemExit)
|
||||
@@ -507,5 +515,15 @@ class MiscTestCase(unittest.TestCase):
|
||||
server.server_close()
|
||||
|
||||
|
||||
class TestModule(unittest.TestCase):
|
||||
def test_deprecated__version__(self):
|
||||
with self.assertWarnsRegex(
|
||||
DeprecationWarning,
|
||||
"'__version__' is deprecated and slated for removal in Python 3.20",
|
||||
) as cm:
|
||||
getattr(socketserver, "__version__")
|
||||
self.assertEqual(cm.filename, __file__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+123
-25
@@ -21,7 +21,7 @@ mod _socket {
|
||||
},
|
||||
convert::{IntoPyException, ToPyObject, TryFromBorrowedObject, TryFromObject},
|
||||
function::{
|
||||
ArgBytesLike, ArgIntoFloat, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath,
|
||||
ArgBytesLike, ArgIndex, ArgIntoFloat, ArgMemoryBuffer, ArgStrOrBytesLike, FsPath,
|
||||
FuncArgs, OptionalArg,
|
||||
},
|
||||
types::{Constructor, DefaultConstructor, Destructor, Initializer, Representable},
|
||||
@@ -2441,28 +2441,114 @@ mod _socket {
|
||||
#[pymethod]
|
||||
fn setsockopt(
|
||||
zelf: &Py<Self>,
|
||||
level: i32,
|
||||
name: i32,
|
||||
value: Option<Either<ArgBytesLike, i32>>,
|
||||
optlen: OptionalArg<u32>,
|
||||
args: FuncArgs,
|
||||
vm: &VirtualMachine,
|
||||
) -> Result<(), IoOrPyException> {
|
||||
if !args.kwargs.is_empty() {
|
||||
return Err(vm
|
||||
.new_type_error("setsockopt() takes no keyword arguments")
|
||||
.into());
|
||||
}
|
||||
let nargs = args.args.len();
|
||||
if nargs < 3 {
|
||||
return Err(vm
|
||||
.new_type_error(format!(
|
||||
"setsockopt() takes at least 3 arguments ({nargs} given)"
|
||||
))
|
||||
.into());
|
||||
}
|
||||
if nargs > 4 {
|
||||
return Err(vm
|
||||
.new_type_error(format!(
|
||||
"setsockopt() takes at most 4 arguments ({nargs} given)"
|
||||
))
|
||||
.into());
|
||||
}
|
||||
let (level, name, value, optlen): (
|
||||
i32,
|
||||
i32,
|
||||
Option<PyObjectRef>,
|
||||
OptionalArg<ArgIndex>,
|
||||
) = args.bind(vm)?;
|
||||
let optlen = match optlen {
|
||||
OptionalArg::Present(index) => {
|
||||
let index = index.as_ref();
|
||||
// CPython's unsigned-int bitfield conversion masks the value,
|
||||
// warning only when it fits neither a signed nor unsigned int.
|
||||
if !index.as_bigint().to_i64().is_some_and(|value| {
|
||||
(i64::from(i32::MIN)..=i64::from(u32::MAX)).contains(&value)
|
||||
}) {
|
||||
crate::vm::warn::warn(
|
||||
vm.ctx.new_str("integer value out of range").into(),
|
||||
Some(vm.ctx.exceptions.deprecation_warning.to_owned()),
|
||||
1,
|
||||
None,
|
||||
vm,
|
||||
)?;
|
||||
}
|
||||
OptionalArg::Present(index.as_u32_mask())
|
||||
}
|
||||
OptionalArg::Missing => OptionalArg::Missing,
|
||||
};
|
||||
enum Value {
|
||||
Int(i32),
|
||||
Buffer(ArgBytesLike),
|
||||
Null(u32),
|
||||
}
|
||||
let value = match (value, optlen) {
|
||||
(Some(value), OptionalArg::Missing) => {
|
||||
if let Some(index) = value.try_index_opt(vm) {
|
||||
Value::Int(index?.try_to_primitive::<i32>(vm)?)
|
||||
} else if value.check_buffer() {
|
||||
let buffer = ArgBytesLike::try_from_object(vm, value)?;
|
||||
#[cfg(windows)]
|
||||
if buffer.len() > i32::MAX as usize {
|
||||
return Err(vm
|
||||
.new_overflow_error(format!(
|
||||
"socket option is larger than {} bytes",
|
||||
i32::MAX
|
||||
))
|
||||
.into());
|
||||
}
|
||||
Value::Buffer(buffer)
|
||||
} else {
|
||||
return Err(vm
|
||||
.new_type_error(format!(
|
||||
"socket option should be int, bytes-like object or None (got {})",
|
||||
value.class().fully_qualified_name(vm)?
|
||||
))
|
||||
.into());
|
||||
}
|
||||
}
|
||||
(None, OptionalArg::Present(optlen)) => Value::Null(optlen),
|
||||
(None, OptionalArg::Missing) => {
|
||||
return Err(vm
|
||||
.new_type_error(
|
||||
"setsockopt() requires 4 arguments when the third argument is None",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
(Some(value), OptionalArg::Present(_)) => {
|
||||
return Err(vm
|
||||
.new_type_error(format!(
|
||||
"setsockopt() only takes 4 arguments when the third argument is None (got {})",
|
||||
value.class().fully_qualified_name(vm)?
|
||||
))
|
||||
.into());
|
||||
}
|
||||
};
|
||||
// Convert Python values before locking the socket; callbacks may close it.
|
||||
let sock = zelf.sock()?;
|
||||
let fd = sock_fileno(&sock);
|
||||
match (value, optlen) {
|
||||
(Some(Either::A(b)), OptionalArg::Missing) => {
|
||||
b.with_ref(|b| host_socket::setsockopt_bytes(fd as _, level, name, b))?
|
||||
match &value {
|
||||
Value::Int(value) => {
|
||||
host_socket::setsockopt_int(fd as _, level, name, *value)?;
|
||||
}
|
||||
(Some(Either::B(val)), OptionalArg::Missing) => {
|
||||
host_socket::setsockopt_int(fd as _, level, name, val)?
|
||||
Value::Buffer(buffer) => {
|
||||
buffer.with_ref(|b| host_socket::setsockopt_bytes(fd as _, level, name, b))?;
|
||||
}
|
||||
(None, OptionalArg::Present(optlen)) => {
|
||||
host_socket::setsockopt_none(fd as _, level, name, optlen)?
|
||||
}
|
||||
_ => {
|
||||
return Err(vm
|
||||
.new_type_error("expected the value arg xor the optlen arg")
|
||||
.into());
|
||||
Value::Null(optlen) => {
|
||||
host_socket::setsockopt_none(fd as _, level, name, *optlen)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -2962,7 +3048,7 @@ mod _socket {
|
||||
#[pyarg(any)]
|
||||
host: Option<ArgStrOrBytesLike>,
|
||||
#[pyarg(any)]
|
||||
port: Option<Either<ArgStrOrBytesLike, i32>>,
|
||||
port: Option<PyObjectRef>,
|
||||
|
||||
#[pyarg(any, default = c::AF_UNSPEC)]
|
||||
family: i32,
|
||||
@@ -3014,10 +3100,23 @@ mod _socket {
|
||||
};
|
||||
let host = host_encoded.as_deref();
|
||||
|
||||
// Encode port: str/bytes as service name, int as port number
|
||||
let port_encoded: Option<String> = match opts.port.as_ref() {
|
||||
Some(Either::A(sb)) => {
|
||||
let port_str = match sb {
|
||||
// Check the index protocol before string/bytes conversion, including for
|
||||
// subclasses, and preserve any exception raised by __index__.
|
||||
let port_index = opts
|
||||
.port
|
||||
.as_ref()
|
||||
.and_then(|port| port.try_index_opt(vm))
|
||||
.transpose()?;
|
||||
let port_encoded: Option<String> = match (port_index, opts.port.as_ref()) {
|
||||
(Some(index), _) => Some(crate::vm::builtins::PyInt::repr_str(&index, vm)?),
|
||||
(None, Some(port)) => {
|
||||
if !port.fast_isinstance(vm.ctx.types.str_type)
|
||||
&& !port.fast_isinstance(vm.ctx.types.bytes_type)
|
||||
{
|
||||
return Err(vm.new_os_error("Int or String expected").into());
|
||||
}
|
||||
let sb = ArgStrOrBytesLike::try_from_object(vm, port.clone())?;
|
||||
let port_str = match &sb {
|
||||
ArgStrOrBytesLike::Str(s) => {
|
||||
// For str, check for surrogates and raise UnicodeEncodeError if found
|
||||
s.to_str()
|
||||
@@ -3055,8 +3154,7 @@ mod _socket {
|
||||
};
|
||||
Some(port_str)
|
||||
}
|
||||
Some(Either::B(i)) => Some(i.to_string()),
|
||||
None => None,
|
||||
(None, None) => None,
|
||||
};
|
||||
let port = port_encoded.as_deref();
|
||||
|
||||
@@ -3070,7 +3168,7 @@ mod _socket {
|
||||
ai.address,
|
||||
ai.socktype,
|
||||
ai.protocol,
|
||||
ai.canonname,
|
||||
ai.canonname.unwrap_or_default(),
|
||||
get_ip_addr_tuple(&ai.sockaddr, vm),
|
||||
))
|
||||
.into()
|
||||
|
||||
Reference in new issue
Block a user