implemented full response endpoint
This commit is contained in:
@@ -12,7 +12,7 @@ module.exports = async function app(domain){
|
||||
console.log(category)
|
||||
const categoryConverted = await categoryConverter.execute(category)
|
||||
console.log(categoryConverted)
|
||||
return categoryConverted
|
||||
return [categoryConverted]
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ const bodyParser = require('body-parser')
|
||||
const cron = require('./cron')
|
||||
const express = require('express')
|
||||
const { ParseDetailedCategoryUseCase } = require('./use-cases/parse-detailed-category-use-case')
|
||||
const { ParseFullCategoryUseCase } = require('./use-cases/parse-full-category-use-case')
|
||||
const { CategoryConverterUseCase } = require('./use-cases/category-converter-use-case')
|
||||
const categoriesMapping = require('./etc/categories-mapping.json')
|
||||
|
||||
@@ -19,7 +20,8 @@ const HTTP_PORT = process.env.HTTP_PORT
|
||||
|
||||
// Initialize use cases
|
||||
const categoryConverter = new CategoryConverterUseCase({ categoriesMapping })
|
||||
const parseDetailedCategory = new ParseDetailedCategoryUseCase({ categoryConverter })
|
||||
const parseDetailedCategory = new ParseDetailedCategoryUseCase({ categoryConverter })
|
||||
const parseFullCategory = new ParseFullCategoryUseCase({ categoryConverter })
|
||||
|
||||
const server = dgram.createSocket('udp4');
|
||||
|
||||
@@ -90,6 +92,19 @@ httpServer.post('/detailed', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
httpServer.post('/full', async (req, res) => {
|
||||
try {
|
||||
const { fqdn } = req.body
|
||||
|
||||
const fullResult = await parseFullCategory.execute(fqdn)
|
||||
|
||||
res.status(200).json(fullResult)
|
||||
} catch (err) {
|
||||
console.error('Error in /full endpoint:', err)
|
||||
res.status(500).json({ error: err.message || err })
|
||||
}
|
||||
})
|
||||
|
||||
httpServer.listen(HTTP_PORT, () => {
|
||||
console.log('HTTP server listening 3333')
|
||||
console.log('UDP server listening 3334')
|
||||
|
||||
@@ -11,7 +11,7 @@ class CategoryConverterUseCase {
|
||||
execute(category) {
|
||||
const entry = this.categoriesMapping.find(item => item.id === category);
|
||||
|
||||
return entry ? entry.related[0].split(', ').map(str => str.trim()) : null;
|
||||
return entry ? entry.related[0] : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,77 +36,48 @@ class ParseDetailedCategoryUseCase {
|
||||
quotedStrings.push(match[1]);
|
||||
}
|
||||
|
||||
// Split on whitespace
|
||||
const parts = output.trim().split(/\t|\s{2,}/);
|
||||
// Split on tabs (primary separator in NetStar output)
|
||||
const parts = output.trim().split('\t');
|
||||
let quotedIndex = 0;
|
||||
|
||||
// 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
|
||||
// Position mapping based on NetStar output structure:
|
||||
// parts[0]: Categorized
|
||||
// parts[1]: Matching Index
|
||||
// parts[2]: Primary Category ID
|
||||
// parts[3]: Primary Category Name (quoted)
|
||||
// parts[4]: Secondary Category ID
|
||||
// parts[5]: Secondary Category Name (quoted if secondary exists)
|
||||
// parts[6]: Security Category ID
|
||||
// parts[7]: Security Category Name
|
||||
// parts[8]: Reputation Score ID
|
||||
// parts[9]: Reputation Score Name (quoted)
|
||||
// parts[10]: Matching Flag Value
|
||||
// parts[11]: Matching Flag Names
|
||||
// parts[12]: Age Rating ID
|
||||
// parts[13]: Age Rating Name (quoted)
|
||||
|
||||
// Primary ID is parts[2] (always third element after "Categorized" and count)
|
||||
const primary = parts[2];
|
||||
const primaryName = quotedStrings[0];
|
||||
const primaryId = parseInt(parts[2]);
|
||||
const primaryName = quotedStrings[quotedIndex++];
|
||||
const primaryMapped = this.categoryConverter.execute(String(primaryId));
|
||||
|
||||
// Secondary ID is parts[4] (always fifth element)
|
||||
const secondary = parts[4];
|
||||
const secondaryId = parts[4] !== '0' && parts[4] !== '-' ? parseInt(parts[4]) : null;
|
||||
let secondaryName = null;
|
||||
let secondaryMapped = null;
|
||||
|
||||
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++;
|
||||
if (secondaryId !== null) {
|
||||
secondaryName = quotedStrings[quotedIndex++];
|
||||
secondaryMapped = this.categoryConverter.execute(String(secondaryId));
|
||||
} else {
|
||||
secondaryName = parts[5] === '-' ? '-' : parts[5];
|
||||
secondaryName = parts[5] === '-' ? null : 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 = '';
|
||||
const reputationId = parseInt(parts[8]);
|
||||
const reputationName = quotedStrings[quotedIndex++];
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
const ageRatingId = parseInt(parts[12]);
|
||||
const ageRatingName = quotedStrings[quotedIndex++];
|
||||
|
||||
// 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)
|
||||
// Build result array in the same format as the / endpoint
|
||||
const resultArray = [String(primaryMapped)];
|
||||
if (secondaryMapped !== null) {
|
||||
resultArray.push(String(secondaryMapped));
|
||||
@@ -118,9 +89,9 @@ class ParseDetailedCategoryUseCase {
|
||||
primary_name: primaryName,
|
||||
secondary: secondaryMapped,
|
||||
secondary_name: secondaryName,
|
||||
reputation: parseInt(reputation),
|
||||
reputation: reputationId,
|
||||
reputation_name: reputationName,
|
||||
age_rating: parseInt(ageRating),
|
||||
age_rating: ageRatingId,
|
||||
age_rating_name: ageRatingName,
|
||||
raw_output: output.trim()
|
||||
};
|
||||
|
||||
172
src/use-cases/parse-full-category-use-case.js
Normal file
172
src/use-cases/parse-full-category-use-case.js
Normal file
@@ -0,0 +1,172 @@
|
||||
const { exec } = require("node:child_process")
|
||||
|
||||
class ParseFullCategoryUseCase {
|
||||
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) {
|
||||
const quotedStrings = [];
|
||||
const quoteRegex = /"([^"]*)"/g;
|
||||
let match;
|
||||
while ((match = quoteRegex.exec(output)) !== null) {
|
||||
quotedStrings.push(match[1]);
|
||||
}
|
||||
|
||||
const parts = output.trim().split('\t');
|
||||
let quotedIndex = 0;
|
||||
|
||||
// Extract primary category (parts[2], parts[3])
|
||||
const primaryId = parseInt(parts[2]);
|
||||
const primaryName = quotedStrings[quotedIndex++];
|
||||
const primaryMapped = this.categoryConverter.execute(String(primaryId));
|
||||
|
||||
// Extract secondary category (parts[4], parts[5])
|
||||
const secondaryId = parts[4] !== '0' && parts[4] !== '-' ? parseInt(parts[4]) : null;
|
||||
let secondaryName = null;
|
||||
let secondaryMapped = null;
|
||||
if (secondaryId !== null && parts[5] !== '-') {
|
||||
secondaryName = quotedStrings[quotedIndex++];
|
||||
secondaryMapped = this.categoryConverter.execute(String(secondaryId));
|
||||
} else {
|
||||
secondaryName = parts[5] === '-' ? null : parts[5];
|
||||
}
|
||||
|
||||
// Extract security category (parts[6], parts[7])
|
||||
const securityId = parts[6] !== '0' && parts[6] !== '-' ? parseInt(parts[6]) : null;
|
||||
let securityName = null;
|
||||
let securityMapped = null;
|
||||
if (securityId !== null && parts[7] !== '-') {
|
||||
securityName = quotedStrings[quotedIndex++];
|
||||
securityMapped = this.categoryConverter.execute(String(securityId));
|
||||
} else {
|
||||
securityName = parts[7] === '-' ? null : parts[7];
|
||||
}
|
||||
|
||||
// Extract reputation (parts[8], parts[9])
|
||||
const reputationId = parseInt(parts[8]);
|
||||
const reputationName = quotedStrings[quotedIndex++];
|
||||
|
||||
// Extract matching flag (parts[10], parts[11])
|
||||
const matchingFlagValue = parts[10];
|
||||
const matchingFlagNames = parts[11] ? parts[11].split('|') : [];
|
||||
|
||||
// Extract age rating (parts[12], parts[13])
|
||||
const ageRatingId = parseInt(parts[12]);
|
||||
const ageRatingName = quotedStrings[quotedIndex++];
|
||||
|
||||
// Extract category groups starting from parts[16] (after 2 empty fields)
|
||||
// Each group has: id (number), name (quoted string)
|
||||
// Pattern: 0 "Internet/Infrastructure" 0 "Malware/Security" ...
|
||||
const categoryGroups = {
|
||||
internet_infrastructure: {
|
||||
id: parseInt(parts[16]) || 0,
|
||||
name: quotedStrings[quotedIndex++]
|
||||
},
|
||||
malware_security: {
|
||||
id: parseInt(parts[18]) || 0,
|
||||
name: quotedStrings[quotedIndex++]
|
||||
},
|
||||
dangerous_harmful: {
|
||||
id: parseInt(parts[20]) || 0,
|
||||
name: quotedStrings[quotedIndex++]
|
||||
},
|
||||
adult: {
|
||||
id: parseInt(parts[22]) || 0,
|
||||
name: quotedStrings[quotedIndex++]
|
||||
},
|
||||
business_government: {
|
||||
id: parseInt(parts[24]) || 0,
|
||||
name: quotedStrings[quotedIndex++]
|
||||
},
|
||||
personal: {
|
||||
id: parseInt(parts[26]) || 0,
|
||||
name: quotedStrings[quotedIndex++]
|
||||
},
|
||||
computing_technology: {
|
||||
id: parseInt(parts[28]) || 0,
|
||||
name: quotedStrings[quotedIndex++]
|
||||
},
|
||||
social_media: {
|
||||
id: parseInt(parts[30]) || 0,
|
||||
name: quotedStrings[quotedIndex++]
|
||||
},
|
||||
miscellaneous: {
|
||||
id: parseInt(parts[32]) || 0,
|
||||
name: quotedStrings[quotedIndex++]
|
||||
}
|
||||
};
|
||||
|
||||
// Extract volume index and submitted URL
|
||||
const volumeIndex = parts[35];
|
||||
const submittedUrl = parts[36];
|
||||
|
||||
const result = {
|
||||
result_status: parts[0],
|
||||
matching_index: parseInt(parts[1]),
|
||||
primary_category: {
|
||||
id: primaryId,
|
||||
name: primaryName,
|
||||
mapped_id: String(primaryMapped)
|
||||
},
|
||||
secondary_category: {
|
||||
id: secondaryId,
|
||||
name: secondaryName,
|
||||
mapped_id: secondaryMapped ? String(secondaryMapped) : null
|
||||
},
|
||||
security_category: {
|
||||
id: securityId,
|
||||
name: securityName,
|
||||
mapped_id: securityMapped ? String(securityMapped) : null
|
||||
},
|
||||
reputation: {
|
||||
id: reputationId,
|
||||
name: reputationName
|
||||
},
|
||||
matching_flag: {
|
||||
value: matchingFlagValue,
|
||||
names: matchingFlagNames
|
||||
},
|
||||
age_rating: {
|
||||
id: ageRatingId,
|
||||
name: ageRatingName
|
||||
},
|
||||
category_groups: categoryGroups,
|
||||
volume_index: volumeIndex,
|
||||
submitted_url: submittedUrl
|
||||
};
|
||||
|
||||
// Add result array in the same format as / endpoint
|
||||
const resultArray = [result.primary_category.mapped_id];
|
||||
if (result.secondary_category.mapped_id !== null) {
|
||||
resultArray.push(result.secondary_category.mapped_id);
|
||||
}
|
||||
result.result = resultArray;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ParseFullCategoryUseCase }
|
||||
Reference in New Issue
Block a user