jentic-openapi-common 1.0.0a10__tar.gz → 1.0.0a11__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.
- {jentic_openapi_common-1.0.0a10 → jentic_openapi_common-1.0.0a11}/PKG-INFO +1 -1
- {jentic_openapi_common-1.0.0a10 → jentic_openapi_common-1.0.0a11}/pyproject.toml +1 -1
- {jentic_openapi_common-1.0.0a10 → jentic_openapi_common-1.0.0a11}/src/jentic/apitools/openapi/common/uri.py +143 -29
- {jentic_openapi_common-1.0.0a10 → jentic_openapi_common-1.0.0a11}/LICENSE +0 -0
- {jentic_openapi_common-1.0.0a10 → jentic_openapi_common-1.0.0a11}/NOTICE +0 -0
- {jentic_openapi_common-1.0.0a10 → jentic_openapi_common-1.0.0a11}/README.md +0 -0
- {jentic_openapi_common-1.0.0a10 → jentic_openapi_common-1.0.0a11}/src/jentic/apitools/openapi/common/path_security.py +0 -0
- {jentic_openapi_common-1.0.0a10 → jentic_openapi_common-1.0.0a11}/src/jentic/apitools/openapi/common/py.typed +0 -0
- {jentic_openapi_common-1.0.0a10 → jentic_openapi_common-1.0.0a11}/src/jentic/apitools/openapi/common/subproc.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
|
-
import urllib.request
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from urllib.parse import urljoin, urlparse, urlsplit, urlunsplit
|
|
5
|
+
from urllib.request import url2pathname
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
@@ -10,8 +10,12 @@ __all__ = [
|
|
|
10
10
|
"is_uri_like",
|
|
11
11
|
"is_http_https_url",
|
|
12
12
|
"is_file_uri",
|
|
13
|
+
"is_scheme_relative_uri",
|
|
14
|
+
"is_absolute_uri",
|
|
15
|
+
"is_fragment_only_uri",
|
|
13
16
|
"is_path",
|
|
14
17
|
"resolve_to_absolute",
|
|
18
|
+
"file_uri_to_path",
|
|
15
19
|
]
|
|
16
20
|
|
|
17
21
|
|
|
@@ -50,7 +54,7 @@ class URIResolutionError(ValueError):
|
|
|
50
54
|
pass
|
|
51
55
|
|
|
52
56
|
|
|
53
|
-
def is_uri_like(
|
|
57
|
+
def is_uri_like(uri: str | None) -> bool:
|
|
54
58
|
r"""
|
|
55
59
|
Heuristic check: is `s` a URI-like reference or absolute/relative path?
|
|
56
60
|
- Accepts http(s)://, file://
|
|
@@ -59,13 +63,13 @@ def is_uri_like(s: str | None) -> bool:
|
|
|
59
63
|
- Must be a single line (no '\\n' or '\\r').
|
|
60
64
|
Leading/trailing whitespace is ignored.
|
|
61
65
|
"""
|
|
62
|
-
if not
|
|
66
|
+
if not uri:
|
|
63
67
|
return False
|
|
64
|
-
|
|
68
|
+
uri = uri.strip()
|
|
65
69
|
# Enforce single line
|
|
66
|
-
if "\n" in
|
|
70
|
+
if "\n" in uri or "\r" in uri:
|
|
67
71
|
return False
|
|
68
|
-
return bool(_URI_LIKE_RE.match(
|
|
72
|
+
return bool(_URI_LIKE_RE.match(uri))
|
|
69
73
|
|
|
70
74
|
|
|
71
75
|
def is_path(s: str | None) -> bool:
|
|
@@ -109,6 +113,107 @@ def is_path(s: str | None) -> bool:
|
|
|
109
113
|
return True
|
|
110
114
|
|
|
111
115
|
|
|
116
|
+
def is_http_https_url(url: str) -> bool:
|
|
117
|
+
p = urlparse(url)
|
|
118
|
+
return p.scheme in ("http", "https") and bool(p.netloc)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def is_file_uri(uri: str) -> bool:
|
|
122
|
+
return urlparse(uri).scheme == "file"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def is_scheme_relative_uri(uri: str) -> bool:
|
|
126
|
+
"""
|
|
127
|
+
Check if `uri` is a scheme-relative URI (also called protocol-relative URI).
|
|
128
|
+
|
|
129
|
+
A scheme-relative URI starts with "//" followed by an authority component (netloc),
|
|
130
|
+
inheriting the scheme from the context (e.g., "//cdn.example.com/path").
|
|
131
|
+
|
|
132
|
+
This is defined in RFC 3986 section 4.2 as a network-path reference.
|
|
133
|
+
Per RFC 3986, a valid network-path reference must have an authority component.
|
|
134
|
+
|
|
135
|
+
Examples:
|
|
136
|
+
- "//cdn.example.com/x.yaml" -> True
|
|
137
|
+
- "//example.com/api" -> True
|
|
138
|
+
- "http://example.com" -> False (has scheme)
|
|
139
|
+
- "/path/to/file" -> False (single slash)
|
|
140
|
+
- "./relative" -> False (relative path)
|
|
141
|
+
- "//" -> False (no authority component)
|
|
142
|
+
- "///path" -> False (no authority component)
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
uri: The string to check
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
True if the string is a valid scheme-relative URI with authority, False otherwise
|
|
149
|
+
"""
|
|
150
|
+
if not uri.startswith("//"):
|
|
151
|
+
return False
|
|
152
|
+
p = urlparse(uri)
|
|
153
|
+
return bool(p.netloc)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def is_absolute_uri(uri: str) -> bool:
|
|
157
|
+
"""
|
|
158
|
+
Check if `uri` is an absolute URI according to RFC 3986.
|
|
159
|
+
|
|
160
|
+
An absolute URI is defined as having a scheme (e.g., "http:", "https:", "ftp:", "file:").
|
|
161
|
+
|
|
162
|
+
Note: Scheme-relative URIs (starting with "//") are NOT considered absolute URIs.
|
|
163
|
+
According to RFC 3986 section 4.2, scheme-relative URIs are classified as
|
|
164
|
+
"relative references" (specifically, "network-path references").
|
|
165
|
+
Use `is_scheme_relative_uri()` to check for those separately.
|
|
166
|
+
|
|
167
|
+
Examples:
|
|
168
|
+
- "http://example.com" -> True
|
|
169
|
+
- "https://example.com/path" -> True
|
|
170
|
+
- "ftp://ftp.example.com" -> True
|
|
171
|
+
- "file:///path/to/file" -> True
|
|
172
|
+
- "//cdn.example.com/x.yaml" -> False (scheme-relative, use is_scheme_relative_uri)
|
|
173
|
+
- "/path/to/file" -> False (absolute path, not URI)
|
|
174
|
+
- "./relative" -> False
|
|
175
|
+
- "#fragment" -> False
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
uri: The string to check
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
True if the string is an absolute URI (has a scheme), False otherwise
|
|
182
|
+
"""
|
|
183
|
+
p = urlparse(uri)
|
|
184
|
+
return bool(p.scheme)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def is_fragment_only_uri(uri: str) -> bool:
|
|
188
|
+
"""
|
|
189
|
+
Check if `uri` is a fragment-only reference.
|
|
190
|
+
|
|
191
|
+
A fragment-only reference consists solely of a fragment identifier (starts with "#").
|
|
192
|
+
These are used in JSON References and OpenAPI to refer to parts within the same document.
|
|
193
|
+
|
|
194
|
+
Note: This checks if the ENTIRE string is a fragment reference, not whether
|
|
195
|
+
a URI contains a fragment. For example, "http://example.com#section" would
|
|
196
|
+
return False because it's a full URI with a fragment, not fragment-only.
|
|
197
|
+
|
|
198
|
+
Examples:
|
|
199
|
+
- "#/definitions/User" -> True
|
|
200
|
+
- "#fragment" -> True
|
|
201
|
+
- "#" -> True (empty fragment identifier)
|
|
202
|
+
- "##" -> True (fragment identifier is "#")
|
|
203
|
+
- "http://example.com#section" -> False (full URI with fragment)
|
|
204
|
+
- "/path/to/file" -> False
|
|
205
|
+
- "./relative" -> False
|
|
206
|
+
- "" -> False
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
uri: The string to check
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
True if the string is a fragment-only reference, False otherwise
|
|
213
|
+
"""
|
|
214
|
+
return uri.startswith("#")
|
|
215
|
+
|
|
216
|
+
|
|
112
217
|
def resolve_to_absolute(value: str, base_uri: str | None = None) -> str:
|
|
113
218
|
"""
|
|
114
219
|
Resolve `value` to either:
|
|
@@ -132,7 +237,7 @@ def resolve_to_absolute(value: str, base_uri: str | None = None) -> str:
|
|
|
132
237
|
return _normalize_url(value)
|
|
133
238
|
|
|
134
239
|
if is_file_uri(value):
|
|
135
|
-
return
|
|
240
|
+
return file_uri_to_path(value)
|
|
136
241
|
|
|
137
242
|
if _looks_like_windows_path(value):
|
|
138
243
|
return _resolve_path_like(value, base_uri)
|
|
@@ -167,6 +272,36 @@ def resolve_to_absolute(value: str, base_uri: str | None = None) -> str:
|
|
|
167
272
|
return _resolve_path_like(value, None)
|
|
168
273
|
|
|
169
274
|
|
|
275
|
+
def file_uri_to_path(file_uri: str) -> str:
|
|
276
|
+
"""
|
|
277
|
+
Convert a file:// URI to an absolute filesystem path.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
file_uri: A file:// URI string (e.g., "file:///path/to/file" or "file://server/share/path")
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Absolute filesystem path as a string
|
|
284
|
+
|
|
285
|
+
Raises:
|
|
286
|
+
URIResolutionError: If the input is not a valid file:// URI
|
|
287
|
+
|
|
288
|
+
Examples:
|
|
289
|
+
>>> file_uri_to_path("file:///home/user/doc.yaml")
|
|
290
|
+
'/home/user/doc.yaml'
|
|
291
|
+
>>> file_uri_to_path("file://localhost/etc/config.yaml")
|
|
292
|
+
'/etc/config.yaml'
|
|
293
|
+
"""
|
|
294
|
+
parsed_uri = urlparse(file_uri)
|
|
295
|
+
if parsed_uri.scheme != "file":
|
|
296
|
+
raise URIResolutionError(f"Not a file URI: {file_uri!r}")
|
|
297
|
+
if parsed_uri.netloc and parsed_uri.netloc not in ("", "localhost"):
|
|
298
|
+
# UNC: \\server\share\path
|
|
299
|
+
unc = f"//{parsed_uri.netloc}{parsed_uri.path}"
|
|
300
|
+
return str(Path(url2pathname(unc)).resolve())
|
|
301
|
+
path = url2pathname(parsed_uri.path)
|
|
302
|
+
return str(Path(path).resolve())
|
|
303
|
+
|
|
304
|
+
|
|
170
305
|
def _guard_single_line(s: str) -> None:
|
|
171
306
|
if not isinstance(s, str) or ("\n" in s or "\r" in s):
|
|
172
307
|
raise URIResolutionError("Input must be a single-line string.")
|
|
@@ -176,15 +311,6 @@ def _looks_like_windows_path(s: str) -> bool:
|
|
|
176
311
|
return bool(_WINDOWS_DRIVE_RE.match(s) or _WINDOWS_UNC_RE.match(s))
|
|
177
312
|
|
|
178
313
|
|
|
179
|
-
def is_http_https_url(s: str) -> bool:
|
|
180
|
-
p = urlparse(s)
|
|
181
|
-
return p.scheme in ("http", "https") and bool(p.netloc)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def is_file_uri(s: str) -> bool:
|
|
185
|
-
return urlparse(s).scheme == "file"
|
|
186
|
-
|
|
187
|
-
|
|
188
314
|
def _normalize_url(s: str) -> str:
|
|
189
315
|
import posixpath
|
|
190
316
|
|
|
@@ -197,24 +323,12 @@ def _normalize_url(s: str) -> str:
|
|
|
197
323
|
return urlunsplit((parts.scheme, parts.netloc, normalized_path, parts.query, parts.fragment))
|
|
198
324
|
|
|
199
325
|
|
|
200
|
-
def _file_uri_to_path(file_uri: str) -> str:
|
|
201
|
-
p = urlparse(file_uri)
|
|
202
|
-
if p.scheme != "file":
|
|
203
|
-
raise URIResolutionError(f"Not a file URI: {file_uri!r}")
|
|
204
|
-
if p.netloc and p.netloc not in ("", "localhost"):
|
|
205
|
-
# UNC: \\server\share\path
|
|
206
|
-
unc = f"//{p.netloc}{p.path}"
|
|
207
|
-
return str(Path(urllib.request.url2pathname(unc)).resolve())
|
|
208
|
-
path = urllib.request.url2pathname(p.path)
|
|
209
|
-
return str(Path(path).resolve())
|
|
210
|
-
|
|
211
|
-
|
|
212
326
|
def _resolve_path_like(value: str, base_uri: str | None) -> str:
|
|
213
327
|
value = os.path.expandvars(os.path.expanduser(value))
|
|
214
328
|
|
|
215
329
|
if base_uri:
|
|
216
330
|
if is_file_uri(base_uri):
|
|
217
|
-
base_path = Path(
|
|
331
|
+
base_path = Path(url2pathname(urlparse(base_uri).path))
|
|
218
332
|
elif is_http_https_url(base_uri):
|
|
219
333
|
# Don't silently combine a local path with a URL base
|
|
220
334
|
raise URIResolutionError("Cannot resolve a local path against an HTTP(S) base_uri.")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|