@xuda.io/drive_module 1.1.1502 → 1.1.1504
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/index.mjs +84 -3
- package/index_ms.mjs +4 -0
- package/index_msa.mjs +4 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1746,6 +1746,22 @@ export const extract_drive_file = async (req, job_id, headers) => {
|
|
|
1746
1746
|
const ext = path.extname(fileName).toLowerCase();
|
|
1747
1747
|
|
|
1748
1748
|
if (fileName.startsWith('__MACOSX/')) {
|
|
1749
|
+
entry.autodrain();
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// A zip entry name is fully attacker controlled, and this validator used to be
|
|
1754
|
+
// called further down only to decide is_archive, never to reject. That let an entry named
|
|
1755
|
+
// "x.png$(cmd)" through as the doc's file_ext, and file_ext is what ocr_file_to_tmp
|
|
1756
|
+
// appends to the path handed to tesseract, which builds a shell command string.
|
|
1757
|
+
// Directories carry no extension, so only file entries are validated. A rejected
|
|
1758
|
+
// entry has to be drained rather than left unread, or unzipper stalls on the rest
|
|
1759
|
+
// of the archive.
|
|
1760
|
+
const validation_ret = type === 'Directory' ? { valid: false } : file_upload_validator(fileName);
|
|
1761
|
+
|
|
1762
|
+
if (type !== 'Directory' && !validation_ret.valid) {
|
|
1763
|
+
console.warn(`[drive_module] skipped zip entry with unsupported extension: ${path.basename(fileName)}`);
|
|
1764
|
+
entry.autodrain();
|
|
1749
1765
|
return;
|
|
1750
1766
|
}
|
|
1751
1767
|
|
|
@@ -1771,8 +1787,6 @@ export const extract_drive_file = async (req, job_id, headers) => {
|
|
|
1771
1787
|
entry.pipe(fs.createWriteStream(extract_path));
|
|
1772
1788
|
}
|
|
1773
1789
|
|
|
1774
|
-
const validation_ret = file_upload_validator(fileName);
|
|
1775
|
-
|
|
1776
1790
|
let file_doc = {
|
|
1777
1791
|
_id: file_doc_id,
|
|
1778
1792
|
|
|
@@ -2490,9 +2504,21 @@ const detectLanguageByCountry = (cfCountry) => {
|
|
|
2490
2504
|
// HTTP entirely. Only a drive with no bucket entry (a datacenter workspace drive, whose
|
|
2491
2505
|
// files are scp'd to the hosting box) still needs the round trip, and that one now uses a
|
|
2492
2506
|
// URL built the same way the drive listing builds it.
|
|
2507
|
+
// The readers below hand this path to a shell: node-tesseract-ocr joins a command string
|
|
2508
|
+
// and calls child_process.exec, and its double quotes around the path still let $(...) and
|
|
2509
|
+
// backticks expand. The upload validator already rejects a filename whose extension is not
|
|
2510
|
+
// on the allowlist, but this is the sink, so it does not get to assume that. Anything that
|
|
2511
|
+
// is not a plain short alphanumeric extension is dropped instead of passed through; the
|
|
2512
|
+
// readers key off the doc's mime, not this suffix, so losing it costs nothing.
|
|
2513
|
+
const SAFE_OCR_EXT = /^\.[a-z0-9]{1,12}$/;
|
|
2514
|
+
const safe_ocr_ext = (doc) => {
|
|
2515
|
+
const candidate = String(doc.file_ext || path.extname(doc.originalname || '') || '').toLowerCase();
|
|
2516
|
+
return SAFE_OCR_EXT.test(candidate) ? candidate : '';
|
|
2517
|
+
};
|
|
2518
|
+
|
|
2493
2519
|
const ocr_file_to_tmp = async (app_id, doc) => {
|
|
2494
2520
|
const write_tmp = async (buffer) => {
|
|
2495
|
-
const tmp_path = path.join(os.tmpdir(), `xuda_ocr_${await _common.xuda_get_uuid('tmp')}${
|
|
2521
|
+
const tmp_path = path.join(os.tmpdir(), `xuda_ocr_${await _common.xuda_get_uuid('tmp')}${safe_ocr_ext(doc)}`);
|
|
2496
2522
|
await fs.promises.writeFile(tmp_path, buffer);
|
|
2497
2523
|
return tmp_path;
|
|
2498
2524
|
};
|
|
@@ -5159,3 +5185,58 @@ export const drive_set_plan = async function (req) {
|
|
|
5159
5185
|
return { code: -1, data: err.message || String(err) };
|
|
5160
5186
|
}
|
|
5161
5187
|
};
|
|
5188
|
+
|
|
5189
|
+
// ---------------------------------------------------------------------------
|
|
5190
|
+
// PLATFORM ARTIFACTS
|
|
5191
|
+
// ---------------------------------------------------------------------------
|
|
5192
|
+
// Uploads a platform-owned file (not a customer drive file) and returns a URL
|
|
5193
|
+
// that a third party can fetch for a short window. Built for the app bundler:
|
|
5194
|
+
// create_app_bundle writes a tree to a local directory, and a macOS build runs
|
|
5195
|
+
// on Codemagic's machine, so the archive has to be reachable from outside.
|
|
5196
|
+
//
|
|
5197
|
+
// Lives here because drive_module owns the R2 client, the bucket map and the
|
|
5198
|
+
// credentials. Nothing else should be constructing S3 clients.
|
|
5199
|
+
//
|
|
5200
|
+
// SIGNED, not public. There is no r2.public_base configured, so an uploaded
|
|
5201
|
+
// object has no public URL at all, and giving the bucket one would make every
|
|
5202
|
+
// object in it world-readable to solve a problem that is really "let one machine
|
|
5203
|
+
// read one file for ten minutes". A presigned URL keeps the bucket private and
|
|
5204
|
+
// expires on its own.
|
|
5205
|
+
export const upload_platform_artifact = async function ({ file_path, key, expires_in = 900, content_type = 'application/gzip' }) {
|
|
5206
|
+
if (!file_path || !fs.existsSync(file_path)) {
|
|
5207
|
+
return { code: -2, data: `artifact not found: ${file_path}` };
|
|
5208
|
+
}
|
|
5209
|
+
if (!key) return { code: -2, data: 'key is mandatory' };
|
|
5210
|
+
|
|
5211
|
+
const region_host = process.env.XUDA_HOSTNAME;
|
|
5212
|
+
const conf = _conf.storage_bucket?.[region_host];
|
|
5213
|
+
const s3 = _drive_s3_for(region_host);
|
|
5214
|
+
if (!s3 || !conf) return { code: -2, data: `no storage config for ${region_host}` };
|
|
5215
|
+
|
|
5216
|
+
// Imported lazily and on purpose: @aws-sdk/s3-request-presigner is a separate
|
|
5217
|
+
// package from client-s3, so a box that has not installed it would otherwise
|
|
5218
|
+
// fail to LOAD drive_module rather than fail this one call.
|
|
5219
|
+
let getSignedUrl;
|
|
5220
|
+
try {
|
|
5221
|
+
({ getSignedUrl } = await import('@aws-sdk/s3-request-presigner'));
|
|
5222
|
+
} catch (err) {
|
|
5223
|
+
return { code: -2, data: 'missing dependency @aws-sdk/s3-request-presigner, run npm install in cpi/' };
|
|
5224
|
+
}
|
|
5225
|
+
|
|
5226
|
+
const Bucket = conf.name;
|
|
5227
|
+
const Key = `platform_artifacts/${String(key).replace(/^\/+/, '')}`;
|
|
5228
|
+
|
|
5229
|
+
try {
|
|
5230
|
+
const upload = new Upload({
|
|
5231
|
+
client: s3,
|
|
5232
|
+
params: { Bucket, Key, Body: fs.createReadStream(file_path), ContentType: content_type },
|
|
5233
|
+
});
|
|
5234
|
+
await upload.done();
|
|
5235
|
+
|
|
5236
|
+
const url = await getSignedUrl(s3, new GetObjectCommand({ Bucket, Key }), { expiresIn: expires_in });
|
|
5237
|
+
|
|
5238
|
+
return { code: 1, data: { url, key: Key, bucket: Bucket, expires_in, bytes: fs.statSync(file_path).size } };
|
|
5239
|
+
} catch (err) {
|
|
5240
|
+
return { code: -1, data: `artifact upload failed: ${err.message}` };
|
|
5241
|
+
}
|
|
5242
|
+
};
|
package/index_ms.mjs
CHANGED
|
@@ -441,6 +441,10 @@ export const drive_set_plan = async function (...args) {
|
|
|
441
441
|
return await broker.send_to_queue("drive_set_plan", ...args);
|
|
442
442
|
};
|
|
443
443
|
|
|
444
|
+
export const upload_platform_artifact = async function (...args) {
|
|
445
|
+
return await broker.send_to_queue("upload_platform_artifact", ...args);
|
|
446
|
+
};
|
|
447
|
+
|
|
444
448
|
export const get_drive_files_studio = async function (...args) {
|
|
445
449
|
return await broker.send_to_queue("get_drive_files_studio", ...args);
|
|
446
450
|
};
|
package/index_msa.mjs
CHANGED
|
@@ -441,6 +441,10 @@ export const drive_set_plan = function (...args) {
|
|
|
441
441
|
broker.send_to_queue_async("drive_set_plan", ...args);
|
|
442
442
|
};
|
|
443
443
|
|
|
444
|
+
export const upload_platform_artifact = function (...args) {
|
|
445
|
+
broker.send_to_queue_async("upload_platform_artifact", ...args);
|
|
446
|
+
};
|
|
447
|
+
|
|
444
448
|
export const get_drive_files_studio = function (...args) {
|
|
445
449
|
broker.send_to_queue_async("get_drive_files_studio", ...args);
|
|
446
450
|
};
|