unctools 0.1.0__py3-none-any.whl
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.
- unctools/__init__.py +100 -0
- unctools/converter.py +378 -0
- unctools/detector.py +531 -0
- unctools/operations.py +562 -0
- unctools/utils/__init__.py +40 -0
- unctools/utils/compat.py +331 -0
- unctools/utils/logger.py +228 -0
- unctools/utils/validation.py +321 -0
- unctools/windows/__init__.py +45 -0
- unctools/windows/network.py +490 -0
- unctools/windows/registry.py +410 -0
- unctools/windows/security.py +586 -0
- unctools-0.1.0.dist-info/METADATA +189 -0
- unctools-0.1.0.dist-info/RECORD +17 -0
- unctools-0.1.0.dist-info/WHEEL +5 -0
- unctools-0.1.0.dist-info/licenses/LICENSE +21 -0
- unctools-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Path validation utilities for UNCtools.
|
|
3
|
+
|
|
4
|
+
This module provides functions for validating and checking various path types,
|
|
5
|
+
ensuring they conform to expected formats and restrictions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional, Dict, List, Tuple, Union, Any
|
|
13
|
+
|
|
14
|
+
# Set up module-level logger
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
# Regular expressions for common path formats
|
|
18
|
+
UNC_PATH_REGEX = re.compile(r'^\\\\([^\\]+)\\([^\\]+)(?:\\(.*))?$')
|
|
19
|
+
DRIVE_PATH_REGEX = re.compile(r'^([A-Za-z]:)(?:\\(.*))?$')
|
|
20
|
+
POSIX_PATH_REGEX = re.compile(r'^(/[^/]*)+/?$')
|
|
21
|
+
|
|
22
|
+
# Maximum path length limitations
|
|
23
|
+
MAX_PATH_WINDOWS = 260
|
|
24
|
+
MAX_PATH_WINDOWS_EXTENDED = 32767
|
|
25
|
+
MAX_PATH_UNIX = 4096 # Common limit, can vary by filesystem
|
|
26
|
+
|
|
27
|
+
class ValidationError(Exception):
|
|
28
|
+
"""Exception raised for path validation errors."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def validate_path(path: Union[str, Path],
|
|
32
|
+
exists: bool = False,
|
|
33
|
+
is_file: bool = False,
|
|
34
|
+
is_dir: bool = False,
|
|
35
|
+
is_absolute: bool = False,
|
|
36
|
+
max_length: Optional[int] = None) -> bool:
|
|
37
|
+
"""
|
|
38
|
+
Validate a path against common requirements.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
path: The path to validate.
|
|
42
|
+
exists: Whether the path must exist.
|
|
43
|
+
is_file: Whether the path must be a file.
|
|
44
|
+
is_dir: Whether the path must be a directory.
|
|
45
|
+
is_absolute: Whether the path must be absolute.
|
|
46
|
+
max_length: Maximum allowed path length.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
True if the path meets all requirements.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValidationError: If the path does not meet the requirements.
|
|
53
|
+
"""
|
|
54
|
+
path_obj = Path(path)
|
|
55
|
+
path_str = str(path_obj)
|
|
56
|
+
|
|
57
|
+
# Check if the path exists if required
|
|
58
|
+
if exists and not os.path.exists(path_str):
|
|
59
|
+
raise ValidationError(f"Path does not exist: {path_str}")
|
|
60
|
+
|
|
61
|
+
# Check if the path is a file if required
|
|
62
|
+
if is_file and not os.path.isfile(path_str):
|
|
63
|
+
raise ValidationError(f"Path is not a file: {path_str}")
|
|
64
|
+
|
|
65
|
+
# Check if the path is a directory if required
|
|
66
|
+
if is_dir and not os.path.isdir(path_str):
|
|
67
|
+
raise ValidationError(f"Path is not a directory: {path_str}")
|
|
68
|
+
|
|
69
|
+
# Check if the path is absolute if required
|
|
70
|
+
if is_absolute and not os.path.isabs(path_str):
|
|
71
|
+
raise ValidationError(f"Path is not absolute: {path_str}")
|
|
72
|
+
|
|
73
|
+
# Check the path length if a maximum is specified
|
|
74
|
+
if max_length is not None:
|
|
75
|
+
if len(path_str) > max_length:
|
|
76
|
+
raise ValidationError(f"Path exceeds maximum length ({len(path_str)} > {max_length}): {path_str}")
|
|
77
|
+
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
def validate_unc_path(path: Union[str, Path]) -> bool:
|
|
81
|
+
"""
|
|
82
|
+
Validate a UNC path (\\\\server\\share\\...).
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
path: The path to validate.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
True if the path is a valid UNC path.
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
ValidationError: If the path is not a valid UNC path.
|
|
92
|
+
"""
|
|
93
|
+
path_str = str(path).replace('/', '\\')
|
|
94
|
+
|
|
95
|
+
# Check if the path matches the UNC pattern
|
|
96
|
+
match = UNC_PATH_REGEX.match(path_str)
|
|
97
|
+
if not match:
|
|
98
|
+
raise ValidationError(f"Not a valid UNC path: {path_str}")
|
|
99
|
+
|
|
100
|
+
# Extract server and share names
|
|
101
|
+
server = match.group(1)
|
|
102
|
+
share = match.group(2)
|
|
103
|
+
|
|
104
|
+
# Validate server name
|
|
105
|
+
if not server:
|
|
106
|
+
raise ValidationError(f"Invalid UNC path: missing server name in {path_str}")
|
|
107
|
+
|
|
108
|
+
# Validate share name
|
|
109
|
+
if not share:
|
|
110
|
+
raise ValidationError(f"Invalid UNC path: missing share name in {path_str}")
|
|
111
|
+
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
def validate_local_path(path: Union[str, Path], windows_only: bool = False) -> bool:
|
|
115
|
+
"""
|
|
116
|
+
Validate a local path (drive letter on Windows, absolute path on Unix).
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
path: The path to validate.
|
|
120
|
+
windows_only: Whether to only accept Windows drive paths.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
True if the path is a valid local path.
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
ValidationError: If the path is not a valid local path.
|
|
127
|
+
"""
|
|
128
|
+
path_str = str(path)
|
|
129
|
+
|
|
130
|
+
# Check if the path is a Windows drive path
|
|
131
|
+
if DRIVE_PATH_REGEX.match(path_str):
|
|
132
|
+
# Valid Windows drive path
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
# If windows_only is True, only accept Windows drive paths
|
|
136
|
+
if windows_only:
|
|
137
|
+
raise ValidationError(f"Not a valid Windows drive path: {path_str}")
|
|
138
|
+
|
|
139
|
+
# Otherwise, check if the path is a valid POSIX absolute path
|
|
140
|
+
if POSIX_PATH_REGEX.match(path_str):
|
|
141
|
+
# Valid POSIX absolute path
|
|
142
|
+
return True
|
|
143
|
+
|
|
144
|
+
# Not a valid local path in either format
|
|
145
|
+
raise ValidationError(f"Not a valid local path: {path_str}")
|
|
146
|
+
|
|
147
|
+
def check_path_length_limits(path: Union[str, Path],
|
|
148
|
+
extended_prefix: bool = False) -> Dict[str, Any]:
|
|
149
|
+
"""
|
|
150
|
+
Check if a path exceeds platform-specific length limits.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
path: The path to check.
|
|
154
|
+
extended_prefix: Whether to consider paths with the Windows extended path prefix.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
A dictionary with information about path length limits:
|
|
158
|
+
{
|
|
159
|
+
'length': int, # Actual path length
|
|
160
|
+
'exceeds_windows': bool, # Whether it exceeds standard Windows limit
|
|
161
|
+
'exceeds_windows_ext': bool, # Whether it exceeds extended Windows limit
|
|
162
|
+
'exceeds_unix': bool, # Whether it exceeds common Unix limit
|
|
163
|
+
'exceeds_current': bool, # Whether it exceeds the current platform's limit
|
|
164
|
+
}
|
|
165
|
+
"""
|
|
166
|
+
path_str = str(path)
|
|
167
|
+
length = len(path_str)
|
|
168
|
+
|
|
169
|
+
# Check if the path has the extended path prefix
|
|
170
|
+
has_extended_prefix = path_str.startswith('\\\\?\\')
|
|
171
|
+
|
|
172
|
+
# Calculate effective length for Windows
|
|
173
|
+
effective_length = length
|
|
174
|
+
if has_extended_prefix:
|
|
175
|
+
# Subtract the prefix length
|
|
176
|
+
effective_length = length - 4
|
|
177
|
+
|
|
178
|
+
# Check against various limits
|
|
179
|
+
exceeds_windows = effective_length > MAX_PATH_WINDOWS
|
|
180
|
+
exceeds_windows_ext = length > MAX_PATH_WINDOWS_EXTENDED
|
|
181
|
+
exceeds_unix = length > MAX_PATH_UNIX
|
|
182
|
+
|
|
183
|
+
# Determine the current platform's limit
|
|
184
|
+
exceeds_current = False
|
|
185
|
+
if os.name == 'nt':
|
|
186
|
+
# On Windows, check against the appropriate limit
|
|
187
|
+
if extended_prefix or has_extended_prefix:
|
|
188
|
+
exceeds_current = exceeds_windows_ext
|
|
189
|
+
else:
|
|
190
|
+
exceeds_current = exceeds_windows
|
|
191
|
+
else:
|
|
192
|
+
# On Unix-like systems, check against the Unix limit
|
|
193
|
+
exceeds_current = exceeds_unix
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
'length': length,
|
|
197
|
+
'effective_length': effective_length,
|
|
198
|
+
'has_extended_prefix': has_extended_prefix,
|
|
199
|
+
'exceeds_windows': exceeds_windows,
|
|
200
|
+
'exceeds_windows_ext': exceeds_windows_ext,
|
|
201
|
+
'exceeds_unix': exceeds_unix,
|
|
202
|
+
'exceeds_current': exceeds_current
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
def is_valid_server_name(server: str) -> bool:
|
|
206
|
+
"""
|
|
207
|
+
Check if a server name is valid.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
server: The server name to check.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
True if the server name is valid, False otherwise.
|
|
214
|
+
"""
|
|
215
|
+
# Check for empty server name
|
|
216
|
+
if not server:
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
# Check for invalid characters
|
|
220
|
+
invalid_chars = '<>:"/\\|?*'
|
|
221
|
+
if any(c in server for c in invalid_chars):
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
# Check for spaces in server name (generally not recommended)
|
|
225
|
+
if ' ' in server:
|
|
226
|
+
logger.warning(f"Server name contains spaces: '{server}'")
|
|
227
|
+
|
|
228
|
+
return True
|
|
229
|
+
|
|
230
|
+
def is_valid_share_name(share: str) -> bool:
|
|
231
|
+
"""
|
|
232
|
+
Check if a share name is valid.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
share: The share name to check.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
True if the share name is valid, False otherwise.
|
|
239
|
+
"""
|
|
240
|
+
# Check for empty share name
|
|
241
|
+
if not share:
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
# Check for invalid characters
|
|
245
|
+
invalid_chars = '<>:"/\\|?*'
|
|
246
|
+
if any(c in share for c in invalid_chars):
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
# Check length (Windows limits share names to 80 characters)
|
|
250
|
+
if len(share) > 80:
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
return True
|
|
254
|
+
|
|
255
|
+
def is_valid_filename(filename: str) -> bool:
|
|
256
|
+
"""
|
|
257
|
+
Check if a filename is valid.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
filename: The filename to check.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
True if the filename is valid, False otherwise.
|
|
264
|
+
"""
|
|
265
|
+
# Check for empty filename
|
|
266
|
+
if not filename:
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
# Check for invalid characters (Windows is most restrictive)
|
|
270
|
+
invalid_chars = '<>:"/\\|?*'
|
|
271
|
+
if any(c in filename for c in invalid_chars):
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
# Check for reserved names on Windows
|
|
275
|
+
if os.name == 'nt':
|
|
276
|
+
reserved_names = [
|
|
277
|
+
'CON', 'PRN', 'AUX', 'NUL',
|
|
278
|
+
'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
|
|
279
|
+
'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
|
|
280
|
+
]
|
|
281
|
+
# Check if the filename (without extension) is a reserved name
|
|
282
|
+
base_name = os.path.splitext(filename)[0].upper()
|
|
283
|
+
if base_name in reserved_names:
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
return True
|
|
287
|
+
|
|
288
|
+
def sanitize_filename(filename: str, replacement: str = '_') -> str:
|
|
289
|
+
"""
|
|
290
|
+
Sanitize a filename by replacing invalid characters.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
filename: The filename to sanitize.
|
|
294
|
+
replacement: The character to use as replacement.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
A sanitized filename.
|
|
298
|
+
"""
|
|
299
|
+
# Replace invalid characters
|
|
300
|
+
invalid_chars = '<>:"/\\|?*'
|
|
301
|
+
for c in invalid_chars:
|
|
302
|
+
filename = filename.replace(c, replacement)
|
|
303
|
+
|
|
304
|
+
# Replace reserved names on Windows
|
|
305
|
+
if os.name == 'nt':
|
|
306
|
+
reserved_names = [
|
|
307
|
+
'CON', 'PRN', 'AUX', 'NUL',
|
|
308
|
+
'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
|
|
309
|
+
'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
|
|
310
|
+
]
|
|
311
|
+
# Check if the filename (without extension) is a reserved name
|
|
312
|
+
base_name, ext = os.path.splitext(filename)
|
|
313
|
+
if base_name.upper() in reserved_names:
|
|
314
|
+
base_name = f"{base_name}{replacement}"
|
|
315
|
+
filename = f"{base_name}{ext}"
|
|
316
|
+
|
|
317
|
+
# Ensure the filename is not empty
|
|
318
|
+
if not filename:
|
|
319
|
+
filename = "unnamed"
|
|
320
|
+
|
|
321
|
+
return filename
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Windows-specific utilities for UNC paths, network drives, and security zones.
|
|
3
|
+
|
|
4
|
+
This subpackage contains Windows-specific functionality for working with UNC paths,
|
|
5
|
+
network mappings, security zones, and registry operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
# Set up module-level logger
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Check if we're running on Windows
|
|
15
|
+
IS_WINDOWS = os.name == 'nt'
|
|
16
|
+
|
|
17
|
+
if not IS_WINDOWS:
|
|
18
|
+
logger.warning("The windows module is being imported on a non-Windows platform. Some functionality will not be available.")
|
|
19
|
+
|
|
20
|
+
# Import key functions for the package namespace
|
|
21
|
+
from .registry import add_to_intranet_zone, fix_security_zone
|
|
22
|
+
from .network import (
|
|
23
|
+
create_network_mapping, remove_network_mapping,
|
|
24
|
+
get_all_network_mappings, check_network_connection
|
|
25
|
+
)
|
|
26
|
+
from .security import (
|
|
27
|
+
get_file_security, set_file_permissions,
|
|
28
|
+
take_ownership, check_access_rights,
|
|
29
|
+
bypass_security_dialog
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# For convenience, re-export these functions at the package level
|
|
33
|
+
__all__ = [
|
|
34
|
+
'add_to_intranet_zone',
|
|
35
|
+
'fix_security_zone',
|
|
36
|
+
'create_network_mapping',
|
|
37
|
+
'remove_network_mapping',
|
|
38
|
+
'get_all_network_mappings',
|
|
39
|
+
'check_network_connection',
|
|
40
|
+
'get_file_security',
|
|
41
|
+
'set_file_permissions',
|
|
42
|
+
'take_ownership',
|
|
43
|
+
'check_access_rights',
|
|
44
|
+
'bypass_security_dialog'
|
|
45
|
+
]
|