socketwrapper 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {socketwrapper-0.1.0/socketwrapper.egg-info → socketwrapper-0.1.1}/PKG-INFO +8 -1
  2. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/README.md +7 -0
  3. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/pyproject.toml +2 -1
  4. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper/__init__.py +3 -3
  5. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper/_io.py +1 -1
  6. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper/_nt.py +38 -12
  7. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper/_utils.py +2 -2
  8. {socketwrapper-0.1.0 → socketwrapper-0.1.1/socketwrapper.egg-info}/PKG-INFO +8 -1
  9. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper.egg-info/zip-safe +1 -1
  10. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/LICENSE +0 -0
  11. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/setup.cfg +0 -0
  12. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper/_varint.py +0 -0
  13. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper/framing.py +0 -0
  14. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper/protocols.py +0 -0
  15. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper/py.typed +0 -0
  16. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper.egg-info/SOURCES.txt +0 -0
  17. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper.egg-info/dependency_links.txt +0 -0
  18. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper.egg-info/requires.txt +0 -0
  19. {socketwrapper-0.1.0 → socketwrapper-0.1.1}/socketwrapper.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socketwrapper
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: high level socket and pipe wrappers
5
5
  Author: Felipe A Hernandez
6
6
  Author-email: ergoithz@gmail.com
@@ -109,6 +109,13 @@ uv pip install 'socketwrapper[msgpack]'
109
109
 
110
110
  ## Changelog
111
111
 
112
+ ### 0.1.1 - 2026.07.03
113
+
114
+ #### Bugfixes
115
+
116
+ - Tighten Windows named pipes permissions.
117
+ - Avoid invalid Windows handles potentially causing descriptor leaks.
118
+
112
119
  ### 0.1.0 - 2026.01.22
113
120
 
114
121
  #### Breaking
@@ -54,6 +54,13 @@ uv pip install 'socketwrapper[msgpack]'
54
54
 
55
55
  ## Changelog
56
56
 
57
+ ### 0.1.1 - 2026.07.03
58
+
59
+ #### Bugfixes
60
+
61
+ - Tighten Windows named pipes permissions.
62
+ - Avoid invalid Windows handles potentially causing descriptor leaks.
63
+
57
64
  ### 0.1.0 - 2026.01.22
58
65
 
59
66
  #### Breaking
@@ -5,7 +5,7 @@ build-backend = 'setuptools.build_meta'
5
5
  [project]
6
6
  name = 'socketwrapper'
7
7
  description = 'high level socket and pipe wrappers'
8
- version = '0.1.0'
8
+ version = '0.1.1'
9
9
  requires-python = '>=3.12'
10
10
  keywords = ['socket', 'pipe', 'ipc', 'asyncio', 'multiprocessing']
11
11
  readme = { file = 'README.md', content-type = 'text/markdown' }
