Skip to content
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ Merged since v2.5.0, not yet released.
the wheel. ([#154])
- Free-threaded (`cp314t`) wheels are no longer published, because the core is
not yet thread-safe; CPython 3.14 is declared supported. ([#155])
- `listenHTTP[]` binds to `localhost` by default and to the host given in
`listenHTTP[host:port]`; use `listenHTTP[0.0.0.0:8000]` for the network,
which prints a warning.
`systemExec` is refused over HTTP, and the file server stays inside the run
directory and `FOREFIREHOME/tools/htdocs`.

## [v2.5.0] — 2026-08-11

Expand Down
24 changes: 15 additions & 9 deletions src/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace libforefire
double Command::endTime = 0;

bool Command::firstCommand = true;
thread_local bool Command::remoteCommand = false;
size_t Command::refTabs = 0;

FFPoint *Command::lastReadLoc = 0;
Expand Down Expand Up @@ -1184,6 +1185,13 @@ namespace libforefire
}
int Command::systemExec(const string &arg, size_t &numTabs)
{
// No shell commands from the network, even through include[] or @t=.
if (remoteCommand)
{
cout << "systemExec is not available over HTTP" << endl;
return error;
}

SimulationParameters *simParam = SimulationParameters::GetInstance();
string finalStr;

Expand Down Expand Up @@ -3373,7 +3381,9 @@ namespace libforefire
std::cout.rdbuf(captured.rdbuf());
std::string fireCommand = cmd;
// Execute the command. (Assuming ExecuteCommand is defined elsewhere.)
remoteCommand = true;
ExecuteCommand(fireCommand);
remoteCommand = false;

std::cout.rdbuf(oldbuf);
return captured.str();
Expand Down Expand Up @@ -3407,14 +3417,10 @@ namespace libforefire
}
}

char hostname[256];
if (gethostname(hostname, sizeof(hostname)) != 0)
{
cout << "Cannot get hostname info " << endl;
return error;
}

std::string address = arg.empty() ? std::string(hostname) + ":" + std::to_string(port) : arg;
// Local only by default; listenHTTP[0.0.0.0:8000] to open it to the network.
std::string address = arg.empty() ? defaultHostname + ":" + std::to_string(port) : arg;
if (address.compare(0, 5, "host=") == 0)
address = address.substr(5);

// Allocate a new HTTP server if none exists.
if (currentSession.server == 0)
Expand All @@ -3434,7 +3440,7 @@ namespace libforefire
std::ofstream infoFile("httpConnectInfo.txt");
if (infoFile)
{
infoFile << hostname << ":" << port << std::endl;
infoFile << address << std::endl;
infoFile.close();
}
else
Expand Down
3 changes: 3 additions & 0 deletions src/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ class Command {

public:

// True while running a command received over HTTP (or scheduled by one)
static thread_local bool remoteCommand;

// Reference time
static double refTime;

Expand Down
4 changes: 4 additions & 0 deletions src/EventCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace libforefire{

EventCommand::EventCommand(string scommand, double schedueledTime) : ForeFireAtom(schedueledTime) {
schedueledCommand = scommand;
remote = Command::remoteCommand;

};

Expand All @@ -29,7 +30,10 @@ EventCommand::~EventCommand() {};


void EventCommand::update(){
bool caller = Command::remoteCommand;
Command::remoteCommand = remote;
Command::ExecuteCommand(schedueledCommand);
Command::remoteCommand = caller;
}
void EventCommand::timeAdvance(){
setUpdateTime(numeric_limits<double>::infinity());
Expand Down
3 changes: 2 additions & 1 deletion src/EventCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ namespace libforefire{
class EventCommand: public ForeFireAtom {

string schedueledCommand;
bool remote; // scheduled by a command received over HTTP

public:
/*! \brief Default constructor */
EventCommand() : ForeFireAtom(0.) {};
EventCommand() : ForeFireAtom(0.), remote(false) {};
/*! \brief standard constructor */
EventCommand( string, double ) ;
/*! \brief Default destructor */
Expand Down
31 changes: 29 additions & 2 deletions src/HttpCommandServer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>

namespace http_command {
Expand All @@ -47,6 +48,20 @@ namespace http_command {
return false;
}
int port = std::stoi(address.substr(colon + 1));
std::string host = address.substr(0, colon);

// Bind to the requested host only (0.0.0.0 for all interfaces).
addrinfo hints;
std::memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
addrinfo *resolved = nullptr;
if (getaddrinfo(host.c_str(), nullptr, &hints, &resolved) != 0 || resolved == nullptr) {
std::cerr << "Cannot resolve host " << host << std::endl;
return false;
}
in_addr bindAddr = reinterpret_cast<sockaddr_in*>(resolved->ai_addr)->sin_addr;
freeaddrinfo(resolved);

server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
Expand All @@ -59,7 +74,7 @@ namespace http_command {
sockaddr_in addr;
std::memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_addr = bindAddr;
addr.sin_port = htons(port);
if (::bind(server_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
perror("bind");
Expand All @@ -73,10 +88,18 @@ namespace http_command {
server_thread = std::thread(&HttpCommandServer::acceptLoop, this);
std::cout
<< "\033[35m" // start color
<< "ForeFire HTTP command server listening at http://localhost:" << port
<< "ForeFire HTTP command server listening at http://" << host << ":" << port
<< "\033[0m"
<< std::endl;

if ((ntohl(bindAddr.s_addr) >> 24) != 127) {
std::cout
<< "\033[31m" // start red
<< "WARNING: development server, reachable from the network. Anyone who can reach this port can run ForeFire commands. Use listenHTTP[localhost:" << port << "] unless you need remote access."
<< "\033[0m"
<< std::endl;
}

std::cout
<< "\033[33m" // start yellow
<< "To stop the server, press Ctrl-C or type quit in the ForeFire shell."
Expand Down Expand Up @@ -269,6 +292,10 @@ namespace http_command {
if (!path.empty() && path[0] == '/')
path = path.substr(1);

// Only files under the run directory or FOREFIREHOME/tools/htdocs.
if ((!path.empty() && path[0] == '/') || ("/" + path + "/").find("/../") != std::string::npos)
return buildSimpleResponse("403 Forbidden", "Forbidden path: " + uri);

// For an empty path, try current directory's index.html;
// if not available, check the FOREFIREHOME environment variable.
if (path.empty()) {
Expand Down
Loading