refactor: get rid of url.parse in core

This commit is contained in:
Barış Soner Uşaklı
2026-02-08 18:52:55 -05:00
parent fb460725ad
commit 157959df60
10 changed files with 37 additions and 33 deletions

View File

@@ -97,7 +97,7 @@
"multer": "2.0.2", "multer": "2.0.2",
"nconf": "0.13.0", "nconf": "0.13.0",
"nodebb-plugin-2factor": "7.6.1", "nodebb-plugin-2factor": "7.6.1",
"nodebb-plugin-composer-default": "10.3.10", "nodebb-plugin-composer-default": "10.3.11",
"nodebb-plugin-dbsearch": "6.3.5", "nodebb-plugin-dbsearch": "6.3.5",
"nodebb-plugin-emoji": "6.0.5", "nodebb-plugin-emoji": "6.0.5",
"nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-emoji-android": "4.1.1",

View File

@@ -2,7 +2,6 @@
const nconf = require('nconf'); const nconf = require('nconf');
const fs = require('fs'); const fs = require('fs');
const url = require('url');
const path = require('path'); const path = require('path');
const { fork } = require('child_process'); const { fork } = require('child_process');
const logrotate = require('logrotate-stream'); const logrotate = require('logrotate-stream');
@@ -140,7 +139,7 @@ function getPorts() {
console.log('[cluster] url is undefined, please check your config.json'); console.log('[cluster] url is undefined, please check your config.json');
process.exit(); process.exit();
} }
const urlObject = url.parse(_url); const urlObject = new URL(_url);
let port = nconf.get('PORT') || nconf.get('port') || urlObject.port || 4567; let port = nconf.get('PORT') || nconf.get('port') || urlObject.port || 4567;
if (!Array.isArray(port)) { if (!Array.isArray(port)) {
port = [port]; port = [port];

View File

@@ -198,10 +198,15 @@ ajaxify.widgets = { render: render };
ajaxify.currentPage = url.split(/[?#]/)[0]; ajaxify.currentPage = url.split(/[?#]/)[0];
ajaxify.requestedPage = null; ajaxify.requestedPage = null;
if (window.history && window.history.pushState) { if (window.history && window.history.pushState) {
const prependSlash = url && !url.startsWith('?') && !url.startsWith('#');
const { relative_path } = config; const { relative_path } = config;
const historyUrl = prependSlash ?
(relative_path + '/' + url) :
relative_path + (url || (relative_path ? '' : '/'));
window.history[!quiet ? 'pushState' : 'replaceState']({ window.history[!quiet ? 'pushState' : 'replaceState']({
url: url, url: url,
}, '', relative_path + (url ? '/' + url : (relative_path ? '' : '/'))); }, '', historyUrl);
} }
}; };
@@ -402,8 +407,11 @@ ajaxify.widgets = { render: render };
}; };
ajaxify.removeRelativePath = function (url) { ajaxify.removeRelativePath = function (url) {
if (url.startsWith(config.relative_path.slice(1))) { if (config.relative_path && url.startsWith(config.relative_path.slice(1))) {
url = url.slice(config.relative_path.length); url = url.slice(config.relative_path.length - 1);
if (url.startsWith('/')) {
url = url.slice(1);
}
} }
return url; return url;
}; };
@@ -565,10 +573,16 @@ $(document).ready(function () {
if (ev !== null && ev.state) { if (ev !== null && ev.state) {
const { returnPath } = ev.state; const { returnPath } = ev.state;
if (ev.state.url === null && returnPath !== undefined) { if (ev.state.url === null && returnPath !== undefined) {
const url = returnPath;
const prependSlash = url && !url.startsWith('?') && !url.startsWith('#');
const { relative_path } = config; const { relative_path } = config;
const historyUrl = prependSlash ?
(relative_path + '/' + url) :
relative_path + (url || (relative_path ? '' : '/'));
window.history.replaceState({ window.history.replaceState({
url: returnPath, url: returnPath,
}, '', relative_path + (returnPath ? '/' + returnPath : (relative_path ? '' : '/'))); }, '', historyUrl);
} else if (ev.state.url !== undefined) { } else if (ev.state.url !== undefined) {
ajaxify.handleTransientElements(); ajaxify.handleTransientElements();
ajaxify.go(ev.state.url, function () { ajaxify.go(ev.state.url, function () {

View File

@@ -1,6 +1,5 @@
'use strict'; 'use strict';
const url = require('url');
const user = require('../user'); const user = require('../user');
const topics = require('../topics'); const topics = require('../topics');
const posts = require('../posts'); const posts = require('../posts');
@@ -29,7 +28,7 @@ exports.buildReqObject = (req, payload) => {
const referer = headers.referer || ''; const referer = headers.referer || '';
if (!host) { if (!host) {
host = url.parse(referer).host || ''; host = new URL(referer).host || '';
} }
return { return {

View File

@@ -1,7 +1,5 @@
'use strict'; 'use strict';
const url = require('url');
const plugins = require('../plugins'); const plugins = require('../plugins');
const meta = require('../meta'); const meta = require('../meta');
const user = require('../user'); const user = require('../user');
@@ -32,19 +30,19 @@ async function rewrite(req, res, next) {
let parsedUrl; let parsedUrl;
try { try {
parsedUrl = url.parse(route, true); parsedUrl = new URL(route, 'http://localhost.com');
} catch (err) { } catch (err) {
return next(err); return next(err);
} }
const { pathname } = parsedUrl; const pathname = parsedUrl.pathname.replace(/^\/+/, '');
const hook = `action:homepage.get:${pathname}`; const hook = `action:homepage.get:${pathname}`;
if (!plugins.hooks.hasListeners(hook)) { if (!plugins.hooks.hasListeners(hook)) {
req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + pathname; req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + pathname;
} else { } else {
res.locals.homePageRoute = pathname; res.locals.homePageRoute = pathname;
} }
req.query = Object.assign(parsedUrl.query, req.query); req.query = Object.assign(Object.fromEntries(parsedUrl.searchParams), req.query);
next(); next();
} }

View File

@@ -217,19 +217,17 @@ async function completeConfigSetup(config) {
} }
// If port is explicitly passed via install vars, use it. Otherwise, glean from url if set. // If port is explicitly passed via install vars, use it. Otherwise, glean from url if set.
const urlObj = url.parse(config.url); const urlObj = new URL(config.url);
if (urlObj.port && (!install.values || !install.values.hasOwnProperty('port'))) { if (urlObj.port && (!install.values || !install.values.hasOwnProperty('port'))) {
config.port = urlObj.port; config.port = urlObj.port;
} }
// Remove trailing slash from non-subfolder installs config.url = urlObj.toString();
if (urlObj.path === '/') { // Remove trailing slash from URL
urlObj.path = ''; if (config.url.endsWith('/')) {
urlObj.pathname = ''; config.url = config.url.slice(0, -1);
} }
config.url = url.format(urlObj);
// ref: https://github.com/indexzero/nconf/issues/300 // ref: https://github.com/indexzero/nconf/issues/300
delete config.type; delete config.type;

View File

@@ -1,7 +1,6 @@
'use strict'; 'use strict';
const nconf = require('nconf'); const nconf = require('nconf');
const url = require('url');
const winston = require('winston'); const winston = require('winston');
const path = require('path'); const path = require('path');
const chalk = require('chalk'); const chalk = require('chalk');
@@ -89,12 +88,13 @@ function loadConfig(configFile) {
if (!nconf.get('sessionKey')) { if (!nconf.get('sessionKey')) {
nconf.set('sessionKey', 'express.sid'); nconf.set('sessionKey', 'express.sid');
} }
const url = nconf.get('url');
if (url) {
const urlObject = new URL(url);
if (nconf.get('url')) { nconf.set('url', url.replace(/\/$/, ''));
nconf.set('url', nconf.get('url').replace(/\/$/, '')); nconf.set('url_parsed', urlObject);
nconf.set('url_parsed', url.parse(nconf.get('url')));
// Parse out the relative_url and other goodies from the configured URL // Parse out the relative_url and other goodies from the configured URL
const urlObject = url.parse(nconf.get('url'));
const relativePath = urlObject.pathname !== '/' ? urlObject.pathname.replace(/\/+$/, '') : ''; const relativePath = urlObject.pathname !== '/' ? urlObject.pathname.replace(/\/+$/, '') : '';
nconf.set('base_url', `${urlObject.protocol}//${urlObject.host}`); nconf.set('base_url', `${urlObject.protocol}//${urlObject.host}`);
nconf.set('secure', urlObject.protocol === 'https:'); nconf.set('secure', urlObject.protocol === 'https:');

View File

@@ -140,7 +140,7 @@ describe('authentication', () => {
it('should regenerate the session identifier on successful login', async () => { it('should regenerate the session identifier on successful login', async () => {
const matchRegexp = /express\.sid=s%3A(.+?);/; const matchRegexp = /express\.sid=s%3A(.+?);/;
const { hostname, path } = url.parse(nconf.get('url')); const { hostname, path } = new URL(nconf.get('url'));
const sid = String(jar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1]; const sid = String(jar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1];
await helpers.logoutUser(jar); await helpers.logoutUser(jar);
const newJar = (await helpers.loginUser('regular', 'regularpwd')).jar; const newJar = (await helpers.loginUser('regular', 'regularpwd')).jar;

View File

@@ -46,7 +46,7 @@ nconf.defaults({
relative_path: '', relative_path: '',
}); });
const urlObject = url.parse(nconf.get('url')); const urlObject = new URL(nconf.get('url'));
const relativePath = urlObject.pathname !== '/' ? urlObject.pathname : ''; const relativePath = urlObject.pathname !== '/' ? urlObject.pathname : '';
nconf.set('relative_path', relativePath); nconf.set('relative_path', relativePath);
nconf.set('asset_base_url', `${relativePath}/assets`); nconf.set('asset_base_url', `${relativePath}/assets`);
@@ -134,9 +134,6 @@ module.exports = db;
before(async function () { before(async function () {
this.timeout(30000); this.timeout(30000);
// Parse out the relative_url and other goodies from the configured URL
const urlObject = url.parse(nconf.get('url'));
nconf.set('core_templates_path', path.join(__dirname, '../../src/views')); nconf.set('core_templates_path', path.join(__dirname, '../../src/views'));
nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-persona/templates')); nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-persona/templates'));
nconf.set('theme_config', path.join(nconf.get('themes_path'), 'nodebb-theme-persona', 'theme.json')); nconf.set('theme_config', path.join(nconf.get('themes_path'), 'nodebb-theme-persona', 'theme.json'));

View File

@@ -1,7 +1,6 @@
'use strict'; 'use strict';
const path = require('path'); const path = require('path');
const url = require('url');
const nconf = require('nconf'); const nconf = require('nconf');
const activePlugins = require('./build/active_plugins.json'); const activePlugins = require('./build/active_plugins.json');
@@ -12,7 +11,7 @@ if (relativePath === undefined) {
file: path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json'), file: path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json'),
}); });
const urlObject = url.parse(nconf.get('url')); const urlObject = new URL(nconf.get('url'));
relativePath = urlObject.pathname !== '/' ? urlObject.pathname.replace(/\/+$/, '') : ''; relativePath = urlObject.pathname !== '/' ? urlObject.pathname.replace(/\/+$/, '') : '';
} }