qalita 2.5.3__py3-none-any.whl → 2.5.4__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.
- qalita/_frontend/node_modules/react/cjs/react-compiler-runtime.production.js +16 -0
- qalita/_frontend/node_modules/react/cjs/react-jsx-dev-runtime.production.js +14 -0
- qalita/_frontend/node_modules/react/cjs/react-jsx-runtime.production.js +34 -0
- qalita/_frontend/node_modules/react/cjs/react.production.js +542 -0
- qalita/_frontend/node_modules/react/compiler-runtime.js +14 -0
- qalita/_frontend/node_modules/react/index.js +1 -1
- qalita/_frontend/node_modules/react/jsx-dev-runtime.js +1 -1
- qalita/_frontend/node_modules/react/jsx-runtime.js +1 -1
- qalita/_frontend/node_modules/react/package.json +19 -15
- qalita/_frontend/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +6603 -0
- qalita/_frontend/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +6692 -0
- qalita/_frontend/node_modules/react-dom/cjs/react-dom-server.browser.production.js +7410 -0
- qalita/_frontend/node_modules/react-dom/cjs/react-dom-server.edge.production.js +7512 -0
- qalita/_frontend/node_modules/react-dom/cjs/react-dom-server.node.production.js +7707 -0
- qalita/_frontend/node_modules/react-dom/cjs/react-dom.production.js +210 -0
- qalita/_frontend/node_modules/react-dom/index.js +1 -1
- qalita/_frontend/node_modules/react-dom/package.json +75 -20
- qalita/_frontend/node_modules/react-dom/server.browser.js +3 -4
- qalita/_frontend/node_modules/react-dom/server.edge.js +17 -0
- qalita/_frontend/node_modules/react-dom/server.node.js +5 -4
- qalita/_frontend/node_modules/react-dom/static.node.js +14 -0
- qalita/_frontend/package.json +6 -6
- qalita/commands/pack.py +41 -23
- qalita/commands/source.py +5 -4
- qalita/commands/worker.py +72 -24
- qalita/internal/request.py +4 -1
- qalita/internal/utils.py +178 -20
- qalita/web/blueprints/context.py +2 -1
- qalita/web/blueprints/dashboard.py +14 -5
- qalita/web/blueprints/sources.py +24 -6
- qalita/web/blueprints/workers.py +18 -4
- {qalita-2.5.3.dist-info → qalita-2.5.4.dist-info}/METADATA +2 -2
- {qalita-2.5.3.dist-info → qalita-2.5.4.dist-info}/RECORD +38 -37
- qalita/_frontend/node_modules/react/cjs/react-jsx-dev-runtime.production.min.js +0 -10
- qalita/_frontend/node_modules/react/cjs/react-jsx-runtime.production.min.js +0 -11
- qalita/_frontend/node_modules/react/cjs/react.production.min.js +0 -26
- qalita/_frontend/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +0 -93
- qalita/_frontend/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.min.js +0 -101
- qalita/_frontend/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js +0 -96
- qalita/_frontend/node_modules/react-dom/cjs/react-dom-server.node.production.min.js +0 -102
- qalita/_frontend/node_modules/react-dom/cjs/react-dom.production.min.js +0 -322
- qalita/_frontend/node_modules/scheduler/cjs/scheduler.development.js +0 -634
- qalita/_frontend/node_modules/scheduler/cjs/scheduler.production.min.js +0 -19
- qalita/_frontend/node_modules/scheduler/index.js +0 -7
- qalita/_frontend/node_modules/scheduler/package.json +0 -36
- /qalita/_frontend/.next/static/{fm8OzItmoekYnFcSZ5R8j → X4_AlYMbCyee-ZVLjCYMg}/_buildManifest.js +0 -0
- /qalita/_frontend/.next/static/{fm8OzItmoekYnFcSZ5R8j → X4_AlYMbCyee-ZVLjCYMg}/_ssgManifest.js +0 -0
- {qalita-2.5.3.dist-info → qalita-2.5.4.dist-info}/WHEEL +0 -0
- {qalita-2.5.3.dist-info → qalita-2.5.4.dist-info}/entry_points.txt +0 -0
- {qalita-2.5.3.dist-info → qalita-2.5.4.dist-info}/licenses/LICENSE +0 -0
qalita/commands/worker.py
CHANGED
|
@@ -570,16 +570,40 @@ def pull_pack(config, pack_id, pack_version=None):
|
|
|
570
570
|
|
|
571
571
|
jobs_path = config.get_worker_run_path()
|
|
572
572
|
# Système de caching, on regarde si le pack est déjà présent dans le cache sinon on le télécharge
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
573
|
+
# Validate URL components to prevent path traversal attacks
|
|
574
|
+
import re
|
|
575
|
+
url_parts = pack_url.split("/")
|
|
576
|
+
file_name = url_parts[-1] if url_parts else ""
|
|
577
|
+
bucket_name = url_parts[3] if len(url_parts) > 3 else ""
|
|
578
|
+
s3_folder = "/".join(url_parts[4:-1]) if len(url_parts) > 4 else ""
|
|
579
|
+
|
|
580
|
+
# Sanitize path components - only allow alphanumeric, dots, hyphens, underscores
|
|
581
|
+
safe_pattern = re.compile(r'^[\w\-\.]+$')
|
|
582
|
+
if not file_name or not safe_pattern.match(file_name):
|
|
583
|
+
logger.error(f"Invalid file name in pack URL: {file_name}")
|
|
584
|
+
sys.exit(1)
|
|
585
|
+
if bucket_name and not safe_pattern.match(bucket_name):
|
|
586
|
+
logger.error(f"Invalid bucket name in pack URL: {bucket_name}")
|
|
587
|
+
sys.exit(1)
|
|
588
|
+
|
|
589
|
+
# Build path and validate it stays within jobs_path
|
|
590
|
+
cache_folder = os.path.join(jobs_path, bucket_name, s3_folder) if s3_folder else os.path.join(jobs_path, bucket_name)
|
|
591
|
+
local_path = os.path.join(cache_folder, file_name)
|
|
592
|
+
|
|
593
|
+
# Resolve to real paths and verify containment
|
|
594
|
+
jobs_path_real = os.path.realpath(os.path.abspath(jobs_path))
|
|
595
|
+
local_path_normalized = os.path.normpath(os.path.abspath(local_path))
|
|
596
|
+
|
|
597
|
+
# Check that the path doesn't escape jobs_path
|
|
598
|
+
if not local_path_normalized.startswith(jobs_path_real + os.sep) and local_path_normalized != jobs_path_real:
|
|
599
|
+
logger.error(f"Invalid pack cache path detected: {local_path_normalized}")
|
|
600
|
+
sys.exit(1)
|
|
577
601
|
|
|
578
602
|
if os.path.exists(local_path):
|
|
579
603
|
logger.info(f"Using CACHED Pack at : {local_path}")
|
|
580
604
|
return local_path, pack_version
|
|
581
|
-
if not os.path.exists(
|
|
582
|
-
os.makedirs(
|
|
605
|
+
if not os.path.exists(cache_folder):
|
|
606
|
+
os.makedirs(cache_folder)
|
|
583
607
|
|
|
584
608
|
# Fetch the pack from api
|
|
585
609
|
response = send_api_request(f"/api/v1/assets/{pack_asset_id}/fetch", "get")
|
|
@@ -678,27 +702,51 @@ def job_run(
|
|
|
678
702
|
# Uncompress the pack
|
|
679
703
|
archive_name = pack_file_path.split("/")[-1]
|
|
680
704
|
archive_path = os.path.join(temp_folder_name, archive_name)
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
705
|
+
|
|
706
|
+
def is_within_directory(directory: str, target: str) -> bool:
|
|
707
|
+
"""Check if target path is within the directory (prevents path traversal)."""
|
|
708
|
+
abs_directory = os.path.abspath(directory)
|
|
709
|
+
abs_target = os.path.abspath(target)
|
|
710
|
+
prefix = os.path.commonprefix([abs_directory, abs_target])
|
|
711
|
+
return prefix == abs_directory
|
|
712
|
+
|
|
713
|
+
def safe_extract(tar: tarfile.TarFile, path: str) -> None:
|
|
714
|
+
"""Safely extract tar members, preventing path traversal attacks."""
|
|
684
715
|
for member in tar.getmembers():
|
|
685
|
-
|
|
686
|
-
|
|
716
|
+
member_path = os.path.join(path, member.name)
|
|
717
|
+
|
|
718
|
+
# Check for absolute paths
|
|
719
|
+
if os.path.isabs(member.name):
|
|
720
|
+
logger.warning(f"Skipping absolute path in tar: {member.name}")
|
|
687
721
|
continue
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
)
|
|
722
|
+
|
|
723
|
+
# Check for path traversal using '..'
|
|
724
|
+
if ".." in member.name.split("/") or ".." in member.name.split(os.sep):
|
|
725
|
+
logger.warning(f"Skipping path traversal attempt in tar: {member.name}")
|
|
693
726
|
continue
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
727
|
+
|
|
728
|
+
# Verify the resolved path is within the extraction directory
|
|
729
|
+
if not is_within_directory(path, member_path):
|
|
730
|
+
logger.warning(f"Skipping path outside extraction directory: {member.name}")
|
|
731
|
+
continue
|
|
732
|
+
|
|
733
|
+
# Check for unsafe symlinks
|
|
734
|
+
if member.issym() or member.islnk():
|
|
735
|
+
# Resolve the link target
|
|
736
|
+
if member.issym():
|
|
737
|
+
link_target = os.path.join(os.path.dirname(member_path), member.linkname)
|
|
738
|
+
else:
|
|
739
|
+
link_target = os.path.join(path, member.linkname)
|
|
740
|
+
|
|
741
|
+
if not is_within_directory(path, link_target):
|
|
742
|
+
logger.warning(f"Skipping symlink pointing outside directory: {member.name} -> {member.linkname}")
|
|
743
|
+
continue
|
|
744
|
+
|
|
745
|
+
# Extract the safe member
|
|
746
|
+
tar.extract(member, path)
|
|
747
|
+
|
|
748
|
+
with tarfile.open(archive_path, "r:gz") as tar:
|
|
749
|
+
safe_extract(tar, temp_folder_name)
|
|
702
750
|
|
|
703
751
|
# Delete the compressed pack (Windows may keep a short lock; retry briefly)
|
|
704
752
|
for _ in range(5):
|
qalita/internal/request.py
CHANGED
|
@@ -121,7 +121,10 @@ def send_request(
|
|
|
121
121
|
token = None
|
|
122
122
|
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
# SSL verification is enabled by default for security
|
|
125
|
+
# Only disable if SKIP_SSL_VERIFY is explicitly set to a truthy value
|
|
126
|
+
skip_ssl_env = os.getenv("SKIP_SSL_VERIFY", "").lower()
|
|
127
|
+
verify_ssl = skip_ssl_env not in ("true", "1", "yes", "on")
|
|
125
128
|
|
|
126
129
|
try:
|
|
127
130
|
if mode == "post":
|
qalita/internal/utils.py
CHANGED
|
@@ -26,16 +26,157 @@ def safe_path_join(base_dir: str, *paths: str) -> str:
|
|
|
26
26
|
Raises:
|
|
27
27
|
ValueError: If the resulting path would escape the base directory.
|
|
28
28
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
# Validate base_dir
|
|
30
|
+
if not isinstance(base_dir, str):
|
|
31
|
+
raise ValueError("Base directory must be a string")
|
|
32
|
+
if "\x00" in base_dir:
|
|
33
|
+
raise ValueError("Base directory contains null bytes")
|
|
34
|
+
|
|
35
|
+
# Validate path components
|
|
36
|
+
for p in paths:
|
|
37
|
+
if not isinstance(p, str):
|
|
38
|
+
raise ValueError("Path component must be a string")
|
|
39
|
+
if "\x00" in p:
|
|
40
|
+
raise ValueError("Path component contains null bytes")
|
|
41
|
+
# Reject absolute paths in components
|
|
42
|
+
if os.path.isabs(p):
|
|
43
|
+
raise ValueError(f"Path component must be relative: {p}")
|
|
44
|
+
|
|
45
|
+
base = os.path.realpath(os.path.abspath(base_dir))
|
|
46
|
+
full_path = os.path.realpath(os.path.abspath(os.path.join(base, *paths)))
|
|
47
|
+
|
|
31
48
|
# Ensure the resulting path is within the base directory
|
|
32
49
|
if not full_path.startswith(base + os.sep) and full_path != base:
|
|
33
50
|
raise ValueError(f"Path traversal detected: attempted to access {full_path} outside of {base}")
|
|
34
51
|
return full_path
|
|
35
52
|
|
|
36
53
|
|
|
37
|
-
def
|
|
38
|
-
"""Validate and normalize a path
|
|
54
|
+
def validate_directory_path(path: str) -> str: # lgtm[py/path-injection]
|
|
55
|
+
"""Validate and normalize a directory path for safe operations.
|
|
56
|
+
|
|
57
|
+
This function performs security checks to ensure the path is safe to use
|
|
58
|
+
for file system operations. It validates the path exists and is a directory.
|
|
59
|
+
|
|
60
|
+
Security: This function acts as a sanitizer for path injection attacks by:
|
|
61
|
+
- Rejecting null bytes and control characters
|
|
62
|
+
- Rejecting paths with traversal sequences after normalization
|
|
63
|
+
- Resolving to absolute canonical path (resolves symlinks)
|
|
64
|
+
- Verifying the path exists and is a directory
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
path: The directory path to validate.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The normalized absolute path.
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
ValueError: If the path is invalid or contains dangerous patterns.
|
|
74
|
+
FileNotFoundError: If the directory does not exist.
|
|
75
|
+
"""
|
|
76
|
+
if not isinstance(path, str):
|
|
77
|
+
raise ValueError("Path must be a string")
|
|
78
|
+
if not path or not path.strip():
|
|
79
|
+
raise ValueError("Path cannot be empty")
|
|
80
|
+
if "\x00" in path:
|
|
81
|
+
raise ValueError("Path contains null bytes")
|
|
82
|
+
if any(ord(c) < 32 for c in path if c not in ('\t', '\n', '\r')):
|
|
83
|
+
raise ValueError("Path contains invalid control characters")
|
|
84
|
+
|
|
85
|
+
# Strip whitespace from input
|
|
86
|
+
clean_path = path.strip()
|
|
87
|
+
|
|
88
|
+
# Normalize path components (resolve .., ., etc.)
|
|
89
|
+
normalized = os.path.normpath(clean_path)
|
|
90
|
+
|
|
91
|
+
# Security check: reject paths that still contain traversal sequences after normalization
|
|
92
|
+
# This explicit check helps static analyzers recognize this as a sanitizer
|
|
93
|
+
if ".." in normalized.split(os.sep):
|
|
94
|
+
raise ValueError("Path contains directory traversal sequences")
|
|
95
|
+
|
|
96
|
+
# Get canonical absolute path (resolves symlinks)
|
|
97
|
+
# Security note: This is an intentional path sanitizer for CLI operations.
|
|
98
|
+
# The function validates user-provided paths with multiple security checks above.
|
|
99
|
+
abs_path = os.path.realpath(os.path.abspath(normalized)) # nosec B108 # lgtm[py/path-injection]
|
|
100
|
+
|
|
101
|
+
# Verify the resolved path is not empty
|
|
102
|
+
if not abs_path:
|
|
103
|
+
raise ValueError("Path resolves to empty string")
|
|
104
|
+
|
|
105
|
+
if not os.path.exists(abs_path):
|
|
106
|
+
raise FileNotFoundError(f"Directory does not exist: {abs_path}")
|
|
107
|
+
if not os.path.isdir(abs_path):
|
|
108
|
+
raise ValueError(f"Path is not a directory: {abs_path}")
|
|
109
|
+
|
|
110
|
+
return abs_path
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def validate_file_path(path: str) -> str: # lgtm[py/path-injection]
|
|
114
|
+
"""Validate and normalize a file path for safe operations.
|
|
115
|
+
|
|
116
|
+
This function performs security checks to ensure the path is safe to use
|
|
117
|
+
for file system operations. It validates the path exists and is a file.
|
|
118
|
+
|
|
119
|
+
Security: This function acts as a sanitizer for path injection attacks by:
|
|
120
|
+
- Rejecting null bytes and control characters
|
|
121
|
+
- Rejecting paths with traversal sequences after normalization
|
|
122
|
+
- Resolving to absolute canonical path (resolves symlinks)
|
|
123
|
+
- Verifying the path exists and is a file
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
path: The file path to validate.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
The normalized absolute path.
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
ValueError: If the path is invalid or contains dangerous patterns.
|
|
133
|
+
FileNotFoundError: If the file does not exist.
|
|
134
|
+
"""
|
|
135
|
+
if not isinstance(path, str):
|
|
136
|
+
raise ValueError("Path must be a string")
|
|
137
|
+
if not path or not path.strip():
|
|
138
|
+
raise ValueError("Path cannot be empty")
|
|
139
|
+
if "\x00" in path:
|
|
140
|
+
raise ValueError("Path contains null bytes")
|
|
141
|
+
if any(ord(c) < 32 for c in path if c not in ('\t', '\n', '\r')):
|
|
142
|
+
raise ValueError("Path contains invalid control characters")
|
|
143
|
+
|
|
144
|
+
# Strip whitespace from input
|
|
145
|
+
clean_path = path.strip()
|
|
146
|
+
|
|
147
|
+
# Normalize path components (resolve .., ., etc.)
|
|
148
|
+
normalized = os.path.normpath(clean_path)
|
|
149
|
+
|
|
150
|
+
# Security check: reject paths that still contain traversal sequences after normalization
|
|
151
|
+
# This explicit check helps static analyzers recognize this as a sanitizer
|
|
152
|
+
if ".." in normalized.split(os.sep):
|
|
153
|
+
raise ValueError("Path contains directory traversal sequences")
|
|
154
|
+
|
|
155
|
+
# Get canonical absolute path (resolves symlinks)
|
|
156
|
+
# Security note: This is an intentional path sanitizer for CLI operations.
|
|
157
|
+
# The function validates user-provided paths with multiple security checks above.
|
|
158
|
+
abs_path = os.path.realpath(os.path.abspath(normalized)) # nosec B108 # lgtm[py/path-injection]
|
|
159
|
+
|
|
160
|
+
# Verify the resolved path is not empty
|
|
161
|
+
if not abs_path:
|
|
162
|
+
raise ValueError("Path resolves to empty string")
|
|
163
|
+
|
|
164
|
+
if not os.path.exists(abs_path):
|
|
165
|
+
raise FileNotFoundError(f"File does not exist: {abs_path}")
|
|
166
|
+
if not os.path.isfile(abs_path):
|
|
167
|
+
raise ValueError(f"Path is not a file: {abs_path}")
|
|
168
|
+
|
|
169
|
+
return abs_path
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def safe_path_check(path: str) -> str: # lgtm[py/path-injection]
|
|
173
|
+
"""Validate and normalize a path, preventing path injection attacks.
|
|
174
|
+
|
|
175
|
+
Security: This function acts as a sanitizer for path injection attacks by:
|
|
176
|
+
- Rejecting null bytes and control characters
|
|
177
|
+
- Rejecting paths with traversal sequences after normalization
|
|
178
|
+
- Normalizing path components (resolve .., ., etc.)
|
|
179
|
+
- Resolving to canonical absolute path
|
|
39
180
|
|
|
40
181
|
Args:
|
|
41
182
|
path: The path to validate.
|
|
@@ -44,23 +185,44 @@ def safe_path_check(path: str) -> str:
|
|
|
44
185
|
The normalized absolute path.
|
|
45
186
|
|
|
46
187
|
Raises:
|
|
47
|
-
ValueError: If the path contains
|
|
188
|
+
ValueError: If the path contains dangerous patterns.
|
|
48
189
|
"""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
190
|
+
if not isinstance(path, str):
|
|
191
|
+
raise ValueError("Path must be a string")
|
|
192
|
+
|
|
193
|
+
# Reject null bytes which can be used for path injection
|
|
194
|
+
if "\x00" in path:
|
|
195
|
+
raise ValueError("Path contains null bytes")
|
|
196
|
+
|
|
197
|
+
# Reject other control characters
|
|
198
|
+
if any(ord(c) < 32 for c in path if c not in ('\t', '\n', '\r')):
|
|
199
|
+
raise ValueError("Path contains invalid control characters")
|
|
200
|
+
|
|
201
|
+
# Strip whitespace
|
|
202
|
+
clean_path = path.strip() if path else path
|
|
203
|
+
|
|
204
|
+
# Normalize the path to resolve any .. or . components
|
|
205
|
+
normalized = os.path.normpath(clean_path)
|
|
206
|
+
|
|
207
|
+
# Security check: reject paths that still contain traversal sequences after normalization
|
|
208
|
+
# This explicit check helps static analyzers recognize this as a sanitizer
|
|
209
|
+
if ".." in normalized.split(os.sep):
|
|
210
|
+
raise ValueError("Path contains directory traversal sequences")
|
|
211
|
+
|
|
212
|
+
# Get the real absolute path (resolves symlinks)
|
|
213
|
+
# Security note: This is an intentional path sanitizer for CLI operations.
|
|
214
|
+
# The function validates user-provided paths with multiple security checks above.
|
|
215
|
+
abs_path = os.path.realpath(os.path.abspath(normalized)) # nosec B108 # lgtm[py/path-injection]
|
|
52
216
|
|
|
53
|
-
#
|
|
54
|
-
if
|
|
55
|
-
|
|
56
|
-
# This is for cases where relative paths are legitimately used
|
|
57
|
-
pass
|
|
217
|
+
# Additional check: ensure the resolved path is not empty
|
|
218
|
+
if not abs_path:
|
|
219
|
+
raise ValueError("Path resolves to empty string")
|
|
58
220
|
|
|
59
221
|
return abs_path
|
|
60
222
|
|
|
61
223
|
|
|
62
224
|
def get_version():
|
|
63
|
-
return "2.5.
|
|
225
|
+
return "2.5.4"
|
|
64
226
|
|
|
65
227
|
|
|
66
228
|
def make_tarfile(output_filename, source_dir):
|
|
@@ -173,9 +335,7 @@ def test_connection(config, type_):
|
|
|
173
335
|
client = InsecureClient(url, user=config["user"])
|
|
174
336
|
client.status(config["path"], strict=False)
|
|
175
337
|
elif type_ == "folder":
|
|
176
|
-
folder_path =
|
|
177
|
-
if not os.path.isdir(folder_path):
|
|
178
|
-
raise Exception(f"Folder {folder_path} not found")
|
|
338
|
+
folder_path = validate_directory_path(config["path"]) # lgtm[py/path-injection]
|
|
179
339
|
if not os.access(folder_path, os.R_OK):
|
|
180
340
|
raise Exception(f"No read access to folder {folder_path}")
|
|
181
341
|
elif type_ == "oracle":
|
|
@@ -191,9 +351,7 @@ def test_connection(config, type_):
|
|
|
191
351
|
conn.close()
|
|
192
352
|
# FTP support removed for security reasons
|
|
193
353
|
elif type_ == "file":
|
|
194
|
-
file_path =
|
|
195
|
-
if not os.path.isfile(file_path):
|
|
196
|
-
raise Exception(f"File {file_path} not found")
|
|
354
|
+
file_path = validate_file_path(config["path"]) # lgtm[py/path-injection]
|
|
197
355
|
if not os.access(file_path, os.R_OK):
|
|
198
356
|
raise Exception(f"No read access to file {file_path}")
|
|
199
357
|
else:
|
qalita/web/blueprints/context.py
CHANGED
|
@@ -57,8 +57,9 @@ def select_context():
|
|
|
57
57
|
ok = True
|
|
58
58
|
message = "Selection cleared"
|
|
59
59
|
except Exception as exc:
|
|
60
|
+
logger.error(f"Failed to clear selection: {exc}")
|
|
60
61
|
ok = False
|
|
61
|
-
message =
|
|
62
|
+
message = "Failed to clear selection"
|
|
62
63
|
else:
|
|
63
64
|
try:
|
|
64
65
|
root = qalita_home()
|
|
@@ -175,11 +175,20 @@ def push_pack_from_ui():
|
|
|
175
175
|
feedback = None
|
|
176
176
|
feedback_level = "info"
|
|
177
177
|
if pack_dir:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
try:
|
|
179
|
+
ok, message = push_from_directory(cfg, pack_dir)
|
|
180
|
+
# Sanitize message to avoid exposing internal details
|
|
181
|
+
if ok:
|
|
182
|
+
feedback = "Pack pushed successfully."
|
|
183
|
+
else:
|
|
184
|
+
# Log the detailed message but return a generic one to the user
|
|
185
|
+
logger.error(f"Pack push failed: {message}")
|
|
186
|
+
feedback = "Pack push failed. Check the logs for details."
|
|
187
|
+
feedback_level = "info" if ok else "error"
|
|
188
|
+
except Exception:
|
|
189
|
+
logger.exception("Unexpected error during pack push")
|
|
190
|
+
feedback = "An unexpected error occurred during pack push."
|
|
191
|
+
feedback_level = "error"
|
|
183
192
|
else:
|
|
184
193
|
feedback = "Please select a pack folder."
|
|
185
194
|
feedback_level = "error"
|
qalita/web/blueprints/sources.py
CHANGED
|
@@ -719,10 +719,19 @@ def _find_source_by_id(conf: Dict[str, Any], src_id: str) -> Dict[str, Any] | No
|
|
|
719
719
|
|
|
720
720
|
|
|
721
721
|
def _csv_preview(source: Dict[str, Any], options: Dict[str, Any] | None = None) -> Tuple[Dict[str, Any], int]:
|
|
722
|
+
from qalita.internal.utils import validate_file_path
|
|
723
|
+
|
|
722
724
|
cfg = source.get("config") if isinstance(source.get("config"), dict) else {}
|
|
723
|
-
|
|
724
|
-
if not
|
|
725
|
-
return {"ok": False, "message": "CSV file not
|
|
725
|
+
raw_path = cfg.get("path") or source.get("path")
|
|
726
|
+
if not raw_path:
|
|
727
|
+
return {"ok": False, "message": "CSV file path not specified"}, 400
|
|
728
|
+
|
|
729
|
+
# Validate and sanitize the file path to prevent path injection
|
|
730
|
+
try:
|
|
731
|
+
path = validate_file_path(raw_path)
|
|
732
|
+
except (ValueError, FileNotFoundError) as e:
|
|
733
|
+
logger.warning(f"CSV preview path validation failed: {e}")
|
|
734
|
+
return {"ok": False, "message": "CSV file not found or invalid path"}, 404
|
|
726
735
|
delimiter = cfg.get("delimiter") or ","
|
|
727
736
|
encoding = cfg.get("encoding") or "utf-8"
|
|
728
737
|
has_header = bool(cfg.get("has_header", True))
|
|
@@ -776,15 +785,24 @@ _PREVIEW_HANDLERS: Dict[str, PreviewHandler] = {
|
|
|
776
785
|
|
|
777
786
|
|
|
778
787
|
def _excel_preview(source: Dict[str, Any], options: Dict[str, Any] | None = None) -> Tuple[Dict[str, Any], int]:
|
|
788
|
+
from qalita.internal.utils import validate_file_path
|
|
789
|
+
|
|
779
790
|
try:
|
|
780
791
|
import openpyxl # type: ignore
|
|
781
792
|
except Exception:
|
|
782
793
|
return {"ok": False, "message": "Excel preview requires 'openpyxl' to be installed"}, 500
|
|
783
794
|
|
|
784
795
|
cfg = source.get("config") if isinstance(source.get("config"), dict) else {}
|
|
785
|
-
|
|
786
|
-
if not
|
|
787
|
-
return {"ok": False, "message": "Excel file not
|
|
796
|
+
raw_path = cfg.get("path") or source.get("path")
|
|
797
|
+
if not raw_path:
|
|
798
|
+
return {"ok": False, "message": "Excel file path not specified"}, 400
|
|
799
|
+
|
|
800
|
+
# Validate and sanitize the file path to prevent path injection
|
|
801
|
+
try:
|
|
802
|
+
path = validate_file_path(raw_path)
|
|
803
|
+
except (ValueError, FileNotFoundError) as e:
|
|
804
|
+
logger.warning(f"Excel preview path validation failed: {e}")
|
|
805
|
+
return {"ok": False, "message": "Excel file not found or invalid path"}, 404
|
|
788
806
|
|
|
789
807
|
sheet_name = (cfg.get("sheet") or "").strip()
|
|
790
808
|
header_row_raw = cfg.get("header_row")
|
qalita/web/blueprints/workers.py
CHANGED
|
@@ -345,15 +345,29 @@ def worker_summary():
|
|
|
345
345
|
|
|
346
346
|
@bp.get("/worker/run/<run_name>")
|
|
347
347
|
def open_worker_run(run_name: str):
|
|
348
|
+
import re
|
|
349
|
+
|
|
348
350
|
cfg = current_app.config["QALITA_CONFIG_OBJ"]
|
|
349
351
|
run_root = cfg.get_worker_run_path()
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
352
|
+
|
|
353
|
+
# Validate run_name format to prevent path traversal attempts
|
|
354
|
+
# Expected format: YYYYMMDDHHMMSS_XXXXX (timestamp_randomseed)
|
|
355
|
+
if not run_name or not re.match(r'^[\w\-]+$', run_name):
|
|
356
|
+
return jsonify({
|
|
357
|
+
"ok": False,
|
|
358
|
+
"error": "Invalid run name format",
|
|
359
|
+
"message": "Run name contains invalid characters.",
|
|
360
|
+
}), 400
|
|
361
|
+
|
|
362
|
+
# Use realpath to resolve symlinks and get canonical paths
|
|
363
|
+
run_root_real = os.path.realpath(os.path.abspath(run_root))
|
|
364
|
+
candidate = os.path.realpath(os.path.abspath(os.path.join(run_root, run_name)))
|
|
365
|
+
|
|
366
|
+
# Ensure the resolved path is within the run_root directory
|
|
367
|
+
if not candidate.startswith(run_root_real + os.sep) and candidate != run_root_real:
|
|
353
368
|
return jsonify({
|
|
354
369
|
"ok": False,
|
|
355
370
|
"error": "Invalid path",
|
|
356
|
-
"path": candidate,
|
|
357
371
|
"message": "If the path is invalid it means: Your local agent is not the one that ran the analysis you are trying to get files from, or the job failed or was cancelled.",
|
|
358
372
|
}), 400
|
|
359
373
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qalita
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.4
|
|
4
4
|
Summary: QALITA Platform Command Line Interface
|
|
5
5
|
Author-email: QALITA SAS <contact@qalita.io>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -45,7 +45,7 @@ Requires-Dist: toml>=0.10.2
|
|
|
45
45
|
Requires-Dist: waitress==3.0.2
|
|
46
46
|
Provides-Extra: dev
|
|
47
47
|
Requires-Dist: black>=25.1.0; extra == 'dev'
|
|
48
|
-
Requires-Dist: flake8<
|
|
48
|
+
Requires-Dist: flake8<8.0,>=3.8.1; extra == 'dev'
|
|
49
49
|
Requires-Dist: pre-commit>=3.3.3; extra == 'dev'
|
|
50
50
|
Requires-Dist: pylint>=2.17.4; extra == 'dev'
|
|
51
51
|
Requires-Dist: pytest>=7.4.0; extra == 'dev'
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
qalita/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
qalita/__main__.py,sha256=gs7MmT1d9-WPXK9ForTO37EC2oIKJWI03p5-7HpwGk8,13978
|
|
3
|
-
qalita/_frontend/package.json,sha256=
|
|
3
|
+
qalita/_frontend/package.json,sha256=MhIsZiRI5VIfq4UEVP5WRnGmj444vbiFW5ryfAkkqhY,634
|
|
4
4
|
qalita/_frontend/server.js,sha256=f5pZF0Z-nZbY2aST7eArz7AmUdVtObAPV6x3-p9X8TQ,6417
|
|
5
|
+
qalita/_frontend/.next/static/X4_AlYMbCyee-ZVLjCYMg/_buildManifest.js,sha256=lHbgr9P52mimTm7FhjEZH9PqymLJ9aN351F_6aieWeo,219
|
|
6
|
+
qalita/_frontend/.next/static/X4_AlYMbCyee-ZVLjCYMg/_ssgManifest.js,sha256=Z49s4suAsf5y_GfnQSvm4qtq2ggxEbZPfEDTXjy6XgA,80
|
|
5
7
|
qalita/_frontend/.next/static/chunks/023d923a37d494fc.js,sha256=Kv4cEeP6FJtSzuGGpMg-gKQD4H7j0ea6mKdMI_YEPzk,214812
|
|
6
8
|
qalita/_frontend/.next/static/chunks/0f84739db4a8acc7.js,sha256=0Zhp_p6Gsh5e3RYFq9WPRTuNaDsqkWLGGzDpmee8L1c,12946
|
|
7
9
|
qalita/_frontend/.next/static/chunks/1107bdca1eff6d34.css,sha256=Lkh6CMInMrpk_bFB5PHjbkfzyJVQojtC8b7EemDt710,27391
|
|
@@ -16,8 +18,6 @@ qalita/_frontend/.next/static/chunks/cbd55ab9639e1e66.js,sha256=tPgJbUb7fEba3Qbh
|
|
|
16
18
|
qalita/_frontend/.next/static/chunks/e393fec0d8ba175d.js,sha256=cHc7vArx3ngdkkppgF6epM9e_JaIkVy5pWpqlC5Qfw8,29965
|
|
17
19
|
qalita/_frontend/.next/static/chunks/ff1a16fafef87110.js,sha256=6XhrEwWJTkEZu5gRqBbayc9XZ1VZbdmJ7hW0VXSfw14,282
|
|
18
20
|
qalita/_frontend/.next/static/chunks/turbopack-1ad58da399056f41.js,sha256=0w0vc4UMJogp7-l6SinryZc3wHS8vbQjVJFsPbcalM0,9909
|
|
19
|
-
qalita/_frontend/.next/static/fm8OzItmoekYnFcSZ5R8j/_buildManifest.js,sha256=lHbgr9P52mimTm7FhjEZH9PqymLJ9aN351F_6aieWeo,219
|
|
20
|
-
qalita/_frontend/.next/static/fm8OzItmoekYnFcSZ5R8j/_ssgManifest.js,sha256=Z49s4suAsf5y_GfnQSvm4qtq2ggxEbZPfEDTXjy6XgA,80
|
|
21
21
|
qalita/_frontend/node_modules/@img/colour/color.cjs,sha256=oAjEFf-X2T1sHb6sWFYkfq_KGzypc7_NdRy1lujnKaw,44950
|
|
22
22
|
qalita/_frontend/node_modules/@img/colour/index.cjs,sha256=5uk-f7IJQgog_fLsZ26cZ0E76NWs6Z7sZPlGhIDwNo0,49
|
|
23
23
|
qalita/_frontend/node_modules/@img/colour/package.json,sha256=bvoBzsVeaHJG179iItF4Y944mYWHQxLmp0ud8aROlSo,1016
|
|
@@ -1175,26 +1175,27 @@ qalita/_frontend/node_modules/next/dist/trace/report/index.js,sha256=hVEGzQE7m3b
|
|
|
1175
1175
|
qalita/_frontend/node_modules/next/dist/trace/report/to-json-build.js,sha256=hXI3spPSPDwrGZRZl4sm4lOGf93VLt2EM05mVXmQE2I,4181
|
|
1176
1176
|
qalita/_frontend/node_modules/next/dist/trace/report/to-json.js,sha256=LhWr9g69oMDa1TPamj1T6lAks7kA6qDmY1OT2hsC4SM,4546
|
|
1177
1177
|
qalita/_frontend/node_modules/next/dist/trace/report/to-telemetry.js,sha256=dqwHUJyKycCqnYC5gHDECKIe4PsfMHonEMIfr-kjC4M,849
|
|
1178
|
-
qalita/_frontend/node_modules/react/
|
|
1179
|
-
qalita/_frontend/node_modules/react/
|
|
1180
|
-
qalita/_frontend/node_modules/react/jsx-runtime.js,sha256=
|
|
1181
|
-
qalita/_frontend/node_modules/react/
|
|
1182
|
-
qalita/_frontend/node_modules/react/
|
|
1183
|
-
qalita/_frontend/node_modules/react/cjs/react-
|
|
1184
|
-
qalita/_frontend/node_modules/react/cjs/react.production.
|
|
1185
|
-
qalita/_frontend/node_modules/react-
|
|
1186
|
-
qalita/_frontend/node_modules/react
|
|
1187
|
-
qalita/_frontend/node_modules/react-dom/
|
|
1188
|
-
qalita/_frontend/node_modules/react-dom/
|
|
1189
|
-
qalita/_frontend/node_modules/react-dom/
|
|
1190
|
-
qalita/_frontend/node_modules/react-dom/
|
|
1191
|
-
qalita/_frontend/node_modules/react-dom/
|
|
1192
|
-
qalita/_frontend/node_modules/react-dom/
|
|
1193
|
-
qalita/_frontend/node_modules/react-dom/cjs/react-dom.production.
|
|
1194
|
-
qalita/_frontend/node_modules/
|
|
1195
|
-
qalita/_frontend/node_modules/
|
|
1196
|
-
qalita/_frontend/node_modules/
|
|
1197
|
-
qalita/_frontend/node_modules/
|
|
1178
|
+
qalita/_frontend/node_modules/react/compiler-runtime.js,sha256=XABFagGAt7ayNmNI9yGLDqLDqFx_7-JMGFM-1UEm9gE,412
|
|
1179
|
+
qalita/_frontend/node_modules/react/index.js,sha256=YMr_3svcXbO8TsToPflIg0XMsnHQdTO5IQv1dQkY2X4,186
|
|
1180
|
+
qalita/_frontend/node_modules/react/jsx-dev-runtime.js,sha256=SGWJEgqWJyHIKBtazsL21J5gzl-dkIzIFN3-ccYWOs4,218
|
|
1181
|
+
qalita/_frontend/node_modules/react/jsx-runtime.js,sha256=s5KVGnfQouamvQF-SddoSBrltjxBCcbDH2Is97JDPtw,210
|
|
1182
|
+
qalita/_frontend/node_modules/react/package.json,sha256=w4Os7WpnyNBvAplgFcChY-eFMqMwqW3_HM_RB7AKsyY,1248
|
|
1183
|
+
qalita/_frontend/node_modules/react/cjs/react-compiler-runtime.production.js,sha256=99UY9cBDzQCFFLT9jGZanS-laGvTbG59RNeBh8BDhwA,463
|
|
1184
|
+
qalita/_frontend/node_modules/react/cjs/react-jsx-dev-runtime.production.js,sha256=ups2cRmXHtdGklpgAlbTQutMw_I4neAT9h42_x81lKU,387
|
|
1185
|
+
qalita/_frontend/node_modules/react/cjs/react-jsx-runtime.production.js,sha256=HkbxUAJpaYXoDGHUeqow2rsDyVQnCmkPX337us-pACs,976
|
|
1186
|
+
qalita/_frontend/node_modules/react/cjs/react.production.js,sha256=h3I2ZdCW_Veil0xkt1TGMRHi5vNVVXlJOeMIzyUrTuc,17217
|
|
1187
|
+
qalita/_frontend/node_modules/react-dom/index.js,sha256=uho-M0ifhoNxVhd3YH4-5Qkd8PfqPxoQ94nWuG5MFQU,1359
|
|
1188
|
+
qalita/_frontend/node_modules/react-dom/package.json,sha256=2SbxWOG-FYharqwlnDlWekb2Ul-90Uw4BShIbDFFmZY,3063
|
|
1189
|
+
qalita/_frontend/node_modules/react-dom/server.browser.js,sha256=91lGDrNXHVvx4anl73ywM-xcZ_JdZKa79amsQ70lgxo,563
|
|
1190
|
+
qalita/_frontend/node_modules/react-dom/server.edge.js,sha256=pjFSdASwp7vhCqedsEhSGW_X_YigVsG7hM8aGIXh6TQ,561
|
|
1191
|
+
qalita/_frontend/node_modules/react-dom/server.node.js,sha256=8t2T9vkfmDzZEjIJjUrFVXhZbTPWgNfLlDjqOwphG0s,669
|
|
1192
|
+
qalita/_frontend/node_modules/react-dom/static.node.js,sha256=OeE07W3JA9iXKzrl4qohqHcsalP-00RX9NwH9rMVcns,445
|
|
1193
|
+
qalita/_frontend/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js,sha256=Iym-PAZByxWU1O1hZ0DekVoxsX_MzUzgDmNoCHFQUp8,237937
|
|
1194
|
+
qalita/_frontend/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js,sha256=LfUTZ0hEXsOlyDPs2UKkjBUdJ2EgTZ3Jw-EDEZ4C5bQ,242793
|
|
1195
|
+
qalita/_frontend/node_modules/react-dom/cjs/react-dom-server.browser.production.js,sha256=xYsMac3eKc3OJ4PRR4th-B5U8mf-EB_RP5OUqGVHKBs,268058
|
|
1196
|
+
qalita/_frontend/node_modules/react-dom/cjs/react-dom-server.edge.production.js,sha256=E6B-Zft43I2rwIB7CoIxb3NJHvQ6WEmkejmS-svqeoo,273614
|
|
1197
|
+
qalita/_frontend/node_modules/react-dom/cjs/react-dom-server.node.production.js,sha256=LLYx4ZQfkr6treNk_qEtNyd91L5LO90xkV6OUGSXAfA,277046
|
|
1198
|
+
qalita/_frontend/node_modules/react-dom/cjs/react-dom.production.js,sha256=r2ZWairoxxYqMyMi5AHAtRWF5tO-wiYdo6LYJbFoQVo,6655
|
|
1198
1199
|
qalita/_frontend/node_modules/semver/package.json,sha256=fgufa1PgSLU7PKEXSi-vH88CBklyl2Fdm10C_ajyZcU,1663
|
|
1199
1200
|
qalita/_frontend/node_modules/semver/classes/comparator.js,sha256=BUIClWQw1j1f9Fmfrgl2DORltInk8LXvXOfMesIRV6w,3631
|
|
1200
1201
|
qalita/_frontend/node_modules/semver/classes/range.js,sha256=z4-OtpNGY64qnQSDjU1xVVQnoqvTyHSO5SGVB5aaa0Y,14977
|
|
@@ -1310,24 +1311,24 @@ qalita/_frontend/public/sources-logos/xls.svg,sha256=ckgO7s5pv9numYYFJboxvGQ_P3f
|
|
|
1310
1311
|
qalita/_frontend/public/sources-logos/xlsx.svg,sha256=ckgO7s5pv9numYYFJboxvGQ_P3fLA1zz-2xiERcz2bE,2044
|
|
1311
1312
|
qalita/_frontend/public/sources-logos/yugabyte-db.png,sha256=-sGA5gqwSIHlGqk6LHNQ5EogsywwKXiWbKXS3PluYKI,5212
|
|
1312
1313
|
qalita/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1313
|
-
qalita/commands/pack.py,sha256=
|
|
1314
|
-
qalita/commands/source.py,sha256=
|
|
1315
|
-
qalita/commands/worker.py,sha256=
|
|
1314
|
+
qalita/commands/pack.py,sha256=ua_QoSz1n7eFfr_f5NFac8mnhgLnCUmjCuEkE-ZQzHo,40292
|
|
1315
|
+
qalita/commands/source.py,sha256=reAeLAchaDu6YT32FmjrDw2X7hlk5c5GtuenMU1591I,31388
|
|
1316
|
+
qalita/commands/worker.py,sha256=Ds-Ih0LNfM8IdwLAXQsyP5ykboL7Lzcx30XcONvrehE,53077
|
|
1316
1317
|
qalita/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1317
1318
|
qalita/internal/config.py,sha256=FVooOC7T-IXOdzl3Q4TPr9ErhqP03QXmRSUo6OhIqIo,4314
|
|
1318
1319
|
qalita/internal/error_patterns.py,sha256=QDFNZEoTmqVOHkiGFljstY6VOhux7d4p8FIL3pKzwtQ,1447
|
|
1319
1320
|
qalita/internal/logger.py,sha256=4xWy2BZII89nUw4tKrLXFPOlVGONm8KUBgzX2XZ3u7g,1868
|
|
1320
|
-
qalita/internal/request.py,sha256=
|
|
1321
|
-
qalita/internal/utils.py,sha256=
|
|
1321
|
+
qalita/internal/request.py,sha256=cHRpinjdbVwxIskLVsqmzrDTyxybflhs_4Hk_SsStqo,6408
|
|
1322
|
+
qalita/internal/utils.py,sha256=AU_2Kl7CINomVIB33r5_rq62qxl6shvx_zJd-stTheU,14014
|
|
1322
1323
|
qalita/web/__init__.py,sha256=NRcQjZSizkqN0a-LutsN7X9LBQ8MhyazXBSuqzUEmXE,62
|
|
1323
1324
|
qalita/web/app.py,sha256=UvAzP7HcwE7E48ZEQViHHgVPE4Xw4TcIgdC7D_4xW4A,4973
|
|
1324
|
-
qalita/web/blueprints/context.py,sha256=
|
|
1325
|
-
qalita/web/blueprints/dashboard.py,sha256
|
|
1325
|
+
qalita/web/blueprints/context.py,sha256=dqaigSBLqyMIq_EI5OJ2I62YMlE2jaVVQgmpph0HqlE,4788
|
|
1326
|
+
qalita/web/blueprints/dashboard.py,sha256=LDYXol53YOut0R_KURQ88GYssD16v6MwkkCzTNo9Ucw,6571
|
|
1326
1327
|
qalita/web/blueprints/helpers.py,sha256=4lSqT01IaWTFkFQB7J2-R5XKKsc4iDAxzeprPhhQIxI,17788
|
|
1327
|
-
qalita/web/blueprints/sources.py,sha256=
|
|
1328
|
-
qalita/web/blueprints/workers.py,sha256=
|
|
1329
|
-
qalita-2.5.
|
|
1330
|
-
qalita-2.5.
|
|
1331
|
-
qalita-2.5.
|
|
1332
|
-
qalita-2.5.
|
|
1333
|
-
qalita-2.5.
|
|
1328
|
+
qalita/web/blueprints/sources.py,sha256=bzdLhwwPbhBzuWw7L6yvOcIT8_cjfvFWp2CGECDPeZo,34771
|
|
1329
|
+
qalita/web/blueprints/workers.py,sha256=5184SrpgT9JPktKZGd2yO4MMdi0SXe220dTGuLA1zLQ,16479
|
|
1330
|
+
qalita-2.5.4.dist-info/METADATA,sha256=Qjk78wFYiwpCOikPA9_lVVxczqIDl7pDxlSDjIuJDZ8,2524
|
|
1331
|
+
qalita-2.5.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
1332
|
+
qalita-2.5.4.dist-info/entry_points.txt,sha256=h_4bMcWq-LKrSLca7IAPQnqlZkxz7BBH8eio4nqZCVU,48
|
|
1333
|
+
qalita-2.5.4.dist-info/licenses/LICENSE,sha256=cZt92dnxw87-VK4HB6KnmYV7mpf4JUdBkAHzFn1kQxM,22458
|
|
1334
|
+
qalita-2.5.4.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license React
|
|
3
|
-
* react-jsx-dev-runtime.production.min.js
|
|
4
|
-
*
|
|
5
|
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
6
|
-
*
|
|
7
|
-
* This source code is licensed under the MIT license found in the
|
|
8
|
-
* LICENSE file in the root directory of this source tree.
|
|
9
|
-
*/
|
|
10
|
-
'use strict';var a=Symbol.for("react.fragment");exports.Fragment=a;exports.jsxDEV=void 0;
|