Merge fix/code-quality-audit into main
This commit is contained in:
@@ -88,9 +88,18 @@ if (UNIX AND NOT APPLE)
|
||||
target_link_libraries(qobuz-qt PRIVATE asound)
|
||||
endif ()
|
||||
|
||||
# Compiler warnings
|
||||
# Compiler warnings + hardening
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
target_compile_options(qobuz-qt PRIVATE -Wall -Wextra -Wno-unused-parameter)
|
||||
target_compile_options(qobuz-qt PRIVATE
|
||||
-Wall -Wextra -Wno-unused-parameter
|
||||
-fstack-protector-strong
|
||||
-D_FORTIFY_SOURCE=2
|
||||
-fPIE
|
||||
)
|
||||
target_link_options(qobuz-qt PRIVATE
|
||||
-pie
|
||||
-Wl,-z,relro,-z,now
|
||||
)
|
||||
endif ()
|
||||
|
||||
# D-Bus
|
||||
|
||||
@@ -31,3 +31,4 @@ toml = "0.8"
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
opt-level = 3
|
||||
overflow-checks = true
|
||||
|
||||
@@ -60,6 +60,8 @@ impl QobuzClient {
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
/// Compute the request signature required by the Qobuz API.
|
||||
/// NOTE: MD5 is mandated by the Qobuz API protocol — not our choice.
|
||||
fn request_sig(&self, method: &str, params: &mut Vec<(&str, String)>, ts: u64) -> String {
|
||||
params.sort_by_key(|(k, _)| *k);
|
||||
let mut s = method.replace('/', "");
|
||||
@@ -116,6 +118,7 @@ impl QobuzClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// NOTE: Qobuz API requires credentials as GET query params — not our choice.
|
||||
async fn oauth2_login(&mut self, email: &str, password: &str) -> Result<OAuthLoginResponse> {
|
||||
let ts = Self::ts();
|
||||
let mut sign_params: Vec<(&str, String)> = vec![
|
||||
|
||||
@@ -96,7 +96,9 @@ pub struct Backend(BackendInner);
|
||||
// ---------- Helpers ----------
|
||||
|
||||
fn call_cb(cb: EventCallback, ud: SendPtr, ev: c_int, json: &str) {
|
||||
let cstr = CString::new(json).unwrap_or_else(|_| CString::new("{}").unwrap());
|
||||
// Strip null bytes that would cause CString::new to fail
|
||||
let safe = json.replace('\0', "");
|
||||
let cstr = CString::new(safe).unwrap_or_else(|_| CString::new("{}").unwrap());
|
||||
unsafe { cb(ud.0, ev, cstr.as_ptr()) };
|
||||
}
|
||||
|
||||
@@ -119,8 +121,14 @@ pub unsafe extern "C" fn qobuz_backend_new(
|
||||
event_cb: EventCallback,
|
||||
userdata: *mut c_void,
|
||||
) -> *mut Backend {
|
||||
let rt = Runtime::new().expect("tokio runtime");
|
||||
let client = Arc::new(Mutex::new(QobuzClient::new().expect("QobuzClient")));
|
||||
let rt = match Runtime::new() {
|
||||
Ok(r) => r,
|
||||
Err(_) => return std::ptr::null_mut(),
|
||||
};
|
||||
let client = match QobuzClient::new() {
|
||||
Ok(c) => Arc::new(Mutex::new(c)),
|
||||
Err(_) => return std::ptr::null_mut(),
|
||||
};
|
||||
let player = Player::new();
|
||||
|
||||
Box::into_raw(Box::new(Backend(BackendInner {
|
||||
|
||||
@@ -8,6 +8,10 @@ QobuzBackend::QobuzBackend(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_backend = qobuz_backend_new(&QobuzBackend::eventTrampoline, this);
|
||||
if (!m_backend) {
|
||||
qCritical("Failed to initialize Qobuz backend");
|
||||
return;
|
||||
}
|
||||
|
||||
m_positionTimer = new QTimer(this);
|
||||
m_positionTimer->setInterval(50);
|
||||
@@ -194,7 +198,12 @@ void QobuzBackend::onPositionTick()
|
||||
|
||||
void QobuzBackend::onEvent(int eventType, const QString &json)
|
||||
{
|
||||
const QJsonObject obj = QJsonDocument::fromJson(json.toUtf8()).object();
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8());
|
||||
if (!doc.isObject()) {
|
||||
emit error(tr("Malformed response from backend"));
|
||||
return;
|
||||
}
|
||||
const QJsonObject obj = doc.object();
|
||||
|
||||
switch (eventType) {
|
||||
case EV_LOGIN_OK:
|
||||
|
||||
@@ -294,7 +294,8 @@ void TrackListModel::sort(int column, Qt::SortOrder order)
|
||||
|
||||
QString TrackListModel::formatDuration(qint64 secs)
|
||||
{
|
||||
const int m = static_cast<int>(secs / 60);
|
||||
const int s = static_cast<int>(secs % 60);
|
||||
if (secs < 0) secs = 0;
|
||||
const qint64 m = secs / 60;
|
||||
const qint64 s = secs % 60;
|
||||
return QStringLiteral("%1:%2").arg(m).arg(s, 2, 10, QLatin1Char('0'));
|
||||
}
|
||||
|
||||
@@ -133,15 +133,22 @@ void ArtistView::setArtist(const QJsonObject &artist)
|
||||
// artist/page: name is {"display": "..."}
|
||||
m_nameLabel->setText(artist["name"].toObject()["display"].toString());
|
||||
|
||||
// biography.content is HTML — strip tags for the summary label
|
||||
// biography.content is HTML — strip tags for safe plain-text display
|
||||
const QString bioHtml = artist["biography"].toObject()["content"].toString();
|
||||
if (!bioHtml.isEmpty()) {
|
||||
// Remove HTML tags for plain-text display
|
||||
QString plain = bioHtml;
|
||||
// Strip HTML entities and tags to prevent rendering injected content
|
||||
plain.remove(QRegularExpression(QStringLiteral("<[^>]*>")));
|
||||
plain.replace(QStringLiteral("&"), QStringLiteral("&"));
|
||||
plain.replace(QStringLiteral("<"), QStringLiteral("<"));
|
||||
plain.replace(QStringLiteral(">"), QStringLiteral(">"));
|
||||
plain.replace(QStringLiteral("""), QStringLiteral("\""));
|
||||
plain.replace(QStringLiteral("'"), QStringLiteral("'"));
|
||||
plain.replace(QStringLiteral(" "), QStringLiteral(" "));
|
||||
plain = plain.trimmed();
|
||||
m_bioLabel->setTextFormat(Qt::PlainText);
|
||||
m_bioLabel->setText(plain);
|
||||
m_bioLabel->setVisible(true);
|
||||
m_bioLabel->setVisible(!plain.isEmpty());
|
||||
} else {
|
||||
m_bioLabel->setVisible(false);
|
||||
}
|
||||
|
||||
@@ -150,8 +150,8 @@ void MainToolBar::setCurrentTrack(const QJsonObject &track)
|
||||
void MainToolBar::updateProgress(quint64 position, quint64 duration)
|
||||
{
|
||||
if (m_seeking) return;
|
||||
const int sliderPos = duration > 0
|
||||
? static_cast<int>(position * 1000 / duration) : 0;
|
||||
const int sliderPos = (duration > 0)
|
||||
? static_cast<int>(qMin(position * 1000 / duration, quint64(1000))) : 0;
|
||||
m_progress->blockSignals(true);
|
||||
m_progress->setValue(sliderPos);
|
||||
m_progress->blockSignals(false);
|
||||
|
||||
Reference in New Issue
Block a user