0
0
mirror of https://github.com/RSS-Bridge/rss-bridge.git synced 2025-06-29 18:21:07 +00:00

Compare commits

...

4 Commits

Author SHA1 Message Date
Tomasz Molski
23cb9349fc
[CeskaTelevizeBridge] Adjusted getting article timestamp (#4486)
* [CeskaTelevizeBridge] Adjusted getting article timestamp

* [CeskaTelevizeBridge] Removed excess whitespace
2025-03-23 21:30:45 +01:00
Pavel Korytov
05a9ac0f06
[OpenCVEBridge] Rewrite for API change (#4476)
* [OpenCVEBridge] Rewrite for API change

* [OpenCVEBridge] Fix lint
2025-03-23 21:01:21 +01:00
Dan Wainwright
91fe6c1fae
[BazarakiBridge] Add new bridge (#4473)
* [BazarakiBridge] Add new bridge

* fix

---------

Co-authored-by: Dag <me@dvikan.no>
2025-03-23 20:57:17 +01:00
chibicitiberiu
7260f28e10
[RedditBridge] Added time interval and filter for min comment count (#4471)
* Reddit Bridge - added filter for min comment count and time interval.

* [RedditBridge] Add sort by comment count

* lint

* consistent commas

---------

Co-authored-by: Dag <me@dvikan.no>
2025-03-23 20:45:35 +01:00
5 changed files with 486 additions and 135 deletions

139
bridges/BazarakiBridge.php Normal file
View File

@ -0,0 +1,139 @@
<?php
class BazarakiBridge extends BridgeAbstract
{
const NAME = 'Bazaraki Bridge';
const URI = 'https://bazaraki.com';
const DESCRIPTION = 'Fetch adverts from Bazaraki, a Cyprus-based classifieds website.';
const MAINTAINER = 'danwain';
const PARAMETERS = [
[
'url' => [
'name' => 'URL',
'type' => 'text',
'required' => true,
'title' => 'Enter the URL of the Bazaraki page to fetch adverts from.',
'exampleValue' => 'https://www.bazaraki.com/real-estate-for-sale/houses/?lat=0&lng=0&radius=100000',
],
'limit' => [
'name' => 'Limit',
'type' => 'number',
'required' => false,
'title' => 'Enter the number of adverts to fetch. (max 50)',
'exampleValue' => '10',
'defaultValue' => 10,
]
]
];
public function collectData()
{
$url = $this->getInput('url');
if (! str_starts_with($url, 'https://www.bazaraki.com/')) {
throw new \Exception('Nope');
}
$html = getSimpleHTMLDOM($url);
$i = 0;
foreach ($html->find('div.advert') as $element) {
$i++;
if ($i > $this->getInput('limit') || $i > 50) {
break;
}
$item = [];
$item['uri'] = 'https://www.bazaraki.com' . $element->find('a.advert__content-title', 0)->href;
# Get the content
$advert = getSimpleHTMLDOM($item['uri']);
$price = trim($advert->find('div.announcement-price__cost', 0)->plaintext);
$name = trim($element->find('a.advert__content-title', 0)->plaintext);
$item['title'] = $name . ' - ' . $price;
$time = trim($advert->find('span.date-meta', 0)->plaintext);
$time = str_replace('Posted: ', '', $time);
$item['content'] = $this->processAdvertContent($advert);
$item['timestamp'] = $this->convertRelativeTime($time);
$item['author'] = trim($advert->find('div.author-name', 0)->plaintext);
$item['uid'] = $advert->find('span.number-announcement', 0)->plaintext;
$this->items[] = $item;
}
}
/**
* Process the advert content to clean up HTML
*
* @param simple_html_dom $advert The SimpleHTMLDOM object for the advert page
* @return string Processed HTML content
*/
private function processAdvertContent($advert)
{
// Get the content sections
$header = $advert->find('div.announcement-content-header', 0);
$characteristics = $advert->find('div.announcement-characteristics', 0);
$description = $advert->find('div.js-description', 0);
$images = $advert->find('div.announcement__images', 0);
// Remove all favorites divs
foreach ($advert->find('div.announcement-meta__favorites') as $favorites) {
$favorites->outertext = '';
}
// Replace all <a> tags with their text content
foreach ($advert->find('a') as $a) {
$a->outertext = $a->innertext;
}
// Format the content with section headers and dividers
$formattedContent = '';
// Add header section
$formattedContent .= $header->innertext;
$formattedContent .= '<hr/>';
// Add characteristics section with header
$formattedContent .= '<h3>Details</h3>';
$formattedContent .= $characteristics->innertext;
$formattedContent .= '<hr/>';
// Add description section with header
$formattedContent .= '<h3>Description</h3>';
$formattedContent .= $description->innertext;
$formattedContent .= '<hr/>';
// Add images section with header
$formattedContent .= '<h3>Images</h3>';
$formattedContent .= $images->innertext;
return $formattedContent;
}
/**
* Convert relative time strings like "Yesterday 12:32" to proper timestamps
*
* @param string $timeString The relative time string from the website
* @return string Timestamp in a format compatible with strtotime()
*/
private function convertRelativeTime($timeString)
{
if (strpos($timeString, 'Yesterday') !== false) {
// Replace "Yesterday" with actual date
$time = str_replace('Yesterday', date('Y-m-d', strtotime('-1 day')), $timeString);
return date('Y-m-d H:i:s', strtotime($time));
} elseif (strpos($timeString, 'Today') !== false) {
// Replace "Today" with actual date
$time = str_replace('Today', date('Y-m-d'), $timeString);
return date('Y-m-d H:i:s', strtotime($time));
} else {
// For other formats, return as is and let strtotime handle it
return $timeString;
}
}
}

View File

@ -41,11 +41,15 @@ class CeskaTelevizeBridge extends BridgeAbstract
foreach ($html->find('#episodeListSection a[data-testid=card]') as $element) {
$itemContent = $element->find('p[class^=content-]', 0);
$itemDate = $element->find('div[class^=playTime-] span, [data-testid=episode-item-broadcast] span', 0);
// Remove special characters and whitespace
$cleanDate = preg_replace('/[^0-9.]/', '', $itemDate->plaintext);
$item = [
'title' => $this->fixChars($element->find('h3', 0)->plaintext),
'uri' => self::URI . $element->getAttribute('href'),
'content' => '<img src="' . $element->find('img', 0)->getAttribute('srcset') . '" /><br />' . $this->fixChars($itemContent->plaintext),
'timestamp' => $this->getUploadTimeFromString($itemDate->plaintext),
'timestamp' => $this->getUploadTimeFromString($cleanDate),
];
$this->items[] = $item;
@ -58,7 +62,7 @@ class CeskaTelevizeBridge extends BridgeAbstract
return strtotime('today');
} elseif (strpos($string, 'včera') !== false) {
return strtotime('yesterday');
} elseif (!preg_match('/(\d+).\s(\d+).(\s(\d+))?/', $string, $match)) {
} elseif (!preg_match('/(\d+).(\d+).((\d+))?/', $string, $match)) {
returnServerError('Could not get date from Česká televize string');
}

View File

@ -14,8 +14,8 @@ class OpenCVEBridge extends BridgeAbstract
'instance' => [
'name' => 'OpenCVE Instance',
'required' => true,
'defaultValue' => 'https://www.opencve.io',
'exampleValue' => 'https://www.opencve.io'
'defaultValue' => 'https://app.opencve.io',
'exampleValue' => 'https://app.opencve.io'
],
'login' => [
'name' => 'Login',
@ -155,14 +155,14 @@ class OpenCVEBridge extends BridgeAbstract
$titlePrefix = '[' . $queryName . '] ';
}
foreach (json_decode($response) as $cveItem) {
if (array_key_exists($cveItem->id, $fetchedIds)) {
foreach (json_decode($response)->results as $cveItem) {
if (array_key_exists($cveItem->cve_id, $fetchedIds)) {
continue;
}
$fetchedIds[$cveItem->id] = true;
$fetchedIds[$cveItem->cve_id] = true;
$item = [
'uri' => $instance . '/cve/' . $cveItem->id,
'uid' => $cveItem->id,
'uri' => $instance . '/cve/' . $cveItem->cve_id,
'uid' => $cveItem->cve_id,
];
if ($this->getInput('upd_timestamp') == 1) {
$item['timestamp'] = strtotime($cveItem->updated_at);
@ -179,7 +179,7 @@ class OpenCVEBridge extends BridgeAbstract
$item['content'] = $content;
$item['title'] = $title;
} else {
$item['content'] = $cveItem->summary . $this->getLinks($cveItem->id);
$item['content'] = $cveItem->description . $this->getLinks($cveItem->cve_id);
$item['title'] = $this->getTitle($titlePrefix, $cveItem);
}
$this->items[] = $item;
@ -193,17 +193,17 @@ class OpenCVEBridge extends BridgeAbstract
private function getTitle($titlePrefix, $cveItem)
{
$summary = $cveItem->summary;
$summary = $cveItem->description;
$limit = $this->getInput('limit');
if ($limit && mb_strlen($summary) > 100) {
$summary = mb_substr($summary, 0, $limit) + '...';
}
return $titlePrefix . $cveItem->id . '. ' . $summary;
return $titlePrefix . $cveItem->cve_id . '. ' . $summary;
}
private function fetchContents($cveItem, $titlePrefix, $instance, $authHeader)
{
$url = $instance . '/api/cve/' . $cveItem->id;
$url = $instance . '/api/cve/' . $cveItem->cve_id;
$response = getContents($url, [$authHeader]);
$datum = json_decode($response);
@ -211,26 +211,36 @@ class OpenCVEBridge extends BridgeAbstract
$title = $this->getTitleFromDatum($datum, $titlePrefix);
$result = self::CSS;
$result .= '<h1>' . $cveItem->id . '</h1>';
$result .= '<h1>' . $cveItem->cve_id . '</h1>';
$result .= $this->getCVSSLabels($datum);
$result .= '<p>' . $datum->summary . '</p>';
$result .= '<p>' . $datum->description . '</p>';
$result .= <<<EOD
<h3>Information:</h3>
<p>
<ul>
<li><b>Publication date</b>: {$datum->raw_nvd_data->published}
<li><b>Last modified</b>: {$datum->raw_nvd_data->lastModified}
<li><b>Last modified</b>: {$datum->raw_nvd_data->lastModified}
<li><b>Created At</b>: {$datum->created_at}
<li><b>Updated At</b>: {$datum->updated_at}
</ul>
</p>
EOD;
$result .= $this->getV3Table($datum);
$result .= $this->getV2Table($datum);
if (isset($datum->metrics->cvssV4_0->data->vector)) {
$result .= $this->cvssV4VectorToTable($datum->metrics->cvssV4_0->data->vector);
}
$result .= $this->getLinks($datum->id);
$result .= $this->getReferences($datum);
if (isset($datum->metrics->cvssV3_1->data->vector)) {
$result .= $this->cvssV3VectorToTable($datum->metrics->cvssV3_1->data->vector);
}
if (isset($datum->metrics->cvssV3_0->data->vector)) {
$result .= $this->cvssV3VectorToTable($datum->metrics->cvssV3_0->data->vector);
}
if (isset($datum->metrics->cvssV2_0->data->vector)) {
$result .= $this->cvssV2VectorToTable($datum->metrics->cvssV2_0->data->vector);
}
$result .= $this->getLinks($datum->cve_id);
$result .= $this->getVendors($datum);
return [$result, $title];
@ -239,14 +249,20 @@ class OpenCVEBridge extends BridgeAbstract
private function getTitleFromDatum($datum, $titlePrefix)
{
$title = $titlePrefix;
if ($datum->cvss->v3) {
$title .= "[v3: {$datum->cvss->v3}] ";
if (isset($datum->metrics->cvssV4_0->data->score)) {
$title .= "[v4: {$datum->metrics->cvssV4_0->data->score}] ";
}
if ($datum->cvss->v2) {
$title .= "[v2: {$datum->cvss->v2}] ";
if (isset($datum->metrics->cvssV3_1->data->score)) {
$title .= "[v3.1: {$datum->metrics->cvssV3_1->data->score}] ";
}
$title .= $datum->id . '. ';
$titlePostfix = $datum->summary;
if (isset($datum->metrics->cvssV3_0->data->score)) {
$title .= "[v3: {$datum->metrics->cvssV3_0->data->score}] ";
}
if (isset($datum->metrics->cvssV2_0->data->score)) {
$title .= "[v2: {$datum->metrics->cvssV2_0->data->score}] ";
}
$title .= $datum->cve_id . '. ';
$titlePostfix = $datum->description;
$limit = $this->getInput('limit');
if ($limit && mb_strlen($titlePostfix) > 100) {
$titlePostfix = mb_substr($titlePostfix, 0, $limit) + '...';
@ -257,64 +273,49 @@ class OpenCVEBridge extends BridgeAbstract
private function getCVSSLabels($datum)
{
$CVSSv2Text = 'n/a';
$CVSSv2Class = 'cvss-na-color';
if ($datum->cvss->v2) {
$importance = '';
if ($datum->cvss->v2 >= 7) {
$importance = 'HIGH';
$CVSSv2Class = 'cvss-high-color';
} else if ($datum->cvss->v2 >= 4) {
$importance = 'MEDIUM';
$CVSSv2Class = 'cvss-medium-color';
} else {
$importance = 'LOW';
$CVSSv2Class = 'cvss-low-color';
}
$CVSSv2Text = sprintf('[%s] %.1f', $importance, $datum->cvss->v2);
$cvss4 = '';
$cvss31 = '';
$cvss3 = '';
$cvss2 = '';
if (isset($datum->metrics->cvssV4_0->data->score)) {
$cvss4 = $this->formatCVSSLabel($datum->metrics->cvssV4_0->data->score, '4.0', 9, 7, 4);
}
if (isset($datum->metrics->cvssV3_1->data->score)) {
$cvss31 = $this->formatCVSSLabel($datum->metrics->cvssV3_1->data->score, '3.1', 9, 7, 4);
}
if (isset($datum->metrics->cvssV3_0->data->score)) {
$cvss3 = $this->formatCVSSLabel($datum->metrics->cvssV3_0->data->score, '3.0', 9, 7, 4);
}
if (isset($datum->metrics->cvssV2_0->data->score)) {
$cvss2 = $this->formatCVSSLabel($datum->metrics->cvssV2_0->data->score, '2.0', 99, 7, 4);
}
$CVSSv2Item = "<div>CVSS v2: </div><div class=\"label {$CVSSv2Class}\">{$CVSSv2Text}</div>";
$CVSSv3Text = 'n/a';
$CVSSv3Class = 'cvss-na-color';
if ($datum->cvss->v3) {
$importance = '';
if ($datum->cvss->v3 >= 9) {
$importance = 'CRITICAL';
$CVSSv3Class = 'cvss-crit-color';
} else if ($datum->cvss->v3 >= 7) {
$importance = 'HIGH';
$CVSSv3Class = 'cvss-high-color';
} else if ($datum->cvss->v3 >= 4) {
$importance = 'MEDIUM';
$CVSSv3Class = 'cvss-medium-color';
} else {
$importance = 'LOW';
$CVSSv3Class = 'cvss-low-color';
}
$CVSSv3Text = sprintf('[%s] %.1f', $importance, $datum->cvss->v3);
}
$CVSSv3Item = "<div>CVSS v3: </div><div class=\"label {$CVSSv3Class}\">{$CVSSv3Text}</div>";
return '<div class="labels-row">' . $CVSSv3Item . $CVSSv2Item . '</div>';
return '<div class="labels-row">' . $cvss4 . $cvss31 . $cvss3 . $cvss2 . '</div>';
}
private function getReferences($datum)
private function formatCVSSLabel($score, $version, $critical_thr, $high_thr, $medium_thr)
{
if (count($datum->raw_nvd_data->references) == 0) {
return '';
}
$res = '<h3>References:</h3> <p><ul>';
foreach ($datum->raw_nvd_data->references as $ref) {
$item = '<li>';
if (isset($ref->tags) && count($ref->tags) > 0) {
$item .= '[' . implode(', ', $ref->tags) . '] ';
$text = 'n/a';
$class = 'cvss-na-color';
if ($score) {
$importance = '';
if ($score >= $critical_thr) {
$importance = 'CRITICAL';
$class = 'cvss-crit-color';
} else if ($score >= $high_thr) {
$importance = 'HIGH';
$class = 'cvss-high-color';
} else if ($score >= $medium_thr) {
$importance = 'MEDIUM';
$class = 'cvss-medium-color';
} else {
$importance = 'LOW';
$class = 'cvss-low-color';
}
$item .= "<a href=\"{$ref->url}\">{$ref->url}</a>";
$item .= '<li>';
$res .= $item;
$text = sprintf('[%s] %.1f', $importance, $score);
}
$res .= '</p></ul>';
return $res;
$item = "<div>CVSS {$version}: </div><div class=\"label {$class}\">{$text}</div>";
return $item;
}
private function getLinks($id)
@ -331,84 +332,253 @@ class OpenCVEBridge extends BridgeAbstract
EOD;
}
private function getV3Table($datum)
private function cvssV3VectorToTable($cvssVector)
{
$metrics = $datum->raw_nvd_data->metrics;
if (!isset($metrics->cvssMetricV31) || count($metrics->cvssMetricV31) == 0) {
return '';
$vectorComponents = [];
$parts = explode('/', $cvssVector);
if (!preg_match('/^CVSS:3\.[01]/', $parts[0])) {
return 'Error: Not a valid CVSS v3.0 or v3.1 vector';
}
$v3 = $metrics->cvssMetricV31[0];
$data = $v3->cvssData;
return <<<EOD
<div class="cvss-table">
for ($i = 1; $i < count($parts); $i++) {
$component = explode(':', $parts[$i]);
if (count($component) == 2) {
$vectorComponents[$component[0]] = $component[1];
}
}
$readableNames = [
'AV' => ['N' => 'Network', 'A' => 'Adjacent', 'L' => 'Local', 'P' => 'Physical'],
'AC' => ['L' => 'Low', 'H' => 'High'],
'PR' => ['N' => 'None', 'L' => 'Low', 'H' => 'High'],
'UI' => ['N' => 'None', 'R' => 'Required'],
'S' => ['U' => 'Unchanged', 'C' => 'Changed'],
'C' => ['N' => 'None', 'L' => 'Low', 'H' => 'High'],
'I' => ['N' => 'None', 'L' => 'Low', 'H' => 'High'],
'A' => ['N' => 'None', 'L' => 'Low', 'H' => 'High']
];
$data = new stdClass();
$data->attackVector = isset($readableNames['AV'][$vectorComponents['AV']]) ? $readableNames['AV'][$vectorComponents['AV']] : 'Unknown';
$data->attackComplexity = isset($readableNames['AC'][$vectorComponents['AC']]) ? $readableNames['AC'][$vectorComponents['AC']] : 'Unknown';
$data->privilegesRequired = isset($readableNames['PR'][$vectorComponents['PR']]) ? $readableNames['PR'][$vectorComponents['PR']] : 'Unknown';
$data->userInteraction = isset($readableNames['UI'][$vectorComponents['UI']]) ? $readableNames['UI'][$vectorComponents['UI']] : 'Unknown';
$data->scope = isset($readableNames['S'][$vectorComponents['S']]) ? $readableNames['S'][$vectorComponents['S']] : 'Unknown';
$data->confidentialityImpact = isset($readableNames['C'][$vectorComponents['C']]) ? $readableNames['C'][$vectorComponents['C']] : 'Unknown';
$data->integrityImpact = isset($readableNames['I'][$vectorComponents['I']]) ? $readableNames['I'][$vectorComponents['I']] : 'Unknown';
$data->availabilityImpact = isset($readableNames['A'][$vectorComponents['A']]) ? $readableNames['A'][$vectorComponents['A']] : 'Unknown';
$html = '<div class="cvss-table">
<h3>CVSS v3 details</h3>
<table>
<tr>
<td>Impact score</td><td>{$v3->impactScore}</td>
<td>Exploitability score</td><td>{$v3->exploitabilityScore}</td>
<td>Attack vector</td><td>' . $data->attackVector . '</td>
<td>Confidentiality Impact</td><td>' . $data->confidentialityImpact . '</td>
</tr>
<tr>
<td>Attack vector</td><td>{$data->attackVector}</td>
<td>Confidentiality Impact</td><td>{$data->confidentialityImpact}</td>
<td>Attack complexity</td><td>' . $data->attackComplexity . '</td>
<td>Integrity Impact</td><td>' . $data->integrityImpact . '</td>
</tr>
<tr>
<td>Attack complexity</td><td>{$data->attackComplexity}</td>
<td>Integrity Impact</td><td>{$data->integrityImpact}</td>
<td>Privileges Required</td><td>' . $data->privilegesRequired . '</td>
<td>Availability Impact</td><td>' . $data->availabilityImpact . '</td>
</tr>
<tr>
<td>Privileges Required</td><td>{$data->privilegesRequired}</td>
<td>Availability Impact</td><td>{$data->availabilityImpact}</td>
</tr>
<tr>
<td>User Interaction</td><td>{$data->userInteraction}</td>
<td>Scope</td><td>{$data->scope}</td>
<td>User Interaction</td><td>' . $data->userInteraction . '</td>
<td>Scope</td><td>' . $data->scope . '</td>
</tr>
</table>
</div>
EOD;
</div>';
return $html;
}
private function getV2Table($datum)
private function cvssV2VectorToTable($cvssVector)
{
$metrics = $datum->raw_nvd_data->metrics;
if (!isset($metrics->cvssMetricV2) || count($metrics->cvssMetricV2) == 0) {
return '';
$vectorComponents = [];
$parts = explode('/', $cvssVector);
foreach ($parts as $part) {
$component = explode(':', $part);
if (count($component) == 2) {
$vectorComponents[$component[0]] = $component[1];
}
}
$v2 = $metrics->cvssMetricV2[0];
$data = $v2->cvssData;
return <<<EOD
<div class="cvss-table">
$readableNames = [
'AV' => ['L' => 'Local', 'A' => 'Adjacent Network', 'N' => 'Network'],
'AC' => ['H' => 'High', 'M' => 'Medium', 'L' => 'Low'],
'Au' => ['M' => 'Multiple', 'S' => 'Single', 'N' => 'None'],
'C' => ['N' => 'None', 'P' => 'Partial', 'C' => 'Complete'],
'I' => ['N' => 'None', 'P' => 'Partial', 'C' => 'Complete'],
'A' => ['N' => 'None', 'P' => 'Partial', 'C' => 'Complete']
];
$metricValues = [
'AV' => ['L' => 0.395, 'A' => 0.646, 'N' => 1.0],
'AC' => ['H' => 0.35, 'M' => 0.61, 'L' => 0.71],
'Au' => ['M' => 0.45, 'S' => 0.56, 'N' => 0.704],
'C' => ['N' => 0, 'P' => 0.275, 'C' => 0.660],
'I' => ['N' => 0, 'P' => 0.275, 'C' => 0.660],
'A' => ['N' => 0, 'P' => 0.275, 'C' => 0.660]
];
$confImpact = isset($metricValues['C'][$vectorComponents['C']]) ? $metricValues['C'][$vectorComponents['C']] : 0;
$integImpact = isset($metricValues['I'][$vectorComponents['I']]) ? $metricValues['I'][$vectorComponents['I']] : 0;
$availImpact = isset($metricValues['A'][$vectorComponents['A']]) ? $metricValues['A'][$vectorComponents['A']] : 0;
$impact = 10.41 * (1 - (1 - $confImpact) * (1 - $integImpact) * (1 - $availImpact));
$av = isset($metricValues['AV'][$vectorComponents['AV']]) ? $metricValues['AV'][$vectorComponents['AV']] : 0;
$ac = isset($metricValues['AC'][$vectorComponents['AC']]) ? $metricValues['AC'][$vectorComponents['AC']] : 0;
$au = isset($metricValues['Au'][$vectorComponents['Au']]) ? $metricValues['Au'][$vectorComponents['Au']] : 0;
$exploitability = 20 * $av * $ac * $au;
$impact = round($impact, 1);
$exploitability = round($exploitability, 1);
$data = new stdClass();
$data->accessVector = isset($readableNames['AV'][$vectorComponents['AV']]) ? $readableNames['AV'][$vectorComponents['AV']] : 'Unknown';
$data->accessComplexity = isset($readableNames['AC'][$vectorComponents['AC']]) ? $readableNames['AC'][$vectorComponents['AC']] : 'Unknown';
$data->authentication = isset($readableNames['Au'][$vectorComponents['Au']]) ? $readableNames['Au'][$vectorComponents['Au']] : 'Unknown';
$data->confidentialityImpact = isset($readableNames['C'][$vectorComponents['C']]) ? $readableNames['C'][$vectorComponents['C']] : 'Unknown';
$data->integrityImpact = isset($readableNames['I'][$vectorComponents['I']]) ? $readableNames['I'][$vectorComponents['I']] : 'Unknown';
$data->availabilityImpact = isset($readableNames['A'][$vectorComponents['A']]) ? $readableNames['A'][$vectorComponents['A']] : 'Unknown';
$v2 = new stdClass();
$v2->impactScore = $impact;
$v2->exploitabilityScore = $exploitability;
$html = '<div class="cvss-table">
<h3>CVSS v2 details</h3>
<table>
<tr>
<td>Impact score</td><td>{$v2->impactScore}</td>
<td>Exploitability score</td><td>{$v2->exploitabilityScore}</td>
<td>Impact score</td><td>' . $v2->impactScore . '</td>
<td>Exploitability score</td><td>' . $v2->exploitabilityScore . '</td>
</tr>
<tr>
<td>Access Vector</td><td>{$data->accessVector}</td>
<td>Confidentiality Impact</td><td>{$data->confidentialityImpact}</td>
<td>Access Vector</td><td>' . $data->accessVector . '</td>
<td>Confidentiality Impact</td><td>' . $data->confidentialityImpact . '</td>
</tr>
<tr>
<td>Access Complexity</td><td>{$data->accessComplexity}</td>
<td>Integrity Impact</td><td>{$data->integrityImpact}</td>
<td>Access Complexity</td><td>' . $data->accessComplexity . '</td>
<td>Integrity Impact</td><td>' . $data->integrityImpact . '</td>
</tr>
<tr>
<td>Authentication</td><td>{$data->authentication}</td>
<td>Availability Impact</td><td>{$data->availabilityImpact}</td>
<td>Authentication</td><td>' . $data->authentication . '</td>
<td>Availability Impact</td><td>' . $data->availabilityImpact . '</td>
</tr>
<tr>
</table>
</div>
EOD;
</div>';
return $html;
}
private function cvssV4VectorToTable($cvssVector)
{
$vectorComponents = [];
$parts = explode('/', $cvssVector);
if (!preg_match('/^CVSS:4\.0/', $parts[0])) {
return 'Error: Not a valid CVSS v4.0 vector';
}
for ($i = 1; $i < count($parts); $i++) {
$component = explode(':', $parts[$i]);
if (count($component) == 2) {
$vectorComponents[$component[0]] = $component[1];
}
}
$readableNames = [
'AV' => ['N' => 'Network', 'A' => 'Adjacent', 'L' => 'Local', 'P' => 'Physical'],
'AC' => ['L' => 'Low', 'H' => 'High'],
'AT' => ['N' => 'None', 'P' => 'Present'],
'PR' => ['N' => 'None', 'L' => 'Low', 'H' => 'High'],
'UI' => ['N' => 'None', 'P' => 'Passive', 'A' => 'Active'],
'VC' => ['N' => 'None', 'L' => 'Low', 'H' => 'High'],
'VI' => ['N' => 'None', 'L' => 'Low', 'H' => 'High'],
'VA' => ['N' => 'None', 'L' => 'Low', 'H' => 'High'],
'SC' => ['N' => 'None', 'L' => 'Low', 'H' => 'High'],
'SI' => ['N' => 'None', 'L' => 'Low', 'H' => 'High'],
'SA' => ['N' => 'None', 'L' => 'Low', 'H' => 'High']
];
$data = new stdClass();
$data->attackVector = isset($readableNames['AV'][$vectorComponents['AV']]) ? $readableNames['AV'][$vectorComponents['AV']] : 'Unknown';
$data->attackComplexity = isset($readableNames['AC'][$vectorComponents['AC']]) ? $readableNames['AC'][$vectorComponents['AC']] : 'Unknown';
$data->privilegesRequired = isset($readableNames['PR'][$vectorComponents['PR']]) ? $readableNames['PR'][$vectorComponents['PR']] : 'Unknown';
$data->attackRequirements = isset($readableNames['AT'][$vectorComponents['AT']]) ? $readableNames['AT'][$vectorComponents['AT']] : 'Unknown';
$data->userInteraction = isset($readableNames['UI'][$vectorComponents['UI']]) ? $readableNames['UI'][$vectorComponents['UI']] : 'Unknown';
$data->confidentialityImpact = isset($readableNames['VC'][$vectorComponents['VC']]) ? $readableNames['VC'][$vectorComponents['VC']] : 'Unknown';
$data->integrityImpact = isset($readableNames['VI'][$vectorComponents['VI']]) ? $readableNames['VI'][$vectorComponents['VI']] : 'Unknown';
$data->availabilityImpact = isset($readableNames['VA'][$vectorComponents['VA']]) ? $readableNames['VA'][$vectorComponents['VA']] : 'Unknown';
$data->confidentialityImpactS = isset($readableNames['SC'][$vectorComponents['SC']]) ? $readableNames['SC'][$vectorComponents['SC']] : 'Unknown';
$data->integrityImpactS = isset($readableNames['SI'][$vectorComponents['SI']]) ? $readableNames['SI'][$vectorComponents['SI']] : 'Unknown';
$data->availabilityImpactS = isset($readableNames['SA'][$vectorComponents['SA']]) ? $readableNames['SA'][$vectorComponents['SA']] : 'Unknown';
$html = '<div class="cvss-table">
<h3>CVSS v4.0 details</h3>
<table>
<tr>
<td>Attack vector</td><td>' . $data->attackVector . '</td>
<td>Vulnerable System Confidentiality Impact</td><td>' . $data->confidentialityImpact . '</td>
</tr>
<tr>
<td>Attack complexity</td><td>' . $data->attackComplexity . '</td>
<td>Vulnerable System Integrity Impact</td><td>' . $data->integrityImpact . '</td>
</tr>
<tr>
<td>Privileges Required</td><td>' . $data->privilegesRequired . '</td>
<td>Vulnerable System Availability Impact</td><td>' . $data->availabilityImpact . '</td>
</tr>
<tr>
<td>Attack Requirements</td><td>' . $data->attackRequirements . '</td>
<td>Subsequent System Confidentiality Impact</td><td>' . $data->confidentialityImpactS . '</td>
</tr>
<tr>
<td>User Interaction</td><td>' . $data->userInteraction . '</td>
<td>Subsequent System Integrity Impact</td><td>' . $data->integrityImpactS . '</td>
</tr>
<tr>
<td></td><td></td>
<td>Subsequent System Avaliablity Impact</td><td>' . $data->availabilityImpactS . '</td>
</tr>
</table>
</div>';
return $html;
}
private function getVendors($datum)
{
if (count((array)$datum->vendors) == 0) {
return '';
}
$vendor_data = [];
foreach ($datum->vendors as $vendor_str) {
$pieces = explode('$PRODUCT$', $vendor_str);
if (count($pieces) == 1) {
$vendor = $pieces[0];
if (!array_key_exists($vendor, $vendor_data)) {
$vendor_data[$vendor] = [];
}
} else {
$vendor = $pieces[0];
$product = $pieces[1];
if (!array_key_exists($vendor, $vendor_data)) {
$vendor_data[$vendor] = [];
}
array_push($vendor_data[$vendor], $product);
}
}
$res = '<h3>Affected products</h3><p><ul>';
foreach ($datum->vendors as $vendor => $products) {
foreach ($vendor_data as $vendor => $products) {
$res .= "<li>{$vendor}";
if (count($products) > 0) {
$res .= '<ul>';
@ -420,5 +590,6 @@ class OpenCVEBridge extends BridgeAbstract
$res .= '</li>';
}
$res .= '</ul></p>';
return $res;
}
}

View File

@ -20,7 +20,15 @@ class RedditBridge extends BridgeAbstract
'required' => false,
'type' => 'number',
'exampleValue' => 100,
'title' => 'Filter out posts with lower score'
'title' => 'Filter out posts with lower score. Set to -1 to disable. If both score and comments are set, an OR is applied.',
],
'min_comments' => [
'name' => 'Minimal number of comments',
'required' => false,
'type' => 'number',
'exampleValue' => 100,
'title' => 'Filter out posts with lower number of comments. Set to -1 to disable. If both score and comments are set, an OR is applied.',
'defaultValue' => -1
],
'd' => [
'name' => 'Sort By',
@ -30,10 +38,25 @@ class RedditBridge extends BridgeAbstract
'Hot' => 'hot',
'Relevance' => 'relevance',
'New' => 'new',
'Top' => 'top'
'Top' => 'top',
'Comments' => 'comments',
],
'defaultValue' => 'Hot'
],
't' => [
'name' => 'Time',
'type' => 'list',
'title' => 'Sort by new, hot, top or relevancy',
'values' => [
'All' => 'all',
'Year' => 'year',
'Month' => 'month',
'Week' => 'week',
'Day' => 'day',
'Hour' => 'hour',
],
'defaultValue' => 'week'
],
'search' => [
'name' => 'Keyword search',
'required' => false,
@ -126,6 +149,7 @@ class RedditBridge extends BridgeAbstract
$frontend = 'https://old.reddit.com';
}
$section = $this->getInput('d');
$time = $this->getInput('t');
switch ($this->queriedContext) {
case 'single':
@ -147,7 +171,7 @@ class RedditBridge extends BridgeAbstract
foreach ($subreddits as $subreddit) {
$version = 'v0.0.2';
$useragent = "rss-bridge $version (https://github.com/RSS-Bridge/rss-bridge)";
$url = self::createUrl($search, $flareInput, $subreddit, $user, $section, $this->queriedContext);
$url = self::createUrl($search, $flareInput, $subreddit, $user, $section, $time, $this->queriedContext);
$response = getContents($url, ['User-Agent: ' . $useragent], [], true);
@ -162,8 +186,20 @@ class RedditBridge extends BridgeAbstract
$data = $post->data;
if ($data->score < $this->getInput('score')) {
continue;
$min_score = $this->getInput('score');
$min_comments = $this->getInput('min_comments');
if ($min_score >= 0 && $min_comments >= 0) {
if ($data->num_comments < $min_comments || $data->score < $min_score) {
continue;
}
} elseif ($min_score >= 0) {
if ($data->score < $min_score) {
continue;
}
} elseif ($min_comments >= 0) {
if ($data->num_comments < $min_comments) {
continue;
}
}
$item = [];
@ -264,7 +300,7 @@ class RedditBridge extends BridgeAbstract
});
}
public static function createUrl($search, $flareInput, $subreddit, bool $user, $section, $queriedContext): string
public static function createUrl($search, $flareInput, $subreddit, bool $user, $section, $time, $queriedContext): string
{
if ($search === '') {
$keywords = '';
@ -286,6 +322,7 @@ class RedditBridge extends BridgeAbstract
'q' => $keywords . $flair . ($user ? 'author:' : 'subreddit:') . $name,
'sort' => $section,
'include_over_18' => 'on',
't' => $time
];
return 'https://old.reddit.com/search.json?' . http_build_query($query);
}

View File

@ -11,23 +11,23 @@ class RedditBridgeTest extends TestCase
$sut = new RedditBridge(new NullCache(), new NullLogger());
// https://old.reddit.com/search.json?q=cats dogs hen subreddit:php&sort=hot&include_over_18=on
$expected = 'https://old.reddit.com/search.json?q=cats+dogs+hen+subreddit%3Aphp&sort=hot&include_over_18=on';
$actual = RedditBridge::createUrl('cats,dogs hen', '', 'php', false, 'hot', 'single');
$expected = 'https://old.reddit.com/search.json?q=cats+dogs+hen+subreddit%3Aphp&sort=hot&include_over_18=on&t=all';
$actual = RedditBridge::createUrl('cats,dogs hen', '', 'php', false, 'hot', 'all', 'single');
$this->assertSame($expected, $actual);
// https://old.reddit.com/search.json?q=author:RavenousRandy&sort=hot&include_over_18=on
$expected = 'https://old.reddit.com/search.json?q=author%3ARavenousRandy&sort=hot&include_over_18=on';
$actual = RedditBridge::createUrl('', '', 'RavenousRandy', true, 'hot', 'user');
$expected = 'https://old.reddit.com/search.json?q=author%3ARavenousRandy&sort=hot&include_over_18=on&t=week';
$actual = RedditBridge::createUrl('', '', 'RavenousRandy', true, 'hot', 'week', 'user');
$this->assertSame($expected, $actual);
// https://old.reddit.com/search.json?q=cats dogs hen flair:"Proxy" subreddit:php&sort=hot&include_over_18=on
$expected = 'https://old.reddit.com/search.json?q=cats+dogs+hen+flair%3A%22Proxy%22+subreddit%3Aphp&sort=hot&include_over_18=on';
$actual = RedditBridge::createUrl('cats,dogs hen', 'Proxy', 'php', false, 'hot', 'single');
$expected = 'https://old.reddit.com/search.json?q=cats+dogs+hen+flair%3A%22Proxy%22+subreddit%3Aphp&sort=hot&include_over_18=on&t=month';
$actual = RedditBridge::createUrl('cats,dogs hen', 'Proxy', 'php', false, 'hot', 'month', 'single');
$this->assertSame($expected, $actual);
// https://old.reddit.com/search.json?q=cats dogs hen flair:"Proxy Linux Server" subreddit:php&sort=hot&include_over_18=on
$expected = 'https://old.reddit.com/search.json?q=cats+dogs+hen+flair%3A%22Proxy+Linux+Server%22+subreddit%3Aphp&sort=hot&include_over_18=on';
$actual = RedditBridge::createUrl('cats,dogs hen', 'Proxy,Linux Server', 'php', false, 'hot', 'single');
$expected = 'https://old.reddit.com/search.json?q=cats+dogs+hen+flair%3A%22Proxy+Linux+Server%22+subreddit%3Aphp&sort=hot&include_over_18=on&t=day';
$actual = RedditBridge::createUrl('cats,dogs hen', 'Proxy,Linux Server', 'php', false, 'hot', 'day', 'single');
$this->assertSame($expected, $actual);
}
}