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.
@@ -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
+ ]