Merge pull request #9 from blackdice-cyber/development

Development
This commit is contained in:
Daniel Muniz
2026-04-14 09:15:18 -03:00
committed by GitHub
6 changed files with 492 additions and 9 deletions

View File

@@ -5,14 +5,21 @@ const app = require('./app')
const bodyParser = require('body-parser')
const cron = require('./cron')
const express = require('express')
const { ParseDetailedCategoryUseCase } = require('./use-cases/parse-detailed-category-use-case')
const { CategoryConverterUseCase } = require('./use-cases/category-converter-use-case')
const categoriesMapping = require('./etc/categories-mapping.json')
const httpServer = express()
httpServer.use(bodyParser.json()) // for parsing application/json
httpServer.use(bodyParser.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded
cron()
const UDP_PORT = process.env.UDP_PORT
const HTTP_PORT = process.env.HTTP_PORT
const UDP_PORT = process.env.UDP_PORT
const HTTP_PORT = process.env.HTTP_PORT
// Initialize use cases
const categoryConverter = new CategoryConverterUseCase({ categoriesMapping })
const parseDetailedCategory = new ParseDetailedCategoryUseCase({ categoryConverter })
const server = dgram.createSocket('udp4');
@@ -55,19 +62,32 @@ httpServer.post('/', async (req, res) => {
const { fqdn } = req.body
const categories = await app(fqdn)
let result = {}
if (categories)
if (categories)
result = {
result: categories
}
res.status(200).json(JSON.stringify(result))
} catch (err) {
res.status(500).json(err)
}
})
httpServer.post('/detailed', async (req, res) => {
try {
const { fqdn } = req.body
const detailedResult = await parseDetailedCategory.execute(fqdn)
res.status(200).json(detailedResult)
} catch (err) {
console.error('Error in /detailed endpoint:', err)
res.status(500).json({ error: err.message || err })
}
})
httpServer.listen(HTTP_PORT, () => {

View File

@@ -0,0 +1,130 @@
const { exec } = require("node:child_process")
class ParseDetailedCategoryUseCase {
constructor({ categoryConverter }) {
this.categoryConverter = categoryConverter;
}
execute(domain) {
return new Promise((resolve, reject) => {
exec(`echo ${domain} | bin/gcf1check.sh etc check_categorize_hybrid`,
{ cwd: '/usr/local/gcf1' },
(error, stdout, stderr) => {
if (error) {
console.error(error);
reject(error);
return;
}
try {
const parsed = this.parseOutput(stdout);
resolve(parsed);
} catch (parseError) {
console.error('Parse error:', parseError);
reject(parseError);
}
});
});
}
parseOutput(output) {
// Extract all quoted strings in order
const quotedStrings = [];
const quoteRegex = /"([^"]*)"/g;
let match;
while ((match = quoteRegex.exec(output)) !== null) {
quotedStrings.push(match[1]);
}
// Split on whitespace
const parts = output.trim().split(/\t|\s{2,}/);
// Find numeric IDs by looking for numbers that appear in sequence
// After "Categorized", we have: count, primary_id, secondary_id, ..., reputation_score, ..., age_rating_score
// Primary ID is parts[2] (always third element after "Categorized" and count)
const primary = parts[2];
const primaryName = quotedStrings[0];
// Secondary ID is parts[4] (always fifth element)
const secondary = parts[4];
let secondaryName = '';
let quotedIndex = 1; // Start after primary name
// If secondary is not "0", it has a quoted name
if (secondary !== '0' && secondary !== '-') {
secondaryName = quotedStrings[quotedIndex];
quotedIndex++;
} else {
secondaryName = parts[5] === '-' ? '-' : parts[5];
}
// Find reputation score - it's a single digit that comes after some markers
// Look for the pattern: a digit followed by a quoted reputation name
let reputation = '';
let reputationName = '';
// Scan from parts[7] onwards to find reputation (it's usually around parts[8-11])
for (let i = 7; i < parts.length; i++) {
const part = parts[i];
// Look for single/double digit that's not a category ID
if (!isNaN(part) && part !== '-' && !part.includes('x') && !part.includes('|')) {
const num = parseInt(part);
// Reputation scores are typically 0-5, single digit
if (num >= 0 && num <= 5 && parts[i+1] !== '-' && !parts[i+1].includes('0x')) {
reputation = part;
reputationName = quotedStrings[quotedIndex];
quotedIndex++;
break;
}
}
}
// Find age rating - it's another single digit that comes after more markers
// After reputation, we should find age rating
let ageRating = '';
let ageRatingName = '';
for (let i = 12; i < parts.length; i++) {
const part = parts[i];
if (!isNaN(part) && part !== '-' && !part.includes('x') && !part.includes('|')) {
const num = parseInt(part);
if (num >= 0 && num <= 5) {
// Make sure it's not already used as reputation
if (part !== reputation || i > 10) {
ageRating = part;
ageRatingName = quotedStrings[quotedIndex];
break;
}
}
}
}
// Convert category IDs using the mapper
const primaryMapped = this.categoryConverter.execute(primary);
const secondaryMapped = secondary !== '-' && secondary !== '0' ?
this.categoryConverter.execute(secondary) : null;
// Build result array in the same format as the / endpoint (as strings)
const resultArray = [String(primaryMapped)];
if (secondaryMapped !== null) {
resultArray.push(String(secondaryMapped));
}
return {
result: resultArray,
primary: primaryMapped,
primary_name: primaryName,
secondary: secondaryMapped,
secondary_name: secondaryName,
reputation: parseInt(reputation),
reputation_name: reputationName,
age_rating: parseInt(ageRating),
age_rating_name: ageRatingName,
raw_output: output.trim()
};
}
}
module.exports = { ParseDetailedCategoryUseCase }