Files
Jihad-Browser/render/browserserver/Yap/YapServer.cpp
T
NotAlexNoyleandClaude Fable 5.1 62df604c54 Licensing audit: notices for everything bundled, no borrowed marks
The owner: "ensure that all software patent and trademark licenses in
the repo are being respected". A read-only audit of what the packages
carry found no blocker and these gaps, all closed here (the record is
impl-review-findings-6.md, "Licensing audit"):

- NOTICE names the runtime the engine ships with: glibc 2.23 as
  crosstool-NG modifies it (LGPL-2.1), the GCC 9.4.0 runtime (GPL-3.0
  with the Runtime Library Exception), and the eight Debian jessie
  libraries, with the FreeType disclaimer, Lato, the hyphenation
  patterns, and where the corresponding source is. licenses/ gains
  LGPL-2.1, GPL-3.0 and the GCC exception from the pinned tarballs and
  Debian's copyright files from the sysroot; every package ships all of
  licenses/ and CREDITS.md, checked in the payload preflight.
- NOTICE names the modified engine's source (the fork, branch, pinned
  commit, base release, the patches), as MPL-2.0 3.2 asks.
- README and LICENSE no longer call the packages Apache-2.0 and MPL-2.0
  only; the dead licensing.shtml reference is replaced by the UXP
  LICENSE and TRADEMARK files and Moonchild's legal and redist pages.
- Apache-2.0 4(b) change notices on the 29 HP-derived files changed
  since import.
- A drawn magnifier is the DuckDuckGo engines' icon; Google's logo is
  Google's only; the unused Bing logo is gone.
- The engine's services-sync.js defaults pointed at Moonchild's Sync,
  key server and add-on host: blanked at bundle time, checked.
- The "Blazer" keyword, the Goanna-mark tagline, a stray screenshot and
  a .bak dropped; the HP SDK's terms stated where it is downloaded;
  CREDITS records the logo's origin and the screenshot strip's licence.

Deferred to the next engine build: about:credits' URL and
--disable-ffmpeg (the system-libavcodec path is dead on the device).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XM9EDSp83ZFRhggY4RCT9X
2026-10-14 13:57:20 -04:00

500 lines
15 KiB
C++