@@ -139,6 +139,7 @@ ignore = [
139
139
  # Ignore pylint broken nonsense
140
140
  'PLR2004', # declare every single constant as a variable (WTF!)
141
141
  'PLR0402', # import namespaces in the same way as properties (WTF!)
142
+ 'PLW0717', # too many statements in try/except (WTF!!)
142
143
  # Ignore tryceratops nonsense
143
144
  'TRY301', # raising inside try/except (WTF!)
144
145
  # Just broken
@@ -320,7 +320,7 @@ class SocketWrapper(_utils.ClosingContext):
320
320
 
321
321
  @property
322
322
  def inheritable(self) -> bool:
323
- """Get whether or not this socket is inheritable by subprocesses.
323
+ """Whether or not this socket is inheritable by subprocesses.
324
324
 
325
325
  :returns: true if the socket is inheritable by subprocesses.
326
326
 
@@ -450,7 +450,7 @@ class MessageWrapper(_utils.ClosingContext, typing_extensions.Generic[R, W, KS])
450
450
 
451
451
  @property
452
452
  def inheritable(self) -> bool:
453
- """Get whether or not this socket is inheritable by subprocesses.
453
+ """Whether or not this socket is inheritable by subprocesses.
454
454
 
455
455
  :returns: true if the socket is inheritable by subprocesses.
456
456
 
@@ -486,7 +486,7 @@ class MessageReader(MessageWrapper[R, typing.Any, KSR], typing_extensions.Generi
486
486
 
487
487
  @property
488
488
  def _frames(self) -> StreamFrames | None:
489
- """Get stream frames function."""
489
+ """Stream frames function."""
490
490
  return self.framing.frames if self._raw._reader.is_stream else None
491
491
 
492
492
  def recv(self, timeout: float | None = None) -> R:
@@ -95,7 +95,7 @@ class Consumer[Q, R, IO_R]:
95
95
 
96
96
  @functools.cached_property
97
97
  def selector(self) -> selectors.BaseSelector:
98
- """Get selector for class."""
98
+ """Selector for class."""
99
99
  selector = selectors.DefaultSelector()
100
100
  selector.register(self.sock, selectors.EVENT_WRITE if self._writer else selectors.EVENT_READ)
101
101
  return selector
@@ -19,10 +19,18 @@ from . import _utils
19
19
 
20
20
  IOBuffer = collections.abc.Buffer | ctypes.Array | ctypes.c_char_p
21
21
 
22
+ advapi32 = ctypes.windll.advapi32
22
23
  kernel32 = ctypes.windll.kernel32
23
24
  ntdll = ctypes.windll.ntdll
24
25
  winsock2 = ctypes.windll.ws2_32
25
26
 
27
+ ignored_io_status_block = (ctypes.c_void_p * 2)()
28
+
29
+ PIPE_SEC = b'D:P(D;;GA;;;NU)(A;;GA;;;CO)'
30
+ # D:P - Protected DACL — blocks inherited ACEs
31
+ # (D;;GA;;; - U) - Deny NETWORK (S-1-5-2) all access
32
+ # (A;;GA;;;CO) - Allow Creator Owner (S-1-3-0) all access, resolved to the pipe creator's SID
33
+
26
34
  FILE_TYPE_PIPE = 0x03
27
35
  WINSOCK_FIONREAD = 0x4004667F
28
36
 
@@ -290,7 +298,7 @@ class StreamOperation(OperationBase):
290
298
  super().__init__(handle)
291
299
  self._info = FilePipeLocalInformation()
292
300
  self._info_args = (
293
- self.handle, ctypes.byref(ctypes.c_void_p()), ctypes.byref(self._info), ctypes.sizeof(self._info),
301
+ self.handle, ctypes.byref(ignored_io_status_block), ctypes.byref(self._info), ctypes.sizeof(self._info),
294
302
  FILE_PIPE_LOCAL_INFORMATION,
295
303
  )
296
304
 
@@ -336,11 +344,22 @@ class StreamOperation(OperationBase):
336
344
 
337
345
  def resolve_pipe_handle(fd: int | None = None, handle: int | None = None) -> tuple[int, int, bool, bool, bool]:
338
346
  """Get a valid fd, handle, and whether or not it's a valid pipe."""
339
- fd = msvcrt.open_osfhandle(handle, os.O_BINARY | os.O_NOINHERIT) if fd is None else fd
340
- handle = msvcrt.get_osfhandle(fd) if handle is None else handle
341
- is_overlapped = is_overlapped_file(handle)
342
- is_named_pipe, is_message = is_message_named_pipe(handle)
343
- is_pipe = is_named_pipe or kernel32.GetFileType(handle) == FILE_TYPE_PIPE
347
+ fd, created = (
348
+ (msvcrt.open_osfhandle(handle, os.O_BINARY | os.O_NOINHERIT), True) if fd is None else
349
+ (fd, False)
350
+ )
351
+ try:
352
+ handle = msvcrt.get_osfhandle(fd) if handle is None else handle
353
+ is_overlapped = is_overlapped_file(handle)
354
+ is_named_pipe, is_message = is_message_named_pipe(handle)
355
+ is_pipe = is_named_pipe or kernel32.GetFileType(handle) == FILE_TYPE_PIPE
356
+
357
+ except Exception:
358
+ if created:
359
+ os.close(fd)
360
+
361
+ raise
362
+
344
363
  return fd, handle, is_pipe, is_overlapped, is_message
345
364
 
346
365
 
@@ -389,15 +408,19 @@ def is_message_named_pipe(handle: int) -> tuple[bool, bool]:
389
408
 
390
409
  def overlapped_pipe() -> tuple[int, int]:
391
410
  """Create overlapped named pipe pair (r,w) and return handlers."""
392
- name = f'\\\\.\\pipe\\iocppipe_{uuid.uuid4().hex}'.encode('ascii')
393
- security = SecurityAttributes(
394
- nLength=ctypes.sizeof(SecurityAttributes),
395
- lpSecurityDescriptor=None,
396
- bInheritHandle=False,
397
- )
411
+ sd_ptr = ctypes.c_void_p()
412
+ if not advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorA(PIPE_SEC, 1, ctypes.byref(sd_ptr), None):
413
+ raise ctypes.WinError()
398
414
 
415
+ name = f'\\\\.\\pipe\\iocppipe_{uuid.uuid4().hex}'.encode('ascii')
399
416
  rhandle = whandle = -1
400
417
  try:
418
+ security = SecurityAttributes(
419
+ nLength=ctypes.sizeof(SecurityAttributes),
420
+ lpSecurityDescriptor=sd_ptr,
421
+ bInheritHandle=False,
422
+ )
423
+
401
424
  rhandle = kernel32.CreateNamedPipeA(name, PIPE_INBOUND_OVERLAPPED, 0, 1, BUFFER_SIZE, BUFFER_SIZE, 0,
402
425
  ctypes.byref(security))
403
426
  if rhandle == -1:
@@ -417,6 +440,9 @@ def overlapped_pipe() -> tuple[int, int]:
417
440
  close_handle(handle=whandle)
418
441
  raise
419
442
 
443
+ finally:
444
+ kernel32.LocalFree(sd_ptr)
445
+
420
446
  return rhandle, whandle
421
447
 
422
448
 
@@ -174,7 +174,7 @@ class Deadline:
174
174
 
175
175
  @property
176
176
  def remaining(self) -> float:
177
- """Get time left before deadline."""
177
+ """Time left before deadline."""
178
178
  return max(0, self.deadline - time.monotonic())
179
179
 
180
180
  def __init__(self, *, deadline: float | None = None, timeout: float = 0, error: str | None = None) -> None:
@@ -251,7 +251,7 @@ class CrossLock:
251
251
 
252
252
  @property
253
253
  def locked(self) -> bool:
254
- """Get whether lock is acquired or not."""
254
+ """Whether lock is acquired or not."""
255
255
  return self._lock.locked()
256
256
 
257
257
  def acquire(self, *, timeout: float | None = None) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socketwrapper
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: high level socket and pipe wrappers
5
5
  Author: Felipe A Hernandez
6
6
  Author-email: ergoithz@gmail.com
@@ -109,6 +109,13 @@ uv pip install 'socketwrapper[msgpack]'
109
109
 
110
110
  ## Changelog
111
111
 
112
+ ### 0.1.1 - 2026.07.03
113
+
114
+ #### Bugfixes
115
+
116
+ - Tighten Windows named pipes permissions.
117
+ - Avoid invalid Windows handles potentially causing descriptor leaks.
118
+
112
119
  ### 0.1.0 - 2026.01.22
113
120
 
114
121
  #### Breaking
File without changes
File without changes