codex-overleaf-link 1.8.6 → 1.9.0
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.
- package/README.md +45 -21
- package/bin/codex-overleaf-link.mjs +58 -0
- package/extension/assets/icons/codex-overleaf-dialog-icon.png +0 -0
- package/extension/assets/icons/codex-overleaf-icon.png +0 -0
- package/extension/assets/icons/icon128.png +0 -0
- package/extension/assets/icons/icon16.png +0 -0
- package/extension/assets/icons/icon32.png +0 -0
- package/extension/assets/icons/icon48.png +0 -0
- package/extension/bootstrap/background.js +404 -0
- package/extension/bootstrap/manifest.template.json +51 -0
- package/extension/bootstrap/popup.css +26 -0
- package/extension/bootstrap/popup.html +38 -0
- package/extension/bootstrap/popup.js +44 -0
- package/extension/bootstrap/runtimeContext.js +2 -0
- package/extension/runtime-manifest.json +60 -0
- package/extension/src/background.js +532 -0
- package/extension/src/content/applyResultFormatters.js +387 -0
- package/extension/src/content/composerAttachments.js +522 -0
- package/extension/src/content/composerPanel.js +254 -0
- package/extension/src/content/contentRuntime.js +8895 -0
- package/extension/src/content/contextTray.js +686 -0
- package/extension/src/content/diagnosticsController.js +659 -0
- package/extension/src/content/diagnosticsPanel.js +342 -0
- package/extension/src/content/diffReviewPanel.js +797 -0
- package/extension/src/content/localSkillsPanel.js +280 -0
- package/extension/src/content/markdownText.js +606 -0
- package/extension/src/content/mirrorHealth.js +92 -0
- package/extension/src/content/modelPicker.js +489 -0
- package/extension/src/content/nativeChannel.js +62 -0
- package/extension/src/content/otWarmMirror.js +964 -0
- package/extension/src/content/otWarmMirrorController.js +174 -0
- package/extension/src/content/panelMaintenance.js +367 -0
- package/extension/src/content/panelRenderer.js +253 -0
- package/extension/src/content/recentProjects.js +1063 -0
- package/extension/src/content/reviewHunks.js +247 -0
- package/extension/src/content/runController.js +242 -0
- package/extension/src/content/runTimelineView.js +1126 -0
- package/extension/src/content/sessionManager.js +487 -0
- package/extension/src/content/sessionPanel.js +263 -0
- package/extension/src/content/settingsPanel.js +527 -0
- package/extension/src/content/themeController.js +91 -0
- package/extension/src/content/updateIdle.js +67 -0
- package/extension/src/content/writebackController.js +300 -0
- package/extension/src/content/writebackOrchestrator.js +850 -0
- package/extension/src/contentScript.js +8 -0
- package/extension/src/page/compileBridge.js +460 -0
- package/extension/src/page/overleafCapabilities.js +100 -0
- package/extension/src/page/overleafEditor.js +282 -0
- package/extension/src/page/overleafProjectSnapshot.js +234 -0
- package/extension/src/page/overleafRealtimeObserver.js +491 -0
- package/extension/src/page/pageBridgeCapability.js +71 -0
- package/extension/src/page/snapshotRouter.js +948 -0
- package/extension/src/page/trackedChangesLifecycle.js +1506 -0
- package/extension/src/page/treeOperations.js +1584 -0
- package/extension/src/page/writeGuard.js +142 -0
- package/extension/src/page/writebackRouter.js +1934 -0
- package/extension/src/pageBridge.js +2224 -0
- package/extension/src/popup.js +247 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/styles/panel.css +4048 -0
- package/native-host/src/index.js +9 -2
- package/native-host/src/managedInstall.js +393 -0
- package/native-host/src/managedLauncherRuntime.js +71 -0
- package/native-host/src/nativeHostPlatform.js +16 -0
- package/native-host/src/runtimeInstaller.js +6 -1
- package/native-host/src/taskRunnerRuntime.js +8 -0
- package/native-host/src/updateArchive.js +253 -0
- package/native-host/src/updateManager.js +548 -0
- package/native-host/src/updateTrust.js +156 -0
- package/package.json +11 -2
- package/scripts/install-managed.mjs +64 -0
- package/scripts/uninstall-managed.mjs +48 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const zlib = require('node:zlib');
|
|
6
|
+
const { updateError } = require('./updateTrust');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_LIMITS = Object.freeze({
|
|
9
|
+
maxCompressedBytes: 32 * 1024 * 1024,
|
|
10
|
+
maxDecompressedBytes: 96 * 1024 * 1024,
|
|
11
|
+
maxFileBytes: 16 * 1024 * 1024,
|
|
12
|
+
maxFiles: 600
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function extractVerifiedUpdateBundle(options = {}) {
|
|
16
|
+
const archivePath = path.resolve(String(options.archivePath || ''));
|
|
17
|
+
const destinationRoot = path.resolve(String(options.destinationRoot || ''));
|
|
18
|
+
const limits = { ...DEFAULT_LIMITS, ...(options.limits || {}) };
|
|
19
|
+
const stat = fs.statSync(archivePath);
|
|
20
|
+
if (!stat.isFile() || stat.size <= 0 || stat.size > limits.maxCompressedBytes) {
|
|
21
|
+
throw updateError('update_archive_compressed_limit', 'Compressed update bundle exceeds its size limit.');
|
|
22
|
+
}
|
|
23
|
+
assertFreshDestination(destinationRoot);
|
|
24
|
+
|
|
25
|
+
let tar;
|
|
26
|
+
try {
|
|
27
|
+
tar = zlib.gunzipSync(fs.readFileSync(archivePath), {
|
|
28
|
+
maxOutputLength: limits.maxDecompressedBytes
|
|
29
|
+
});
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw updateError('update_archive_invalid', 'Update bundle could not be decompressed.', { cause: error });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const files = [];
|
|
35
|
+
let entryCount = 0;
|
|
36
|
+
let totalBytes = 0;
|
|
37
|
+
let offset = 0;
|
|
38
|
+
while (offset + 512 <= tar.length) {
|
|
39
|
+
const header = tar.subarray(offset, offset + 512);
|
|
40
|
+
offset += 512;
|
|
41
|
+
if (header.every(byte => byte === 0)) {
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
verifyTarChecksum(header);
|
|
45
|
+
const entryPath = parseTarPath(header);
|
|
46
|
+
const type = String.fromCharCode(header[156] || 48);
|
|
47
|
+
const size = parseTarNumber(header.subarray(124, 136));
|
|
48
|
+
if (!Number.isSafeInteger(size) || size < 0 || size > limits.maxFileBytes) {
|
|
49
|
+
throw updateError('update_archive_file_limit', 'Update bundle contains an oversized file.');
|
|
50
|
+
}
|
|
51
|
+
if (!isAllowedUpdatePath(entryPath)) {
|
|
52
|
+
throw updateError('update_archive_path_forbidden', 'Update bundle contains a path outside the runtime allowlist.');
|
|
53
|
+
}
|
|
54
|
+
if (type !== '0' && type !== '\0' && type !== '5') {
|
|
55
|
+
throw updateError('update_archive_type_forbidden', 'Update bundle contains a link or unsupported filesystem entry.');
|
|
56
|
+
}
|
|
57
|
+
if (type === '5' && size !== 0) {
|
|
58
|
+
throw updateError('update_archive_invalid', 'Update bundle directory entry has content.');
|
|
59
|
+
}
|
|
60
|
+
const paddedSize = Math.ceil(size / 512) * 512;
|
|
61
|
+
if (offset + paddedSize > tar.length) {
|
|
62
|
+
throw updateError('update_archive_truncated', 'Update bundle is truncated.');
|
|
63
|
+
}
|
|
64
|
+
if (entryCount >= limits.maxFiles) {
|
|
65
|
+
throw updateError('update_archive_file_count_limit', 'Update bundle contains too many files.');
|
|
66
|
+
}
|
|
67
|
+
entryCount += 1;
|
|
68
|
+
|
|
69
|
+
const target = safeArchiveTarget(destinationRoot, entryPath);
|
|
70
|
+
if (type === '5') {
|
|
71
|
+
fs.mkdirSync(target, { recursive: true, mode: 0o755 });
|
|
72
|
+
} else {
|
|
73
|
+
if (files.includes(entryPath)) {
|
|
74
|
+
throw updateError('update_archive_duplicate_path', 'Update bundle contains a duplicate file path.');
|
|
75
|
+
}
|
|
76
|
+
totalBytes += size;
|
|
77
|
+
if (totalBytes > limits.maxDecompressedBytes) {
|
|
78
|
+
throw updateError('update_archive_total_limit', 'Update bundle content exceeds its total size limit.');
|
|
79
|
+
}
|
|
80
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
81
|
+
fs.writeFileSync(target, tar.subarray(offset, offset + size), { mode: parseSafeMode(header) });
|
|
82
|
+
files.push(entryPath);
|
|
83
|
+
}
|
|
84
|
+
offset += paddedSize;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const required of ['extension-runtime/runtime-manifest.json', 'native-runtime/package.json', 'native-runtime/native-host/src/index.js']) {
|
|
88
|
+
if (!files.includes(required)) {
|
|
89
|
+
throw updateError('update_archive_required_file_missing', 'Update bundle is missing ' + required + '.');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { files: files.sort(), totalBytes };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function createUpdateBundleArchive(options = {}) {
|
|
96
|
+
const outputPath = path.resolve(String(options.outputPath || ''));
|
|
97
|
+
const entries = Array.isArray(options.entries) ? options.entries.slice() : [];
|
|
98
|
+
const seen = new Set();
|
|
99
|
+
const chunks = [];
|
|
100
|
+
for (const entry of entries.sort((left, right) => left.archivePath.localeCompare(right.archivePath))) {
|
|
101
|
+
const archivePath = normalizeArchivePath(entry.archivePath);
|
|
102
|
+
if (!isAllowedUpdatePath(archivePath) || seen.has(archivePath)) {
|
|
103
|
+
throw updateError('update_archive_path_forbidden', 'Invalid or duplicate update bundle path: ' + archivePath);
|
|
104
|
+
}
|
|
105
|
+
seen.add(archivePath);
|
|
106
|
+
const content = fs.readFileSync(entry.sourcePath);
|
|
107
|
+
const mode = Number(entry.mode || fs.statSync(entry.sourcePath).mode) & 0o777;
|
|
108
|
+
chunks.push(createTarHeader(archivePath, content.length, mode || 0o644));
|
|
109
|
+
chunks.push(content);
|
|
110
|
+
const padding = (512 - (content.length % 512)) % 512;
|
|
111
|
+
if (padding) {
|
|
112
|
+
chunks.push(Buffer.alloc(padding));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
chunks.push(Buffer.alloc(1024));
|
|
116
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
117
|
+
fs.writeFileSync(outputPath, zlib.gzipSync(Buffer.concat(chunks), { level: 9, mtime: 0 }));
|
|
118
|
+
return { outputPath, files: [...seen].sort() };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function createTarHeader(entryPath, size, mode) {
|
|
122
|
+
const header = Buffer.alloc(512);
|
|
123
|
+
const { name, prefix } = splitTarPath(entryPath);
|
|
124
|
+
writeTarString(header, 0, 100, name);
|
|
125
|
+
writeTarNumber(header, 100, 8, mode);
|
|
126
|
+
writeTarNumber(header, 108, 8, 0);
|
|
127
|
+
writeTarNumber(header, 116, 8, 0);
|
|
128
|
+
writeTarNumber(header, 124, 12, size);
|
|
129
|
+
writeTarNumber(header, 136, 12, 0);
|
|
130
|
+
header.fill(0x20, 148, 156);
|
|
131
|
+
header[156] = '0'.charCodeAt(0);
|
|
132
|
+
writeTarString(header, 257, 6, 'ustar');
|
|
133
|
+
writeTarString(header, 263, 2, '00');
|
|
134
|
+
writeTarString(header, 345, 155, prefix);
|
|
135
|
+
const checksum = header.reduce((sum, byte) => sum + byte, 0);
|
|
136
|
+
const checksumText = checksum.toString(8).padStart(6, '0') + '\0 ';
|
|
137
|
+
header.write(checksumText, 148, 8, 'ascii');
|
|
138
|
+
return header;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function verifyTarChecksum(header) {
|
|
142
|
+
const expected = parseTarNumber(header.subarray(148, 156));
|
|
143
|
+
const copy = Buffer.from(header);
|
|
144
|
+
copy.fill(0x20, 148, 156);
|
|
145
|
+
const actual = copy.reduce((sum, byte) => sum + byte, 0);
|
|
146
|
+
if (expected !== actual) {
|
|
147
|
+
throw updateError('update_archive_checksum_invalid', 'Update bundle tar header checksum is invalid.');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function parseTarPath(header) {
|
|
152
|
+
const name = parseTarString(header.subarray(0, 100));
|
|
153
|
+
const prefix = parseTarString(header.subarray(345, 500));
|
|
154
|
+
return normalizeArchivePath(prefix ? prefix + '/' + name : name);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function normalizeArchivePath(value) {
|
|
158
|
+
const normalized = String(value || '').replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '');
|
|
159
|
+
if (!normalized || normalized.includes('\0') || normalized.startsWith('/') || /^[a-zA-Z]:/.test(normalized)) {
|
|
160
|
+
throw updateError('update_archive_path_forbidden', 'Update bundle contains an absolute or empty path.');
|
|
161
|
+
}
|
|
162
|
+
const parts = normalized.split('/');
|
|
163
|
+
if (parts.some(part => !part || part === '.' || part === '..')) {
|
|
164
|
+
throw updateError('update_archive_path_forbidden', 'Update bundle contains path traversal.');
|
|
165
|
+
}
|
|
166
|
+
return parts.join('/');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isAllowedUpdatePath(value) {
|
|
170
|
+
let entryPath;
|
|
171
|
+
try {
|
|
172
|
+
entryPath = normalizeArchivePath(value);
|
|
173
|
+
} catch (_error) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return entryPath === 'extension-runtime/runtime-manifest.json'
|
|
177
|
+
|| /^extension-runtime\/src\/.+/.test(entryPath)
|
|
178
|
+
|| /^extension-runtime\/styles\/.+/.test(entryPath)
|
|
179
|
+
|| entryPath === 'native-runtime/package.json'
|
|
180
|
+
|| /^native-runtime\/native-host\/src\/.+/.test(entryPath)
|
|
181
|
+
|| /^native-runtime\/extension\/src\/shared\/.+/.test(entryPath)
|
|
182
|
+
|| /^native-runtime\/scripts\/(?:codex-json-agent|install-native-host|uninstall-native-host)\.mjs$/.test(entryPath);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function safeArchiveTarget(root, entryPath) {
|
|
186
|
+
const target = path.resolve(root, ...entryPath.split('/'));
|
|
187
|
+
const relative = path.relative(root, target);
|
|
188
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
189
|
+
throw updateError('update_archive_path_forbidden', 'Update bundle path escapes the staging directory.');
|
|
190
|
+
}
|
|
191
|
+
return target;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function assertFreshDestination(root) {
|
|
195
|
+
if (fs.existsSync(root)) {
|
|
196
|
+
const stat = fs.lstatSync(root);
|
|
197
|
+
if (stat.isSymbolicLink() || !stat.isDirectory() || fs.readdirSync(root).length) {
|
|
198
|
+
throw updateError('update_staging_not_empty', 'Update staging directory must be a fresh empty directory.');
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function parseTarString(buffer) {
|
|
206
|
+
const zero = buffer.indexOf(0);
|
|
207
|
+
return buffer.subarray(0, zero === -1 ? buffer.length : zero).toString('utf8').trim();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function parseTarNumber(buffer) {
|
|
211
|
+
const value = parseTarString(buffer).replace(/\s/g, '');
|
|
212
|
+
return value ? Number.parseInt(value, 8) : 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function parseSafeMode(header) {
|
|
216
|
+
const mode = parseTarNumber(header.subarray(100, 108)) & 0o777;
|
|
217
|
+
return mode & 0o111 ? 0o755 : 0o644;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function writeTarString(buffer, offset, length, value) {
|
|
221
|
+
const content = Buffer.from(String(value || ''), 'utf8');
|
|
222
|
+
if (content.length > length) {
|
|
223
|
+
throw updateError('update_archive_path_too_long', 'Update bundle path is too long for ustar.');
|
|
224
|
+
}
|
|
225
|
+
content.copy(buffer, offset);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function writeTarNumber(buffer, offset, length, value) {
|
|
229
|
+
const text = Number(value).toString(8).padStart(length - 1, '0') + '\0';
|
|
230
|
+
buffer.write(text, offset, length, 'ascii');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function splitTarPath(entryPath) {
|
|
234
|
+
if (Buffer.byteLength(entryPath) <= 100) {
|
|
235
|
+
return { name: entryPath, prefix: '' };
|
|
236
|
+
}
|
|
237
|
+
for (let index = entryPath.lastIndexOf('/'); index > 0; index = entryPath.lastIndexOf('/', index - 1)) {
|
|
238
|
+
const prefix = entryPath.slice(0, index);
|
|
239
|
+
const name = entryPath.slice(index + 1);
|
|
240
|
+
if (Buffer.byteLength(prefix) <= 155 && Buffer.byteLength(name) <= 100) {
|
|
241
|
+
return { name, prefix };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
throw updateError('update_archive_path_too_long', 'Update bundle path is too long for ustar.');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
module.exports = {
|
|
248
|
+
DEFAULT_LIMITS,
|
|
249
|
+
createUpdateBundleArchive,
|
|
250
|
+
extractVerifiedUpdateBundle,
|
|
251
|
+
isAllowedUpdatePath,
|
|
252
|
+
normalizeArchivePath
|
|
253
|
+
};
|