Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions devel/212_2703.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 212_2703

## 如何测试
Open Mogan STEM, insert a PS/EPS image, and verify it renders correctly.
To reproduce the bug: use a macOS account whose full name contains a space
(e.g. "John Doe"), then insert a PS/EPS image.

## 2026/02/25 Fix gs path quoting for paths with spaces on macOS

### What
In `gs_utilities.cpp`, replaced `sys_concretize(url)` with `raw_quote(concretize(url))`
for all file paths passed to Ghostscript commands.

### Why
`sys_concretize` backslash-escapes spaces (e.g. `/tmp/path\ with\ spaces/file.eps`),
which the shell handles fine for standalone args. But GS parses option values like
`-sOutputFile=...` as raw strings and does not interpret the backslash, so it splits
at the space. Double-quoting with `raw_quote` fixes this.

### How
Replace `sys_concretize(u)` with `raw_quote(concretize(u))` in `gs_utilities.cpp`.
Same pattern already used in `gs_prefix()` on Windows.
50 changes: 38 additions & 12 deletions src/Plugins/Ghostscript/gs_utilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@
#include "sys_utils.hpp"
#include "tm_file.hpp"

// Quote a path for use in Ghostscript commands on macOS/Linux.
// Wraps in double quotes (to preserve spaces in GS option values like
// -sOutputFile=) and escapes $, backticks, ", and \ so that wordexp
// does not perform variable/command expansion on the result.
static string
gs_path_quote (string s) {
string r= "\"";
int n= N (s);
for (int i= 0; i < n; i++) {
switch (s[i]) {
case '$':
case '`':
case '"':
case '\\':
r << '\\' << s[i];
break;
default:
r << s[i];
}
}
r << "\"";
return r;
}

static string
gs_executable () {
eval ("(use-modules (binary gs))");
Expand Down Expand Up @@ -104,7 +128,7 @@ gs_image_size (url image, int& w_pt, int& h_pt) {
// reading a ps page
// real eps pages with proper bounding boxes have been recognized
// before this and will have their BoundingBox respected
cmd << sys_concretize (image);
cmd << gs_path_quote (concretize (image));
lolly::system::check_stderr (cmd, buf);
if (DEBUG_CONVERT)
debug_convert << "gs cmd :" << cmd << LF << "answer :" << buf;
Expand Down Expand Up @@ -159,18 +183,20 @@ gs_PDFimage_size (url image, int& w_pt, int& h_pt) {
string buf;
string cmd= gs_prefix ();
if (gs_version () >= 9.50)
cmd << "--permit-file-read=" << sys_concretize (image) << " ";
cmd << "--permit-file-read=" << gs_path_quote (concretize (image)) << " ";
cmd << "-dNODISPLAY -q -sFile=";
cmd << sys_concretize (image);
cmd << gs_path_quote (concretize (image));
cmd << " pdf_info.ps";
buf= eval_system (cmd);
if (occurs ("Unrecoverable error", buf)) {
cmd= gs_prefix ();
if (gs_version () >= 9.50)
cmd << "--permit-file-read=" << sys_concretize (image) << " ";
cmd << "--permit-file-read=" << gs_path_quote (concretize (image)) << " ";
cmd << "-dNODISPLAY -q -sFile=";
cmd << sys_concretize (image);
cmd << " " << sys_concretize ("$TEXMACS_PATH/misc/convert/pdf_info.ps");
cmd << gs_path_quote (concretize (image));
cmd << " "
<< gs_path_quote (
concretize (url ("$TEXMACS_PATH/misc/convert/pdf_info.ps")));
buf= eval_system (cmd);
}
if (DEBUG_CONVERT)
Expand Down Expand Up @@ -241,22 +267,22 @@ gs_to_eps (url image,
cmd= gs_prefix ();
cmd << "-dQUIET -dNOPAUSE -dBATCH -dSAFER ";
cmd << "-sDEVICE=" << eps_device ();
cmd << " -sOutputFile=" << sys_concretize (eps) << " ";
cmd << " -sOutputFile=" << gs_path_quote (concretize (eps)) << " ";
if (suffix (image) == "pdf") {
Comment on lines 268 to 271
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raw_quote(concretize(eps)) does not escape $/backticks/" and is therefore not safe to embed in a command string that is later parsed by wordexp (used by lolly::system::call on macOS/Linux). This is a potential command-injection vector via crafted filenames/paths. Please use a shell-safe quoting/escaping routine for paths here (or avoid string-based command parsing altogether).

Copilot uses AI. Check for mistakes.
image_size (image, bx2, by2);
bx1= by1= 0;
cmd << "-dUseCropBox "
<< " -dDEVICEWIDTHPOINTS=" << as_string (bx2)
<< " -dDEVICEHEIGHTPOINTS=" << as_string (by2) << " "
<< sys_concretize (image);
<< gs_path_quote (concretize (image));
}
else {
ps_bounding_box (image, bx1, by1, bx2, by2);
cmd << " -dDEVICEWIDTHPOINTS=" << as_string (bx2 - bx1)
<< " -dDEVICEHEIGHTPOINTS=" << as_string (by2 - by1) << " ";
// don't use -dEPSCrop which works incorrectly if (bx1 != 0 || by1 != 0)
cmd << "-c \" " << as_string (-bx1) << " " << as_string (-by1)
<< " translate gsave \" " << sys_concretize (image)
<< " translate gsave \" " << gs_path_quote (concretize (image))
<< " -c \" grestore \"";
}
string ans= eval_system (cmd);
Expand All @@ -283,8 +309,8 @@ gs_to_ps (url doc, url ps, bool landscape, double paper_h, double paper_w) {
<< " -dDEVICEHEIGHTPOINTS="
<< as_string ((int) (28.36 * paper_h + 0.5));

cmd << " -sOutputFile=" << sys_concretize (ps) << " ";
cmd << sys_concretize (doc);
cmd << " -sOutputFile=" << gs_path_quote (concretize (ps)) << " ";
cmd << gs_path_quote (concretize (doc));
cmd << " -c \"[ /Title (" << as_string (tail (ps)) << ") /DOCINFO pdfmark\" ";

// NOTE: when converting from pdf to ps the title of the document is
Expand All @@ -301,6 +327,6 @@ void
tm_gs (url image) {
string cmd= gs_prefix ();
cmd << "-q -sDEVICE=x11alpha -dBATCH -dNOPAUSE -dSAFER -dNOEPS ";
cmd << sys_concretize (image);
cmd << gs_path_quote (concretize (image));
lolly::system::call (cmd);
Comment on lines 328 to 331
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raw_quote(concretize(image)) is not a safe way to quote a path for execution on macOS/Linux because raw_quote does not escape $/backticks/", and lolly::system::call uses wordexp (command substitution enabled). This allows command substitution if an image path contains $(...)/backticks. Please use a shell-safe quoting/escaping function for paths passed to subprocesses.

Copilot uses AI. Check for mistakes.
}