/* @@@LICENSE
*
* Copyright (c) 2012 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
LICENSE@@@ */
/* Modified by the Jihad Browser project (2026), per Apache-2.0 section 4(b): this file differs from
* HP's isis-project original. The changes are this repository's git history for this file. */
#include <unistd.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <stdint.h>
#include <byteswap.h>
#include <sys/stat.h>
#include <pthread.h>
#include "YapProxy.h"
#include "YapServer.h"
// Jihad Browser: the desktop daemon runs the GLib main loop directly (see
// run() below), so no Qt is needed. Original used QApplication::exec().
static const int kMaxPathLen = 256;
static const char* kSocketPathPrefix = "/tmp/yapserver.";
static const int kMaxConnections = 100;
// Minimum that is good for performance/power reasons and 32-bit (signed) counter overflow
static const int kDeadlockTimeoutMinSecs = 10;
class YapServerDeadlockPriv
{
public:
YapServerDeadlockPriv(int deadlockTimeoutMs = 15000);
~YapServerDeadlockPriv();
void attach(GMainContext *context);
void pause();
void resume();
void increment();
bool isPaused() const;
void setThreadTimeout(const gint intervalInMs);
private:
pthread_t threadId;
gint paused; // accessed by multiple threads
int deadlockTimerSourceTimeoutMs;
gint deadlockThreadTimerSourceTimeoutMs; // accessed by multiple threads
gint watchdogCount; // accessed by multiple threads
GSource* deadlockTimerSource;
GMainContext* threadMainContext;
GMainLoop* threadMainLoop;
GSource* threadTimeoutSource;
static gboolean deadlockTimerCallback(gpointer data);
static void* deadlockThread(void *arg);
static gboolean deadlockThreadTimerCallback(gpointer data);
};
class YapServerPriv
{
public:
char socketPath[kMaxPathLen];
int socketFd;
GMainLoop* mainLoop;
GMainContext* mainCtxt;
GIOChannel* ioChannel;
GSource* ioSource;
static gboolean ioCallback(GIOChannel* channel, GIOCondition condition, void* data);
YapServerDeadlockPriv* deadlockDetector;
};
YapServerDeadlockPriv::YapServerDeadlockPriv(int deadlockTimeoutMs)
: threadId(0)
, paused(0)
, watchdogCount(0)
, deadlockTimerSource(0)
, threadMainContext(0)
, threadMainLoop(0)
, threadTimeoutSource(0)
{
if (deadlockTimeoutMs < kDeadlockTimeoutMinSecs * 1000) {
g_warning("Attempted to set deadlock timeout to less than %d second minimum; "
"using %d second minimum", kDeadlockTimeoutMinSecs, kDeadlockTimeoutMinSecs);
deadlockTimeoutMs = kDeadlockTimeoutMinSecs * 1000;
}
// The deadlock timer running in its separate thread fires more slowly than the
// one on the mainloop so that we avoid timer aliasing
deadlockThreadTimerSourceTimeoutMs = deadlockTimeoutMs; // Thread hasn't started yet, okay to set directly.
deadlockTimerSourceTimeoutMs = deadlockTimeoutMs / 2;
}
YapServerDeadlockPriv::~YapServerDeadlockPriv()
{
// Both members stay zero until the watchdog THREAD starts, and it only starts on the
// first deadlockTimerCallback — half the timeout, so ~7.5 s by default. A server torn
// down before then would quit a null loop and join thread 0.
if (threadMainLoop) g_main_loop_quit(threadMainLoop);
if (threadId) pthread_join(threadId, NULL);
}
void YapServerDeadlockPriv::attach(GMainContext *context)
{
deadlockTimerSource = g_timeout_source_new(deadlockTimerSourceTimeoutMs);
g_source_set_callback(deadlockTimerSource, deadlockTimerCallback, this, NULL);
g_source_set_priority(deadlockTimerSource, G_PRIORITY_HIGH);
g_source_attach(deadlockTimerSource, context);
}
void YapServerDeadlockPriv::pause()
{
g_atomic_int_set(&paused, 1);
}
void YapServerDeadlockPriv::resume()
{
g_atomic_int_set(&paused, 0);
}
void YapServerDeadlockPriv::increment()
{
g_atomic_int_inc(&watchdogCount);
}
bool YapServerDeadlockPriv::isPaused() const
{
return g_atomic_int_get(&paused) ? true : false;
}
void YapServerDeadlockPriv::setThreadTimeout(const gint intervalInMs)
{
if (g_atomic_int_get(&deadlockThreadTimerSourceTimeoutMs) < intervalInMs) {
g_atomic_int_set(&deadlockThreadTimerSourceTimeoutMs, intervalInMs);
}
}
gboolean YapServerDeadlockPriv::deadlockTimerCallback(gpointer data)
{
static bool firstCallback = true;
YapServerDeadlockPriv* d = static_cast<YapServerDeadlockPriv*>(data);
g_atomic_int_inc(&d->watchdogCount);
//g_debug("deadlockTimerCallback!, count: %d", (int)d->watchdogCount);
if (firstCallback) {
pthread_create(&d->threadId, NULL, d->deadlockThread, d);
firstCallback = false;
}
return TRUE;
}
gboolean YapServerDeadlockPriv::deadlockThreadTimerCallback(gpointer data)
{
YapServerDeadlockPriv* d = static_cast<YapServerDeadlockPriv*>(data);
static gint lastCountSeen = 0;
static gint lastInterval = -1;
const gint interval = g_atomic_int_get(&(d->deadlockThreadTimerSourceTimeoutMs));
if (lastInterval < interval) {
if (lastInterval == -1) {
lastInterval = interval;
}
else {
lastInterval = interval;
deadlockThread(d);
return FALSE; // since the interval has changed, wait for next time out to run test;
}
}
// The timers used by glib are monotonic, but count time during
// suspend (see NOV-112456). As a result, both this callback and the mainloop callback
// could fire at the same time and incorrectly think there is a deadlock. To avoid
// this we check the time that has elapsed with the monotonic clock, which doesn't
// count time during suspend.
static int64_t lastTimeMs = 0;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
int64_t nowMs = now.tv_sec * 1000 + now.tv_nsec / 1000000;
if (nowMs - lastTimeMs < interval) {
g_warning("Deadlock detection timer fired too early");
return TRUE;
}
lastTimeMs = nowMs;
if (d->isPaused()) {
//g_debug("deadlockThreadTimerCallback: paused");
return TRUE;
}
gint curCount = g_atomic_int_get(&d->watchdogCount);
//g_debug("deadlockThreadTimerCallback: curCount: %d, lastCountSeen: %d, interval: %d", (int)curCount, (int)lastCountSeen, (int)interval);
if (curCount <= lastCountSeen && curCount != 0) {
// we're wedged so we give up and crash
g_critical("Deadlock detected; aborting!");
abort();
}
lastCountSeen = curCount;
return TRUE;
}
void* YapServerDeadlockPriv::deadlockThread(void *arg)
{
YapServerDeadlockPriv* d = static_cast<YapServerDeadlockPriv*>(arg);
if (d->threadTimeoutSource) {
g_source_unref(d->threadTimeoutSource);
g_source_destroy(d->threadTimeoutSource);
d->threadTimeoutSource = 0;
}
if (!d->threadMainContext) {
d->threadMainContext = g_main_context_new();
}
if (!d->threadMainLoop) {
d->threadMainLoop = g_main_loop_new(d->threadMainContext, FALSE);
}
d->threadTimeoutSource = g_timeout_source_new(g_atomic_int_get (&(d->deadlockThreadTimerSourceTimeoutMs)));
g_source_set_callback(d->threadTimeoutSource, d->deadlockThreadTimerCallback, d, NULL);
g_source_attach(d->threadTimeoutSource, d->threadMainContext);
g_main_loop_run(d->threadMainLoop);
return NULL;
}
gboolean YapServerPriv::ioCallback(GIOChannel* channel, GIOCondition condition, void* data)
{
YapServer* server = static_cast<YapServer*>(data);
server->ioCallback(channel, condition);
return TRUE;
}
YapServer::YapServer(const char* name)
{
d = new YapServerPriv;
d->socketPath[0] = '\0';
d->socketFd = -1;
d->mainLoop = 0;
d->mainCtxt = 0;
d->ioSource = 0;
// Left uninitialized upstream. YapServerPriv is allocated with a plain `new`, so
// this member holds indeterminate memory, and run()'s default deadlockTimeoutMs of
// -1 means the detector is normally NEVER created — yet ~YapServer does
// `if (d->deadlockDetector) delete d->deadlockDetector`. Upstream never noticed
// because its server never returned from run(); ours now does, on SIGTERM, and the
// garbage pointer was reliably non-null: every clean shutdown SIGSEGV'd inside
// ~YapServerDeadlockPriv (measured 2026-08-04, four for four).
d->deadlockDetector = 0;
::snprintf(d->socketPath, G_N_ELEMENTS(d->socketPath), "%s%s", kSocketPathPrefix, name);
::unlink(d->socketPath);
init();
}
YapServer::~YapServer()
{
if (d->deadlockDetector) {
delete d->deadlockDetector;
}
delete d;
}
void YapServer::init()
{
struct sockaddr_un socketAddr;
d->socketFd = ::socket(PF_LOCAL, SOCK_STREAM, 0);
if (d->socketFd < 0) {
fprintf(stderr, "YAP: Failed to create socket: %s\n", strerror(errno));
return;
}
socketAddr.sun_family = AF_LOCAL;
::strncpy(socketAddr.sun_path, d->socketPath, G_N_ELEMENTS(socketAddr.sun_path));
socketAddr.sun_path[G_N_ELEMENTS(socketAddr.sun_path)-1] = '\0';
if (::bind(d->socketFd, (struct sockaddr*) &socketAddr, SUN_LEN(&socketAddr)) != 0) {
fprintf(stderr, "YAP: Failed to bind socket: %s\n", strerror(errno));
d->socketFd = -1;
return;
}
if (::listen(d->socketFd, kMaxConnections) != 0) {
fprintf(stderr, "YAP: Failed to listen on socket: %s\n", strerror(errno));
d->socketFd = -1;
return;
}
d->mainCtxt = g_main_context_default();
d->mainLoop = g_main_loop_new(d->mainCtxt, TRUE);
d->ioChannel = g_io_channel_unix_new(d->socketFd);
d->ioSource = g_io_create_watch(d->ioChannel, (GIOCondition) (G_IO_IN | G_IO_HUP));
g_source_set_callback(d->ioSource, (GSourceFunc) YapServerPriv::ioCallback, this, NULL);
g_source_attach(d->ioSource, d->mainCtxt);
}
GMainLoop* YapServer::mainLoop() const
{
return d->mainLoop;
}
void YapServer::run(int deadlockTimeoutMs)
{
if (!d->mainLoop)
return;
if (deadlockTimeoutMs > 0) {
d->deadlockDetector = new YapServerDeadlockPriv(deadlockTimeoutMs);
if (d->mainCtxt) {
d->deadlockDetector->attach(d->mainCtxt);
}
}
g_main_loop_run(d->mainLoop); // Jihad: GLib loop instead of QApplication::exec()
}
void YapServer::pauseDeadlockDetection()
{
if (d->deadlockDetector) {
d->deadlockDetector->pause();
}
}
void YapServer::resumeDeadlockDetection()
{
if (d->deadlockDetector) {
d->deadlockDetector->resume();
}
}
void YapServer::incrementDeadlockCounter()
{
if (d->deadlockDetector) {
d->deadlockDetector->increment();
}
}
void YapServer::setDeadlockDetectionInterval(const int IntervalInSeconds) {
if (d->deadlockDetector) {
d->deadlockDetector->setThreadTimeout(IntervalInSeconds * 1000);
}
}
void YapServer::ioCallback(GIOChannel* channel, GIOCondition condition)
{
struct sockaddr_un socketAddr;
socklen_t socketAddrLen;
int socketFd = -1;
int16_t msgSocketPathLen = 0;
char* msgSocketPath = 0;
int16_t msgSocketPostfixLen = 0;
char* msgSocketPostfix = 0;
memset(&socketAddr, 0, sizeof(socketAddr));
memset(&socketAddrLen, 0, sizeof(socketAddrLen));
socketFd = ::accept(d->socketFd, (struct sockaddr*) &socketAddr, &socketAddrLen);
if (-1 == socketFd) {
fprintf(stderr, "YAP: Failed to accept inbound connection.\n");
goto Detached;
}
if (!readSocket(socketFd, (char*) &msgSocketPathLen, 2)) {
fprintf(stderr, "YAP: Failed to read message socket name length\n");
goto Detached;
}
msgSocketPathLen = bswap_16(msgSocketPathLen);
// msgSocketPathLen is signed int16: reject negatives (a hostile/corrupt 0xFFFF became -1,
// making malloc(0) then an out-of-bounds write at [-1]) and cap to a sane socket-path length.
if (msgSocketPathLen < 0 || msgSocketPathLen > kMaxPathLen) {
fprintf(stderr, "YAP: bad message socket name length %d\n", msgSocketPathLen);
goto Detached;
}
msgSocketPath = (char*) malloc(msgSocketPathLen + 1);
if (!msgSocketPath) { fprintf(stderr, "YAP: OOM socket name\n"); goto Detached; }
if (!readSocket(socketFd, msgSocketPath, msgSocketPathLen)) {
fprintf(stderr, "YAP: Failed to read message socket name\n");
goto Detached;
}
msgSocketPath[msgSocketPathLen] = 0;
::chmod( msgSocketPath, S_IRWXU | S_IRWXG | S_IRWXO );
if (!readSocket(socketFd, (char*) &msgSocketPostfixLen, 2)) {
fprintf(stderr, "YAP: Failed to read message socket name length\n");
goto Detached;
}
msgSocketPostfixLen = bswap_16(msgSocketPostfixLen);
if (msgSocketPostfixLen < 0 || msgSocketPostfixLen > kMaxPathLen) {
fprintf(stderr, "YAP: bad message postfix length %d\n", msgSocketPostfixLen);
goto Detached;
}
msgSocketPostfix = (char*) malloc(msgSocketPostfixLen + 1);
if (!msgSocketPostfix) { fprintf(stderr, "YAP: OOM postfix name\n"); goto Detached; }
if (!readSocket(socketFd, msgSocketPostfix, msgSocketPostfixLen)) {
fprintf(stderr, "YAP: Failed to read message postifx name\n");
goto Detached;
}
msgSocketPostfix[msgSocketPostfixLen] = 0;
YapProxy* proxy;
proxy = new YapProxy(this, socketFd, msgSocketPath, msgSocketPostfix);
clientConnected(proxy);
free(msgSocketPath);
free(msgSocketPostfix);
return;
Detached:
if (msgSocketPath)
free(msgSocketPath);
if (msgSocketPostfix)
free(msgSocketPostfix);
if (-1 != socketFd) {
close(socketFd);
}
}
/**
* Create a new detached YapProxy that will only record outbound messages and not send
* them.
*
* @return A new YapProxy. The caller now owns this object.
*/
YapProxy* YapServer::createRecordProxy()
{
return new YapProxy(this, -1, NULL, NULL);
}
void YapServer::deleteRecordProxy(YapProxy* proxy)
{
if (proxy != NULL) {
assert(proxy->isRecordProxy());
delete proxy;
}
}
bool YapServer::readSocket(int fd, char* buf, int len)
{
int index = 0;
while (len > 0) {
int count = ::read(fd, &buf[index], len);
if (count <= 0) {
if (errno == EAGAIN || errno == EINTR)
continue;
else {
fprintf(stderr, "Failed to read from socket. Error: %d, %s\n", errno, strerror(errno));
return false;
}
}
index += count;
len -= count;
}
return true;
}
int YapServer::serverSocketFd() const
{
return d->socketFd;
}