Browse Source

reg.i2p files

Signed-off-by: r4sas <r4sas@i2pmail.org>
master
R4SAS 4 years ago
commit
80bb08fbd2
Signed by: r4sas
GPG Key ID: 66F6C87B98EBCFE2
  1. 2
      .gitignore
  2. 7
      LICENSE
  3. 110
      README.md
  4. 107
      bin/verifyhost.cpp
  5. 0
      cache/.gitkeep
  6. 70
      checker.php
  7. 38
      composer.json
  8. 718
      composer.lock
  9. 33
      config.php.dist
  10. 39
      database.sql
  11. 118
      export.php
  12. 110
      fetch.php
  13. 65
      import.php
  14. 168
      lib/bob.php
  15. 39
      lib/checker.php
  16. 38
      lib/db.php
  17. 28
      lib/router.php
  18. 264
      lib/utils.php
  19. 0
      logs/.gitkeep
  20. 642
      public/css/style.css
  21. 0
      public/export/.gitkeep
  22. BIN
      public/favicon.ico
  23. 83
      public/img/logo.svg
  24. 43
      public/index.php
  25. 54
      templates/404.twig
  26. 105
      templates/_page.twig
  27. 60
      templates/_pagination.twig
  28. 74
      templates/add.twig
  29. 40
      templates/alive.twig
  30. 41
      templates/all.twig
  31. 67
      templates/home.twig
  32. 57
      templates/jump.twig
  33. 33
      templates/latest.twig
  34. 53
      templates/search.twig
  35. 0
      tmp/.gitkeep
  36. 15
      views/404.php
  37. 176
      views/add.php
  38. 41
      views/alive.php
  39. 33
      views/all.php
  40. 35
      views/home.php
  41. 43
      views/jump.php
  42. 21
      views/latest.php
  43. 40
      views/search.php

2
.gitignore vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
/config.php
/hosts.txt

7
LICENSE

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
Copyright 2021 © The PurpleI2P Project
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

110
README.md

@ -0,0 +1,110 @@ @@ -0,0 +1,110 @@
Registry - reg.i2p
===
This is reg.i2p domain registry code
Requirements
---
* i2pd with enabled BOB protocol
* `verifyhost` binary built inside i2pd-tools project folder
* PHP and php-fpm 7.2+
* Composer
* Corntab (for jobs)
Installation
---
1. Clone repository in your webserver working directory. Note: that must not be `/var/www/html` or something like this, your webserver must point to `public` directory of forked project.
2. Fetch requirements by runnning `composer update`.
3. Point your webserver to serving webpages from `public` directory.
4. Create cron jobs for quering and updating hosts lists.
nginx configuration
---
```nginx
server {
listen 127.0.0.1:8000;
access_log /dev/null;
error_log /dev/null;
root /home/www/reg.i2p/public;
index index.php index.html;
port_in_redirect off;
location ~* ^/hosts\.txt$ {
if ($http_if_none_match) {
rewrite ^/hosts\.txt$ /export/hosts.txt break;
}
rewrite ^/hosts\.txt$ /export/hosts-basic.txt break;
}
location / {
try_files $uri $uri/ /index.php$request_uri;
}
location ~ ^/index\.php(/|$) {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SERVER_PORT 80;
fastcgi_pass unix:/home/www/reg.i2p/tmp/php-fpm.sock;
}
}
```
php-fpm configuration
---
```conf
[reg.i2p]
prefix = /home/www/$pool
user = www-data
group = www-data
listen = tmp/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 2
pm.max_requests = 500
chdir = public
php_admin_flag[log_errors] = On
php_admin_flag[display_errors] = Off
php_admin_value[error_reporting] = E_ALL
php_admin_value[error_log] = /home/www/$pool/logs/php-error.log
php_admin_value[upload_tmp_dir] = /home/www/$pool/tmp
php_admin_value[date.timezone] = Etc/UTC
php_admin_value[session.save_path] = /home/www/$pool/sessions
php_admin_value[session.save_handler] = files
php_admin_value[session.gc_probability] = 2
php_admin_value[session.gc_maxlifetime] = 1440
php_admin_value[memory_limit] = 32M
php_admin_value[upload_max_filesize] = "2M"
php_admin_value[max_execution_time] = 60
```
Cron jobs
---
```crontab
5 * * * * php7.2 /home/www/reg.i2p/checker.php > /home/www/reg.i2p/logs/checker.log 2>&1
45 */6 * * * php7.2 /home/www/reg.i2p/export.php > /home/www/reg.i2p/logs/export.log 2>&1
50 23 * * * php7.2 /home/www/reg.i2p/fetch.php > /home/www/reg.i2p/logs/fetch.log 2>&1
```

107
bin/verifyhost.cpp

@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include "Identity.h"
#include "Base.h"
int main (int argc, char * argv[])
{
if (argc < 2)
{
std::cout << "Usage: verifyhost '<host record>'" << std::endl;
return -1;
}
i2p::crypto::InitCrypto (false, true, true, false);
i2p::data::IdentityEx Identity, OldIdentity;
std::string str (argv[1]);
std::size_t pos;
// get record without command block after "#!"
pos = str.find ("#!");
std::string hostStr = str.substr (0, pos);
// get host base64
pos = hostStr.find ("=");
std::string hostBase64 = hostStr.substr (pos + 1);
// load identity
if (Identity.FromBase64 (hostBase64))
{
// get record without sig key and signature
pos = str.find ("#sig=");
if (pos == std::string::npos)
{
pos = str.find ("!sig=");
if (pos == std::string::npos)
{
std::cout << "Destination signature not found." << std::endl;
return 1;
}
}
int offset = (str[pos - 1] == '#' /* only sig in record */) ? 1 : 0;
std::string hostNoSig = str.substr (0, pos - offset);
std::string sig = str.substr (pos + 5); // after "#sig=" till end
auto signatureLen = Identity.GetSignatureLen ();
uint8_t * signature = new uint8_t[signatureLen];
// validate signature
i2p::data::Base64ToByteStream(sig.c_str (), sig.length(), signature, signatureLen);
if (!Identity.Verify ((uint8_t *)hostNoSig.c_str (), hostNoSig.length (), signature))
{
std::cout << "Invalid destination signature." << std::endl;
return 1;
}
if (str.find ("olddest=") != std::string::npos) // if olddest present
{
// get olddest
pos = str.find ("#olddest=");
std::string oldDestCut = str.substr (pos + 9);
pos = oldDestCut.find ("#");
std::string oldDestBase64 = oldDestCut.substr (0, pos);
// load identity
if(!OldIdentity.FromBase64 (oldDestBase64))
{
std::cout << "Invalid old destination base64." << std::endl;
return 1;
}
signatureLen = OldIdentity.GetSignatureLen ();
signature = new uint8_t[signatureLen];
// get record till oldsig key and oldsig
pos = str.find ("#oldsig=");
std::string hostNoOldSig = str.substr (0, pos);
std::string oldSigCut = str.substr (pos + 8);
pos = oldSigCut.find ("#");
std::string oldSig = oldSigCut.substr (0, pos);
// validate signature
i2p::data::Base64ToByteStream(oldSig.c_str (), oldSig.length(), signature, signatureLen);
bool oldSignValid = OldIdentity.Verify ((uint8_t *)hostNoOldSig.c_str (), hostNoOldSig.length (), signature);
if(!oldSignValid)
{
std::cout << "Invalid old destination signature." << std::endl;
return 1;
}
}
}
else
{
std::cout << "Invalid destination base64." << std::endl;
return 1;
}
i2p::crypto::TerminateCrypto ();
return 0;
}

0
cache/.gitkeep vendored

70
checker.php

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
<?php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/config.php';
use App\BOB;
use App\DB;
use Amp\Loop;
use Amp\Parallel\Worker\DefaultPool;
/* Disable execution limit */
set_time_limit(0);
$pdo = (new DB($options))->pdo;
$results = [];
$STH = $pdo->query("SELECT `host`, `base32` FROM `hosts`");
$hosts = $STH->fetchAll(PDO::FETCH_KEY_PAIR);
/* Start temporary BOB tunnel for checking */
$bob = new BOB($options);
$bob->setnick();
$bob->options();
$bob->newkeys();
$bob->intun();
$bob->start();
/* Sleep 10 seconds awaitng tunnels get built */
echo "BOB session started, awaiting 10 seconds for tunnels" . PHP_EOL;
sleep(10);
/* Start async checker tasks */
Loop::run(function () use (&$results, $hosts, $options) {
$pool = new DefaultPool(64);
$coroutines = [];
foreach ($hosts as $host => $base32) {
$coroutines[$host] = Amp\call(function () use ($pool, $base32, $options) {
$result = yield $pool->enqueue(new App\Checker($base32, $options));
return $result;
});
}
$results = yield Amp\Promise\all($coroutines);
return yield $pool->shutdown();
});
/* Stop BOB tunnel and terminate */
$bob->stop();
$bob->clear();
$bob = null;
/* Update last seen time in DB */
$i = 0;
$pdo->beginTransaction();
foreach ($results as $host => $result)
{
if($result)
{
$pdo->exec("UPDATE `hosts` SET `last_seen` = current_timestamp() WHERE `host` = '" . $host . "'");
$i++;
}
}
$pdo->commit();
echo "Result: Total hosts: " . count($results) . ", Alive: " . $i . PHP_EOL;

38
composer.json

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
{
"name": "purplei2p/regi2p",
"description": "I2P domain registrar",
"type": "project",
"require": {
"php": "^7.2.5",
"twig/twig": "^3.0",
"amphp/parallel": "^1.4"
},
"license": "MIT",
"authors": [
{
"name": "R4SAS",
"email": "r4sas@i2pmail.org"
}
],
"autoload": {
"files": [
"lib/bob.php",
"lib/checker.php",
"lib/db.php",
"lib/router.php",
"lib/utils.php"
]
},
"scripts": {
"checker": [
"Composer\\Config::disableProcessTimeout",
"php checker.php"
],
"clearCache": "rm -rf cache/*",
"export": "php export.php",
"fetch": [
"Composer\\Config::disableProcessTimeout",
"php fetch.php"
]
}
}

718
composer.lock generated

@ -0,0 +1,718 @@ @@ -0,0 +1,718 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "4053556a32d0a974795f8ab1ee625ae1",
"packages": [
{
"name": "amphp/amp",
"version": "v2.5.2",
"source": {
"type": "git",
"url": "https://github.com/amphp/amp.git",
"reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/amp/zipball/efca2b32a7580087adb8aabbff6be1dc1bb924a9",
"reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9",
"shasum": ""
},
"require": {
"php": ">=7"
},
"require-dev": {
"amphp/php-cs-fixer-config": "dev-master",
"amphp/phpunit-util": "^1",
"ext-json": "*",
"jetbrains/phpstorm-stubs": "^2019.3",
"phpunit/phpunit": "^6.0.9 | ^7",
"psalm/phar": "^3.11@dev",
"react/promise": "^2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"Amp\\": "lib"
},
"files": [
"lib/functions.php",
"lib/Internal/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Daniel Lowrey",
"email": "rdlowrey@php.net"
},
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
},
{
"name": "Bob Weinand",
"email": "bobwei9@hotmail.com"
},
{
"name": "Niklas Keller",
"email": "me@kelunik.com"
}
],
"description": "A non-blocking concurrency framework for PHP applications.",
"homepage": "http://amphp.org/amp",
"keywords": [
"async",
"asynchronous",
"awaitable",
"concurrency",
"event",
"event-loop",
"future",
"non-blocking",
"promise"
],
"support": {
"irc": "irc://irc.freenode.org/amphp",
"issues": "https://github.com/amphp/amp/issues",
"source": "https://github.com/amphp/amp/tree/v2.5.2"
},
"funding": [
{
"url": "https://github.com/amphp",
"type": "github"
}
],
"time": "2021-01-10T17:06:37+00:00"
},
{
"name": "amphp/byte-stream",
"version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/byte-stream.git",
"reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/byte-stream/zipball/f0c20cf598a958ba2aa8c6e5a71c697d652c7088",
"reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088",
"shasum": ""
},
"require": {
"amphp/amp": "^2",
"php": ">=7.1"
},
"require-dev": {
"amphp/php-cs-fixer-config": "dev-master",
"amphp/phpunit-util": "^1.4",
"friendsofphp/php-cs-fixer": "^2.3",
"jetbrains/phpstorm-stubs": "^2019.3",
"phpunit/phpunit": "^6 || ^7 || ^8",
"psalm/phar": "^3.11.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Amp\\ByteStream\\": "lib"
},
"files": [
"lib/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
},
{
"name": "Niklas Keller",
"email": "me@kelunik.com"
}
],
"description": "A stream abstraction to make working with non-blocking I/O simple.",
"homepage": "http://amphp.org/byte-stream",
"keywords": [
"amp",
"amphp",
"async",
"io",
"non-blocking",
"stream"
],
"support": {
"irc": "irc://irc.freenode.org/amphp",
"issues": "https://github.com/amphp/byte-stream/issues",
"source": "https://github.com/amphp/byte-stream/tree/master"
},
"time": "2020-06-29T18:35:05+00:00"
},
{
"name": "amphp/parallel",
"version": "v1.4.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/parallel.git",
"reference": "2c1039bf7ca137eae4d954b14c09a7535d7d4e1c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/parallel/zipball/2c1039bf7ca137eae4d954b14c09a7535d7d4e1c",
"reference": "2c1039bf7ca137eae4d954b14c09a7535d7d4e1c",
"shasum": ""
},
"require": {
"amphp/amp": "^2",
"amphp/byte-stream": "^1.6.1",
"amphp/parser": "^1",
"amphp/process": "^1",
"amphp/serialization": "^1",
"amphp/sync": "^1.0.1",
"php": ">=7.1"
},
"require-dev": {
"amphp/php-cs-fixer-config": "dev-master",
"amphp/phpunit-util": "^1.1",
"phpunit/phpunit": "^8 || ^7"
},
"type": "library",
"autoload": {
"psr-4": {
"Amp\\Parallel\\": "lib"
},
"files": [
"lib/Context/functions.php",
"lib/Sync/functions.php",
"lib/Worker/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
},
{
"name": "Stephen Coakley",
"email": "me@stephencoakley.com"
}
],
"description": "Parallel processing component for Amp.",
"homepage": "https://github.com/amphp/parallel",
"keywords": [
"async",
"asynchronous",
"concurrent",
"multi-processing",
"multi-threading"
],
"support": {
"issues": "https://github.com/amphp/parallel/issues",
"source": "https://github.com/amphp/parallel/tree/master"
},
"time": "2020-04-27T15:12:37+00:00"
},
{
"name": "amphp/parser",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/parser.git",
"reference": "f83e68f03d5b8e8e0365b8792985a7f341c57ae1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/parser/zipball/f83e68f03d5b8e8e0365b8792985a7f341c57ae1",
"reference": "f83e68f03d5b8e8e0365b8792985a7f341c57ae1",
"shasum": ""
},
"require": {
"php": ">=7"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.3",
"phpunit/phpunit": "^6"
},
"type": "library",
"autoload": {
"psr-4": {
"Amp\\Parser\\": "lib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Niklas Keller",
"email": "me@kelunik.com"
},
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
}
],
"description": "A generator parser to make streaming parsers simple.",
"homepage": "https://github.com/amphp/parser",
"keywords": [
"async",
"non-blocking",
"parser",
"stream"
],
"support": {
"issues": "https://github.com/amphp/parser/issues",
"source": "https://github.com/amphp/parser/tree/is-valid"
},
"time": "2017-06-06T05:29:10+00:00"
},
{
"name": "amphp/process",
"version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/process.git",
"reference": "355b1e561b01c16ab3d78fada1ad47ccc96df70e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/process/zipball/355b1e561b01c16ab3d78fada1ad47ccc96df70e",
"reference": "355b1e561b01c16ab3d78fada1ad47ccc96df70e",
"shasum": ""
},
"require": {
"amphp/amp": "^2",
"amphp/byte-stream": "^1.4",
"php": ">=7"
},
"require-dev": {
"amphp/php-cs-fixer-config": "dev-master",
"amphp/phpunit-util": "^1",
"phpunit/phpunit": "^6"
},
"type": "library",
"autoload": {
"psr-4": {
"Amp\\Process\\": "lib"
},
"files": [
"lib/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bob Weinand",
"email": "bobwei9@hotmail.com"
},
{
"name": "Niklas Keller",
"email": "me@kelunik.com"
},
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
}
],
"description": "Asynchronous process manager.",
"homepage": "https://github.com/amphp/process",
"support": {
"issues": "https://github.com/amphp/process/issues",
"source": "https://github.com/amphp/process/tree/master"
},
"time": "2019-02-26T16:33:03+00:00"
},
{
"name": "amphp/serialization",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/serialization.git",
"reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1",
"reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"amphp/php-cs-fixer-config": "dev-master",
"phpunit/phpunit": "^9 || ^8 || ^7"
},
"type": "library",
"autoload": {
"psr-4": {
"Amp\\Serialization\\": "src"
},
"files": [
"src/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
},
{
"name": "Niklas Keller",
"email": "me@kelunik.com"
}
],
"description": "Serialization tools for IPC and data storage in PHP.",
"homepage": "https://github.com/amphp/serialization",
"keywords": [
"async",
"asynchronous",
"serialization",
"serialize"
],
"support": {
"issues": "https://github.com/amphp/serialization/issues",
"source": "https://github.com/amphp/serialization/tree/master"
},
"time": "2020-03-25T21:39:07+00:00"
},
{
"name": "amphp/sync",
"version": "v1.4.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/sync.git",
"reference": "613047ac54c025aa800a9cde5b05c3add7327ed4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/sync/zipball/613047ac54c025aa800a9cde5b05c3add7327ed4",
"reference": "613047ac54c025aa800a9cde5b05c3add7327ed4",
"shasum": ""
},
"require": {
"amphp/amp": "^2.2",
"php": ">=7.1"
},
"require-dev": {
"amphp/php-cs-fixer-config": "dev-master",
"amphp/phpunit-util": "^1.1",
"phpunit/phpunit": "^9 || ^8 || ^7"
},
"type": "library",
"autoload": {
"psr-4": {
"Amp\\Sync\\": "src"
},
"files": [
"src/functions.php",
"src/ConcurrentIterator/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
},
{
"name": "Stephen Coakley",
"email": "me@stephencoakley.com"
}
],
"description": "Mutex, Semaphore, and other synchronization tools for Amp.",
"homepage": "https://github.com/amphp/sync",
"keywords": [
"async",
"asynchronous",
"mutex",
"semaphore",
"synchronization"
],
"support": {
"issues": "https://github.com/amphp/sync/issues",
"source": "https://github.com/amphp/sync/tree/v1.4.0"
},
"time": "2020-05-07T18:57:50+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.22.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "c6c942b1ac76c82448322025e084cadc56048b4e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e",
"reference": "c6c942b1ac76c82448322025e084cadc56048b4e",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.22-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-01-07T16:49:33+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.22.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "5232de97ee3b75b0360528dae24e73db49566ab1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1",
"reference": "5232de97ee3b75b0360528dae24e73db49566ab1",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.22-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-01-22T09:19:47+00:00"
},
{
"name": "twig/twig",
"version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5",
"reference": "1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"psr/container": "^1.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
},
"autoload": {
"psr-4": {
"Twig\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Twig Team",
"role": "Contributors"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "https://twig.symfony.com",
"keywords": [
"templating"
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.3.0"
},
"funding": [
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
"type": "tidelift"
}
],
"time": "2021-02-08T09:54:36+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "^7.2.5"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
}

33
config.php.dist

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
<?php
/**
* Configuration file
*/
/* Service options */
$options = [
/* Database settings */
'db_host' => '127.0.0.1',
'db_user' => 'regi2p',
'db_pass' => 'pass',
'db_name' => 'user',
/* I2P settings */
'bob_host' => '127.0.0.1',
'bob_port' => '2827',
'bob_options' => 'inbound.quantity=5 outbound.quantity=5 inbound.length=1 outbound.length=1 i2cp.leaseSetType=3',
'bob_nick' => 'hostchecker',
'http_proxy' => 'tcp://127.0.0.1:4444', // this is HTTP proxy, which must be specified as tcp protocol because we using stream context
/* Service settings */
'approval' => true, // require approval (check host for availability before publishing)
'fetcher' => true, // enable external subscriptions fetcher
'tableitems' => 30, // records limit on alive, all, search pages
/* Records processing options */
'approvedelay' => 24, // check host for availability before publishing for this time (hours)
'approveseen' => 3, // host must be seen lesser than this amount of hours for approving (hours)
'newdays' => 7, // assume host as new for that amout of days
'delnewdays' => 3, // if new host not seen more than X days, disable it and disapprove
'delolddays' => 30, // same as above, but for old hosts
];

39
database.sql

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
CREATE TABLE `hosts` (
`host` varchar(67) NOT NULL,
`base64` varchar(616) NOT NULL,
`base32` varchar(52) NOT NULL,
`description` varchar(128) NOT NULL DEFAULT '',
`add_date` timestamp NOT NULL DEFAULT current_timestamp(),
`last_seen` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`approved` tinyint(1) NOT NULL DEFAULT 0,
`initial` tinyint(1) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `subscriptions` (
`name` varchar(67) NOT NULL,
`url` varchar(256) NOT NULL,
`etag` varchar(64) NOT NULL DEFAULT '',
`active` tinyint(1) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `hosts`
ADD PRIMARY KEY (`host`);
ALTER TABLE `subscriptions`
ADD PRIMARY KEY (`name`);
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

118
export.php

@ -0,0 +1,118 @@ @@ -0,0 +1,118 @@
<?php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/config.php';
set_time_limit (0);
date_default_timezone_set ('UTC');
$pdo = (new App\DB($options))->pdo;
$util = new App\Utils;
$STH = $pdo->query ("SELECT `host`, `base64`, `base32`, `add_date`, `last_seen`, `approved`, `initial` FROM hosts");
$hosts = $STH->fetchAll(PDO::FETCH_ASSOC);
// for automatic approving
$approffs = date ("Y-m-d H:i:s", strtotime ("-" . $options["approvedelay"] . " hour")); // approval offset
$apprseen = date ("Y-m-d H:i:s", strtotime ("-" . $options["approveseen"] . " hour")); // approval maxseen offset
$newregoffs = date ("Y-m-d H:i:s", strtotime ("-" . $options["newdays"] . " day"));
$newseenlim = date ("Y-m-d H:i:s", strtotime ("-" . $options["delnewdays"] . " day"));
$oldseenlim = date ("Y-m-d H:i:s", strtotime ("-" . $options["delolddays"] . " day"));
$export_full = $export_live = $export_init = [];
$export_addr_full = $export_addr_live = $export_addr_init = [];
foreach ($hosts as $host)
{
/* Convert UFT-8 domains to Punycode */
$domain = $util->isASCII ($host["host"]) ? $host["host"] : idn_to_ascii ($host["host"]);
array_push($export_full, $domain . "=" . $host["base64"]);
array_push($export_addr_full, $domain . "," . $host["base32"]);
if (
($options["approval"] == false || $host["approved"] == 1) && (
($host["add_date"] > $newregoffs && $host["last_seen"] > $newseenlim) ||
($host["add_date"] < $newregoffs && $host["last_seen"] > $oldseenlim)
)
)
{
array_push($export_live, $domain . "=" . $host["base64"]);
array_push($export_addr_live, $domain . "," . $host["base32"]);
if ($host["initial"] == 1)
{
array_push($export_init, $domain . "=" . $host["base64"]);
array_push($export_addr_init, $domain . "," . $host["base32"]);
}
}
else if (
$host["approved"] == 0 && (
($host["add_date"] < $approffs && $host["last_seen"] > $apprseen) // if host were registered more then X days ago and seen lesser than X hours ago
)
)
{
$pdo->exec ("UPDATE `hosts` SET `approved` = 1 WHERE `host` = '" . $host["host"] . "'");
array_push($export_live, $domain . "=" . $host["base64"]);
array_push($export_addr_live, $domain . "," . $host["base32"]);
}
}
$STH = null;
$pdo = null;
/* Sort records */
sort ($export_full);
sort ($export_addr_full);
//
sort ($export_live);
sort ($export_addr_live);
//
sort ($export_init);
sort ($export_addr_init);
/* Export all records */
$f = fopen (__DIR__ . "/public/export/hosts-all.txt", "w");
foreach ($export_full as $l) {
$toWrite = print_r ($l, true);
fwrite ($f, $toWrite . "\n");
}
fclose ($f);
$f = fopen (__DIR__ . "/public/export/addresses-all.csv", "w");
foreach ($export_addr_full as $l) {
$toWrite = print_r ($l, true);
fwrite ($f, $toWrite . "\n");
}
fclose ($f);
/* Export alive records */
$f = fopen (__DIR__ . "/public/export/hosts.txt", "w");
foreach ($export_live as $l) {
$toWrite = print_r ($l, true);
fwrite ($f, $toWrite . "\n");
}
fclose ($f);
$f = fopen (__DIR__ . "/public/export/addresses.csv", "w");
foreach ($export_addr_live as $l) {
$toWrite = print_r ($l, true);
fwrite ($f, $toWrite . "\n");
}
fclose ($f);
/* Export initial records */
$f = fopen (__DIR__ . "/public/export/hosts-basic.txt", "w");
foreach ($export_init as $l) {
$toWrite = print_r ($l, true);
fwrite ($f, $toWrite . "\n");
}
fclose ($f);
$f = fopen (__DIR__ . "/public/export/addresses-basic.csv", "w");
foreach ($export_addr_init as $l) {
$toWrite = print_r ($l, true);
fwrite ($f, $toWrite . "\n");
}
fclose ($f);

110
fetch.php

@ -0,0 +1,110 @@ @@ -0,0 +1,110 @@
<?php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/config.php';
$pdo = (new App\DB($options))->pdo;
$util = new App\Utils;
$error = "";
$aContext = array(
'http' => array(
'method' => 'GET',
'proxy' => $options['http_proxy'],
'user_agent' => 'MYOB/6.66 (AN/ON)',
'request_fulluri' => true,
'timeout' => 120.0,
),
);
$STH = $pdo->query ("SELECT `name`, `url`, `etag` FROM `subscriptions` WHERE `active` = 1");
$lists = $STH->fetchAll(PDO::FETCH_ASSOC);
foreach ($lists as $list) {
echo "Processing " . $list['name'] . " subscription..." . PHP_EOL;
if (!empty($list['etag']))
$aContext['http']['header'] = 'If-None-Match: ' . $list['etag'] . '\r\n';
$cxContext = stream_context_create($aContext);
$f = fopen($list['url'], "r", false, $cxContext);
if ($f) {
$f_meta = stream_get_meta_data($f);
if (strpos($f_meta['wrapper_data'][0], "200") === false) {
continue;
}
$etagHeader = array_filter($f_meta['wrapper_data'], function($el) {
return (strpos($el, "ETag") !== false);
});
if ($etagHeader) {
$etag = substr($etagHeader[array_keys($etagHeader)[0]], 6);
var_dump($etag);
$pdo->exec("UPDATE `subscriptions` SET `etag` = '" . $etag . "' WHERE `name` = '" . $list['name'] . "'");
}
while (($buffer = fgets($f, 4096)) !== false) {
$domain = "";
$record = $util->parseHostRecord($buffer);
if (!$util->isValidAddress($record['host'], $error)) {
echo "Error while validating " . $record['host'] . ": " . $error . PHP_EOL;
continue;
} else {
if ($util->isPunycodeDomain($record['host'])) {
$domain = idn_to_utf8($record['host']);
} else {
$domain = $record['host'];
}
}
if (!$util->isValidBase64($record['b64'])) {
continue;
}
if ((isset($record['commands']) && isset($record['commands']['action']) && $record['commands']['action'] == "adddest" && isset($record['commands']['olddest'])) &&
$pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "' AND `base64` = '" . $record['commands']['olddest'] . "' LIMIT 1")->fetchColumn()) {
$base32 = $util->b32from64($record['b64']);
$pdo->exec("UPDATE `hosts` SET `base64` = '" . $record["b64"] . "', `base32` = '" . $base32 . "' WHERE `host` = '" . $domain . "'");
echo "Processed " . $domain . " (adddest)" . PHP_EOL;
continue;
}
if ((isset($record['commands']) && isset($record['commands']['action']) && $record['commands']['action'] == "addsubdomain" && isset($record['commands']['oldname']) && isset($record['commands']['olddest'])) &&
($pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $record['commands']['oldname'] . "' AND `base64` = '" . $record['commands']['olddest'] . "' LIMIT 1")->fetchColumn() &&
!$pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "'")->fetchColumn())) {
$base32 = $util->b32from64($record['b64']);
$pdo->exec("INSERT INTO `hosts` (`host`, `base64`, `base32`, `approved`) VALUES ('" . $domain . "', '" . $record["b64"] . "', '" . $base32 . "', 1)");
echo "Processed " . $domain . " (addsubdomain)" . PHP_EOL;
continue;
}
if(isset($record['commands']) && !$pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "' LIMIT 1")->fetchColumn()) {
$base32 = $util->b32from64($record['b64']);
$pdo->exec("INSERT INTO `hosts` (`host`, `base64`, `base32`, `approved`) VALUES ('" . $domain . "', '" . $record["b64"] . "', '" . $base32 . "', 1)");
echo "Processed " . $domain . PHP_EOL;
continue;
}
}
if (!feof($f)) {
echo "Error: fgets() ended earlier than needed." . PHP_EOL;
}
fclose($f);
} else {
echo "Empty response while fetching update from " . $list['name']. "." . PHP_EOL;
}
}
$pdo = null;

65
import.php

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
<?php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/config.php';
$pdo = (new App\DB($options))->pdo;
$util = new App\Utils;
$error = "";
$f = @fopen("hosts.txt", "r");
if ($f) {
$pdo->beginTransaction();
$STH = $pdo->prepare("INSERT IGNORE INTO hosts (host, base64, base32) VALUES (:host, :base64, :base32)");
$STH->bindParam('host', $domain);
$STH->bindParam('base64', $base64);
$STH->bindParam('base32', $base32);
while (($buffer = fgets($f, 4096)) !== false)
{
$domain = "";
$record = $util->parseHostRecord($buffer);
if (!$util->isValidAddress($record['host'], $error))
{
echo "Error while validating " . $record['host'] . ": " . $error . PHP_EOL;
continue;
}
else
{
if($util->isPunycodeDomain($record['host']))
{
$domain = idn_to_utf8($record['host']);
}
else
{
$domain = $record['host'];
}
}
if (!$util->isValidBase64($record['b64']))
{
echo "Error while validating " . $record['host'] . ": incorrect base64: " . $record['b64'] . PHP_EOL;
continue;
}
$base64 = $record['b64'];
$base32 = $util->b32from64($record['b64']);
$STH->execute();
}
if (!feof($f))
{
echo "Error: fgets() ended earlier than needed" . PHP_EOL;
}
if (!$pdo->commit())
{
echo "Error while saving records to database";
}
fclose($f);
}
$pdo = null;

168
lib/bob.php

@ -0,0 +1,168 @@ @@ -0,0 +1,168 @@
<?php
namespace App;
class BOB {
private $sock;
protected $options = [
"bob_host" => "127.0.0.1",
"bob_port" => "2827",
"bob_options" => "inbound.quantity=5 outbound.quantity=5 inbound.length=1 outbound.length=1 i2cp.leaseSetType=3",
"bob_nick" => "hostchecker",
];
public function __construct(array $options = [])
{
ob_implicit_flush();
$this->options = array_merge($this->options, (array) $options);
$this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($this->sock === false)
{
throw new \ErrorException("socket_create() failed: reason: " . socket_strerror(socket_last_error()));
}
else
{
$result = socket_connect($this->sock, $this->options["bob_host"], $this->options["bob_port"]);
if ($result === false)
{
throw new \ErrorException("socket_connect() failed.\nReason: ($result) " . socket_strerror(socket_last_error($socket)));
}
/* Reading BOB greeting */
$response = socket_read($this->sock, 1024);
if(!preg_match('/OK/', $response))
{
throw new \ErrorException("BOB returned incorrect response on connect");
}
}
}
public function __destruct()
{
$command = "quit\n";
socket_write($this->sock, $command, strlen($command));
socket_read($this->sock, 1024, PHP_NORMAL_READ);
socket_close($this->sock);
}
public function setnick()
{
$command = "setnick " . $this->options["bob_nick"] . "\n";
socket_write($this->sock, $command, strlen($command));
$response = socket_read($this->sock, 1024, PHP_NORMAL_READ);
if(!preg_match('/^OK/', $response))
echo "BOB response: " . $response;
}
public function options()
{
$options = explode(" ", $this->options["bob_options"]);
foreach($options as $option)
{
$command = "option " . $option . "\n";
socket_write($this->sock, $command, strlen($command));
$response = socket_read($this->sock, 1024, PHP_NORMAL_READ);
if(!preg_match('/^OK/', $response))
echo "BOB response: " . $response;
}
}
public function newkeys()
{
$command = "newkeys\n";
socket_write($this->sock, $command, strlen($command));
$response = socket_read($this->sock, 1024, PHP_NORMAL_READ);
if(!preg_match('/^OK/', $response))
echo "BOB response: " . $response;
}
public function start()
{
$command = "start\n";
socket_write($this->sock, $command, strlen($command));
$response = socket_read($this->sock, 1024, PHP_NORMAL_READ);
if(!preg_match('/^OK/', $response))
echo "BOB response: " . $response;
}
public function intun()
{
$command = "inhost 127.0.0.1\n";
socket_write($this->sock, $command, strlen($command));
$response = socket_read($this->sock, 1024, PHP_NORMAL_READ);
if(!preg_match('/^OK/', $response))
echo "BOB response: " . $response;
$command = "inport " . rand(1024, 65535) . "\n";
socket_write($this->sock, $command, strlen($command));
$response = socket_read($this->sock, 1024, PHP_NORMAL_READ);
if(!preg_match('/^OK/', $response))
echo "BOB response: " . $response;
}
public function getnick()
{
$command = "getnick " . $this->options["bob_nick"] . "\n";
socket_write($this->sock, $command, strlen($command));
$response = socket_read($this->sock, 1024, PHP_NORMAL_READ);
if(!preg_match('/^OK/', $response))
echo "BOB response: " . $response;
}
public function lookup(string $address): bool
{
$command = "lookup " . $address . "\n";
socket_write($this->sock, $command, strlen($command));
$response = socket_read($this->sock, 1024, PHP_NORMAL_READ);
if(preg_match('/^OK/', $response))
return true;
return false;
}
public function stop()
{
$command = "stop\n";
socket_write($this->sock, $command, strlen($command));
$response = socket_read($this->sock, 1024, PHP_NORMAL_READ);
if(!preg_match('/^OK/', $response))
echo "BOB response: " . $response;
}
public function clear()
{
$command = "clear\n";
socket_write($this->sock, $command, strlen($command));
$response = socket_read($this->sock, 1024, PHP_NORMAL_READ);
if(!preg_match('/^OK/', $response))
echo "BOB response: " . $response;
}
public function zap()
{
$command = "zap\n";
socket_write($this->sock, $command, strlen($command));
$response = socket_read($this->sock, 1024, PHP_NORMAL_READ);
if(!preg_match('/^OK/', $response))
echo "BOB response: " . $response;
}
}

39
lib/checker.php

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
<?php
namespace App;
use App\BOB;
use Amp\Parallel\Worker\Environment;
use Amp\Parallel\Worker\Task;
use Amp\Parallel\Worker\TaskFailureError;
use Amp\Parallel\Worker\TaskFailureException;
use Amp\Parallel\Worker\TaskFailureThrowable;
class Checker implements Task
{
private $options = [];
private $base32;
public function __construct($base32, $options = [])
{
$this->options = array_merge($this->options, (array) $options);
$this->base32 = $base32;
}
/**
* @param Environment $environment
* @return \Amp\Promise|\Generator|mixed
*/
public function run(Environment $environment)
{
$bob = new BOB($this->options);
$bob->getnick();
$result = $bob->lookup($this->base32 . ".b32.i2p");
$bob = null;
echo "Processed " . $this->base32 . ": " . ($result ? "online" : "offline") . PHP_EOL;
return $result;
}
}

38
lib/db.php

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
<?php
namespace App;
use PDO;
class DB
{
public $pdo;
protected $options = [
"db_host" => "127.0.0.1",
"db_user" => "regi2p",
"db_pass" => "password",
"db_name" => "regi2p",
];
public function __construct(array $options = [])
{
$this->options = array_merge($this->options, (array) $options);
try {
$this->pdo = new PDO("mysql:host=".$this->options["db_host"].";dbname=".$this->options["db_name"], $this->options["db_user"], $this->options["db_pass"]);
$this->pdo->setAttribute(PDO::ATTR_TIMEOUT, 20);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->pdo->exec("set names utf8mb4");
}
catch(PDOException $e) {
exit("Database connection error: " . $e . PHP_EOL);
}
}
public function __destruct()
{
$pdo = null;
}
}

28
lib/router.php

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
<?php
namespace App;
class Router
{
private $routes;
private $errRoute;
public function addRoute($pattern, $function) {
$this->routes['{'.$pattern.'}'] = $function;
}
public function addErrorRoute($function) {
$this->errRoute = $function;
}
public function run() {
foreach ($this->routes as $pattern => $function) {
if (preg_match($pattern, $_SERVER['REQUEST_URI'], $params)) {
array_shift($params);
array_unshift($params, $_SERVER['REQUEST_URI']);
return call_user_func_array($function, array_values($params));
}
}
return call_user_func($this->errRoute);
}
}

264
lib/utils.php

@ -0,0 +1,264 @@ @@ -0,0 +1,264 @@
<?php
namespace App;
class Utils {
// I2P uses custom base64 alphabet, defining translation here
private const b64Trans = array("-" => "+", "~" => "/");
// base32 alpabet
private const b32alphabet = 'abcdefghijklmnopqrstuvwxyz234567';
/**
* base32 encoding function.
* @param string $data
* @return string
*/
private static function b32encode(string $data): string
{
if (empty($data)) {
return "";
}
/* Create binary string zeropadded to eight bits. */
$data = str_split($data);
$binary = implode("", array_map(function ($character) {
return sprintf("%08b", ord($character));
}, $data));
/* Split to five bit chunks and make sure last chunk has five bits. */
$binary = str_split($binary, 5);
$last = array_pop($binary);
if (null !== $last) {
$binary[] = str_pad($last, 5, "0", STR_PAD_RIGHT);
}
/* Convert each five bits to Base32 character. */
$encoded = implode("", array_map(function ($fivebits) {
$index = bindec($fivebits);
return self::b32alphabet[$index];
}, $binary));
return $encoded;
}
/**
* I2P base64 decoding function.
* @param string $data
* @return string
*/
private static function b64decode(string $data): string
{
return base64_decode(strtr($data, static::b64Trans));
}
/**
* Checks if the given domain is in Punycode.
* @param string $domain The domain to check.
* @return bool Whether the domain is in Punycode.
*/
public static function isPunycodeDomain($domain): bool
{
$hasPunycode = false;
foreach (explode('.', $domain) as $part) {
if (false === static::isAscii($part))
return false;
if (static::isPunycode($part))
$hasPunycode = true;
}
return $hasPunycode;
}
/**
* Checks if the given value is in ASCII character encoding.
* @param string $value The value to check.
* @return bool Whether the value is in ASCII character encoding.
*/
public static function isAscii($value): bool
{
return ('ASCII' === mb_detect_encoding($value, 'ASCII', true));
}
/**
* Checks if the given value is in Punycode.
* @param string $value The value to check.
* @return bool Whether the value is in Punycode.
* @throws \LogicException If the string is not encoded by UTF-8.
*/
public static function isPunycode($value): bool
{
if (false === static::isAscii($value))
return false;
if ('UTF-8' !== mb_detect_encoding($value, 'UTF-8', true))
throw new \LogicException('The string should be encoded by UTF-8 to do the right check.');
return (0 === mb_stripos($value, 'xn--', 0, 'UTF-8'));
}
/**
* base64 validation function.
* @param string $data
* @return bool
*/
public static function isValidBase64(string $data): bool
{
$len = strlen($data);
if($len < 516 || $len > 616)
{
return false;
}
/* .i2p in string */
if(preg_match('/\.i2p/', $data))
{
return false;
}
/* DSA-SHA1. Will reject them because they are old (length 516) */
if($len == 516 && preg_match('/^[a-zA-Z0-9\-~]+AA$/', $data))
{
return false;
}
/* ECDSA or EdDSA */
if($len == 524 && !preg_match('/^[a-zA-Z0-9\-~]+AEAA[Ec]AAA==$/', $data))
{
return false;
}
return true;
}
/**
* I2P base64 to base32 conversion function.
* @param string $data
* @return string
*/
public static function b32from64(string $data): string
{
return self::b32encode(hash('sha256', self::b64decode($data), true));
}
/**
* Domain validation function for registration.
* @param string $data
* @return bool
*/
public static function isValidAddress(string $data, string &$result): bool
{
$len = strlen($data);
if($len < 5 || $len > 67)
{
$result = "Domain must be longer than 5 and lesser than 67 chars.";
return false;
}
if(preg_match('/\.b32\.i2p$/', $data))
{
$result = "Domain can't end with .b32.i2p.";
return false;
}
if(filter_var($data, FILTER_VALIDATE_DOMAIN) !== false && preg_match('/\.i2p$/', $data)
&& (static::isPunycodeDomain($data) || static::isAscii($data)))
{
return true;
}
else
{
$result = "Domain is not valid or is not in Punycode format.";
return false;
}
return false;
}
/**
* Domain validation function.
* @param string $data
* @return bool
*/
public static function isValidDomain(string $data, string &$result): bool
{
$len = strlen($data);
if($len < 5 || $len > 67)
{
$result = "Domain must be longer than 5 and lesser than 67 chars.";
return false;
}
if(preg_match('/\.b32\.i2p$/', $data))
{
$result = "Domain can't end with .b32.i2p.";
return false;
}
if(filter_var($data, FILTER_VALIDATE_DOMAIN) !== false && preg_match('/\.i2p$/', $data))
{
return true;
}
else
{
$result = "Inputed data or domain is not valid.";
return false;
}
return false;
}
public static function verifyHostRecord(string $data, &$output): bool
{
$retval = null;
$cmd = dirname(__FILE__) . "/../bin/verifyhost '" . $data . "'";
exec($cmd, $output, $retval);
if (!$retval)
return true;
return false;
}
public static function parseHostRecord(string $data): array
{
$record = [];
/* Return empty array if no data provided */
if(!strlen($data))
return $record;
$data = trim($data);
/* Split addressbook host record and extended commands */
$tmp = preg_split("/#!/", $data);
/* Parse host record */
$host = preg_split("/\=/", $tmp[0], 2);
$record["host"] = $host[0];
$record["b64"] = $host[1];
/* Parse extended commands */
if(isset($tmp[1]))
{
$cmds = [];
$cmdlist = preg_split("/#/", $tmp[1]);
foreach($cmdlist as $i)
{
list($name,$value) = explode('=', $i, 2);
$cmds[$name] = $value;
}
$record["commands"] = $cmds;
}
return $record;
}
}

0
logs/.gitkeep

642
public/css/style.css

@ -0,0 +1,642 @@ @@ -0,0 +1,642 @@
body {
margin: 0;
margin-right: auto;
margin-left: auto;
background-color: #e6e6e6
}
main {
flex-grow: 1;
display: flex;
flex-flow: column nowrap;
align-items: stretch;
justify-content: flex-start;
padding: 5px 0;
}
.container_100vh {
flex-grow: 1;
display: flex;
flex-flow: column nowrap;
align-items: stretch;
justify-content: center;
}
.container_main {
font-size: 16px;
}
.container_main ul {
margin-bottom: 20px;
}
.container_main li {
list-style-position: inside;
margin-bottom: 5px;
}
.container_main b {
font-size: 18px;
margin-bottom: 5px;
}
.container_main a {
color: #203070;
transition: .3s color linear;
}
.container_main a:hover {
color: #4477ff;
}
* {
box-sizing: border-box;
font-family: monospace;
}
button, input, ul, li {
padding: 0;
margin: 0;
border: 0 solid transparent;
}
svg {
max-width: 100%;
}
html {
background-color: #e6e6e6;
}
body {
background-color: #dadada;
max-width: 1240px;
margin: 0 auto;
min-height: 100vh;
display: flex;
flex-flow: column nowrap;
align-items: stretch;
justify-content: flex-start;
}
.container {
width: 100%;
padding: 0 20px;
}
.header {
position: sticky;
top: 0;
left: 0;
width: 100%;
}
.header__top {
display: flex;
flex-flow: row nowrap;
align-items: center;
justify-content: space-between;
padding: 10px 20px;
background-color: #c1c1c1;
}
.header__logo {
flex-basis: 220px;
}
.logo {
font-size: 0;
}
.logo__link {
display: inline-block;
font-size: 0;
width: 100%;
}
.search {
display: flex;
flex-flow: row nowrap;
align-items: stretch;
justify-content: space-between;
}
.text-input {
padding: 0 10px;
border: 1px solid #e6e6e6;
background-color: #e6e6e6;
transition: .3s background-color linear, .3s border linear, .3s box-shadow linear;
outline: none;
}
.search__text-input {
width: 260px;
margin-right: 5px;
}
.text-input:hover {
background-color: #fff;
}
.text-input:focus {
border: 1px solid #0054a6;
background-color: #fff;
box-shadow: 0 0px 6px 1px rgba(0, 84, 166, .6);
}
.text-input::placeholder {
font-size: 16px;
font-weight: bold;
color: #777
}
.search__btn {
height: 100%;
padding: 5px 10px;
cursor: pointer;
background-color: #dadada;
transition: .3s background-color linear, .3s box-shadow linear;
}
.search__btn img {
width: 20px;
}
.search__btn svg path {
fill: #333;
transition: .3s fill linear;
}
.search__btn:hover {
background-color: #fff;
box-shadow: 0 0px 6px 1px rgba(0, 84, 166, .6)
}
.search__btn:hover svg path {
fill: #0054a6;
}
.main-menu__list {
display: flex;
flex-flow: row nowrap;
align-items: stretch;
justify-content: space-between;
}
.main-menu__item {
list-style: none;
font-size: 0;
flex-grow: 1;
margin-right: 5px;
}
.main-menu__item:last-child{
margin-right: 0px;
}
.main-menu__link {
display: inline-block;
width: 100%;
font-size: 22px;
font-size: 22px;
font-weight: 400;
text-transform: uppercase;
text-align: center;
padding: 4px 0;
cursor: pointer;
background-color: #e6e6e6;
border: 1px solid #e6e6e6;
box-shadow: 0 4px 6px 2px rgba(0, 0, 0, .5);
text-decoration: none;
color: #444;
transition: .3s background-color linear, .3s border linear, .3s color linear, .3s box-shadow linear;
}
.main-menu__link:hover {
background-color: #fff;
border: 1px solid #0054a6;
color: #0054a6;
box-shadow: 0 0px 12px 2px rgba(0, 84, 166, .8)
}
.pagination__link:hover {
background-color: #fff;
border: 1px solid #808080;
color: #0054a6;
}
.header__mobile-logo {
display: none;
}
.header__mobile-search {
display: none;
}
.footer {
margin-top: auto;
padding: 25px 0;
text-align: center;
font-size: 18px;
color: #000;
background-color: #a7a7a7;
}
.adder, .jumper {
font-size: 18px;
color: #000000;
}
.title {
font-size: 24px;
text-align: center;
text-transform: uppercase;
font-weight: bold;
}
.line {
margin-bottom: 10px;
}
.line:last-child {
margin-bottom: 0;
}
.crazy-base64-span, .table__cell_long-ass {
word-break: break-all;
}
.form__field {
margin-bottom: 20px;
display: flex;
flex-flow: row nowrap;
align-items: center;
justify-content: flex-start;
}
.form__field:last-child {
margin-bottom: 0;
}
.adder__text-input, .jumper__text-input, .search-page__text-input {
flex-grow: 1;
padding: 5px 10px;
margin-left: 15px;
}
.btn {
height: 100%;
padding: 5px 10px;
cursor: pointer;
background-color: #dadada;
transition: .3s background-color linear, .3s color linear, .3s border linear, .3s box-shadow linear;
}
.adder__btn, .jumper__btn, .search-page__btn {
display: block;
margin-left: auto;
margin-right: auto;
background-color: #fff;
text-transform: uppercase;
border: 1px solid #0054a6;
color: #0054a6;
font-size: 20px;
font-weight: bold;
}
.adder__btn:hover, .jumper__btn:hover, .search-page__btn:hover {
background-color: #0054a6;
color: #ffffff;
border: 1px solid #fff;
box-shadow: 0 0px 23px 13px rgba(255, 255, 255, 1)
}
.pagination__item.active .pagination__link, .pagination__item.active .pagination__link:hover {
background-color: #0054a6;
color: #ffffff;
box-shadow: 0px 0px 0px 0px transparent;
}
.table {
font-size: 18px;
border-collapse: collapse;
width: 100%;
}
.table__head {
font-weight: bold;
text-transform: uppercase;
}
.table__head .table__row {
background-color: transparent !important;
color: #000 !important;
}
.table__row {
margin-bottom: 10px;
background-color: #dadada;
color: #000;
}
.table__row:nth-child(odd) {
background-color: #808080;
color: #fff;
}
.table__row a {
color: #203070;
transition: .3s color linear;
}
.table__row:nth-child(odd) a {
color: #0054a6;
}
.table__row a:hover {
color: #50a0ff
}
.table__row:nth-child(odd) a:hover {
color: #ddddff;
}
.table__cell {
padding: 10px;
}
.pagination {
margin-top: 40px;
display: flex;
flex-flow: row nowrap;
align-items: stretch;
justify-content: center;
}
.pagination__item {
list-style: none;
width: 40px;
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
font-size: 0;
margin-right: 5px;
}
.pagination__item:last-child {
margin-right: 0;
}
.pagination__link {
text-decoration: none;
text-transform: uppercase;
line-height: 1;
width: 100%;
height: 100%;
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
padding: 10px 0px;
cursor: pointer;
background-color: #dadada;
transition: .3s background-color linear, .3s color linear, .3s border linear, .3s box-shadow linear;
border: 1px solid transparent;
color: #0054a6;
font-size: 18px;
font-weight: bold;
}
.pagination__link_arrow {
font-size: 0;
position: relative;
}
.pagination__link_arrow::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 5px;
height: 5px;
}
.pagination__link_arrow_prev::after {
transform: translate(-50%, -50%) rotate(-45deg);
border-left: 1px solid #0054a6;
border-top: 1px solid #0054a6;
}
.pagination__link_arrow_next::after {
transform: translate(-50%, -50%) rotate(45deg);
border-right: 1px solid #0054a6;
border-top: 1px solid #0054a6;
}
.error-msg {
text-align: center;
}
.error-msg svg {
max-width: 300px;
}
.hamburger {
display: none;
}
.header-bottom__main-menu-switch {
display: none;
}
.section-head {
margin-bottom: 40px;
}
.disclaimer {
font-size: 16px;
margin-bottom: 15px;
}
.disclaimer:last-child {
margin-bottom: 30px;
}
@media screen and (max-width: 920px) {
.container {
padding: 0 8px;
}
.header__logo {
flex-basis: auto;
margin-bottom: 5px;
}
.logo svg {
max-width: 120px;
}
.header__search .search__text-input {
max-width: unset;
flex-grow: 1;
margin: 0;
}
.header__top {
display: none;
}
.header__bottom {
/* background-color: rgba(30, 30, 80, .8); */
padding: 3px 5px;
position: relative;
display: flex;
flex-flow: row nowrap;
align-items: center;
justify-content: space-between;
}
.header__bottom:before {
content: "";
z-index: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(190, 190, 190, .6);
backdrop-filter: blur(1px);
-webkit-backdrop-filter: blur(1px);
}
.header__mobile-logo {
display: block;
margin-left: 10px;
margin-right: auto;
width: 100px;
position: relative;
z-index: 1;
}
.header__mobile-logo .st2 {
/* fill: #fff; */
}
.header__mobile-search {
display: flex;
margin-left: 10px;
position: relative;
z-index: 1;
}
.header__mobile-search .search__btn {
padding: 3px 5px;
}
.header__mobile-search svg {
width: 15px;
}
.header__mobile-search .search__text-input {
width: 140px;
}
.hamburger {
display: block;
}
.hamburger__stripes {
border-top: 2px solid #333;
border-bottom: 2px solid #333;
width: 20px;
height: 14px;
position: relative
}
.hamburger__stripes:after {
content: "";
position: absolute;
height: 2px;
width: 100%;
top: 50%;
left: 0;
transform: translateY(-50%);
background-color: #333;
}
.main-menu {
background-color: #fff;
position: absolute;
left: 0;
top: 100%;
height: calc(100vh - 32px);
background-color: #e6e6e6;
transform: translateX(-100%);
transition: .2s transform linear, .1s box-shadow linear;
box-shadow: none;
}
.main-menu__list {
flex-flow: column nowrap;
align-items: stretch;
justify-content: flex-start;
}
.main-menu__item {
margin-right: 0;
padding: 0 8px;
min-width: 30vw;
}
.main-menu__link {
box-shadow: none;
border-bottom: 1px solid #aaa
}
.header-bottom__main-menu-switch:checked ~ .main-menu {
transform: translateX(0);
box-shadow: 0px 0px 12px 4px rgba(0, 0, 0, .5)
}
.title {
font-size: 16px;
}
.table {
font-size: 14px;
}
.table__cell {
padding: 6px 4px;
word-break: break-word;
min-width: 48px;
}
.text-input {
padding: 0 6px;
font-size: 16px;
}
.text-input::placeholder {
font-size: 14px;
}
.pagination__link {
padding: 3px 0px;
font-size: 14px;
}
.adder__btn, .jumper__btn, .search-page__btn {
font-size: 16px;
}
.footer {
font-size: 12px;
}
}
@media screen and (max-width: 620px) {
.main-menu__item {
min-width: 45vw;
}
}

0
public/export/.gitkeep

BIN
public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

83
public/img/logo.svg

@ -0,0 +1,83 @@ @@ -0,0 +1,83 @@
<svg version="1.1" id="logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 585 148" style="enable-background:new 0 0 585 148;" xml:space="preserve">
<style type="text/css">
.st0{fill:#662D91;}
.st1{fill:#FFFFFF;}
.st2{fill:#333366;}
</style>
<g id="Expanded">
<g>
<g>
<circle class="st0" cx="75.1" cy="74" r="71.9"/>
</g>
<g>
<path class="st1" d="M114.9,96.5H36.1l-0.5-0.4c-8-5.3-12.8-14.2-12.8-23.8c0-15.8,12.8-28.6,28.6-28.6c4.6,0,9.1,1.1,13.1,3.2
l-2,3.9c-3.4-1.8-7.2-2.7-11.1-2.7C38,48.2,27.1,59,27.1,72.4c0,7.9,3.8,15.3,10.3,19.8h76.2c6.5-4.5,10.3-11.9,10.3-19.8
c0-13.3-10.9-24.2-24.2-24.2c-3.9,0-7.6,0.9-11.1,2.7l-2-3.9c4-2.1,8.6-3.2,13.1-3.2c15.8,0,28.6,12.8,28.6,28.6
c0,9.6-4.8,18.5-12.8,23.8L114.9,96.5z"/>
</g>
<g>
<path class="st1" d="M115.1,118.5H35.9c-5.7,0-8.8-4.4-8.8-8.8s3-8.8,8.8-8.8h79.2c5.7,0,8.8,4.4,8.8,8.8
S120.8,118.5,115.1,118.5z M35.9,105.4c-3.2,0-4.3,2.4-4.3,4.4s1.1,4.4,4.3,4.4h79.2c3.2,0,4.4-2.4,4.4-4.4s-1.1-4.4-4.4-4.4
H35.9z"/>
</g>
<g>
<path class="st1" d="M50.7,83.4c-0.9,0-1.8-0.6-2.1-1.5c-1.1-3.1-1.7-6.3-1.7-9.6c0-15.8,12.8-28.6,28.6-28.6
s28.6,12.8,28.6,28.6c0,3.3-0.6,6.5-1.6,9.6c-0.4,1.1-1.7,1.7-2.8,1.3c-1.2-0.4-1.7-1.7-1.3-2.8c0.9-2.6,1.4-5.3,1.4-8.1
c0-13.3-10.8-24.2-24.2-24.2S51.3,59,51.3,72.4c0,2.8,0.5,5.5,1.4,8.1c0.4,1.1-0.2,2.4-1.3,2.8C51.1,83.4,50.9,83.4,50.7,83.4z"
/>
</g>
<g>
<path class="st1" d="M75.5,39.4c-7.3,0-13.2-5.9-13.2-13.2c0-7.3,5.9-13.2,13.2-13.2c7.3,0,13.2,5.9,13.2,13.2
C88.7,33.4,82.8,39.4,75.5,39.4z M75.5,17.4c-4.8,0-8.8,3.9-8.8,8.8s3.9,8.8,8.8,8.8c4.8,0,8.8-3.9,8.8-8.8
C84.3,21.3,80.4,17.4,75.5,17.4z"/>
</g>
<g>
<path class="st1" d="M86.5,28.4h-22c-1.2,0-2.2-1-2.2-2.2s1-2.2,2.2-2.2h22c1.2,0,2.2,1,2.2,2.2C88.7,27.4,87.7,28.4,86.5,28.4z"
/>
</g>
<g>
<path class="st1" d="M75.5,48.2c-1.2,0-2.2-1-2.2-2.2v-8.8c0-1.2,1-2.2,2.2-2.2s2.2,1,2.2,2.2V46C77.7,47.2,76.7,48.2,75.5,48.2z
"/>
</g>
</g>
</g>
<g>
<path class="st2" d="M219.7,102.6h-39.8V91.9h10.8V61.2h-10.8V50.5h24.7l1.3,9.7c1.2-3.5,3.1-6.3,5.6-8.3c2.6-2,5.5-3,8.7-3
c1.3,0,2.4,0.1,3.4,0.2s2,0.4,3.1,0.7L224.2,65c-1-0.3-1.9-0.5-2.9-0.6s-2-0.1-3.1-0.1c-3.3,0-5.8,1.1-7.7,3.4s-3.2,5.2-4.1,8.7
v15.5h13.3L219.7,102.6L219.7,102.6z"/>
<path class="st2" d="M252,81.1c0.5,4.3,1.9,7.3,4.1,9.1s5,2.7,8.3,2.7c2.2,0,4.4-0.4,6.5-1.1s4.3-1.9,6.5-3.6l6.6,8.9
c-2.5,2-5.6,3.7-9.1,5.1s-7.4,2.1-11.7,2.1c-4.5,0-8.5-0.7-11.9-2.1c-3.4-1.4-6.3-3.3-8.5-5.7c-2.3-2.5-4-5.4-5.1-8.7
c-1.1-3.4-1.7-7.1-1.7-11.1c0-3.9,0.6-7.5,1.7-10.8c1.1-3.4,2.8-6.3,4.9-8.8c2.2-2.5,4.8-4.5,8-6c3.1-1.5,6.7-2.2,10.8-2.2
c3.7,0,7.1,0.6,10.2,1.8c3,1.2,5.6,3,7.8,5.3c2.2,2.3,3.8,5.1,5,8.4s1.8,7.1,1.8,11.3c0,0.9,0,1.9,0,2.9c0,1-0.1,1.9-0.3,2.7
L252,81.1L252,81.1z M261.4,59.2c-2.8,0-5,1-6.6,3c-1.7,2-2.7,5.1-3,9.5h18.9c-0.1-3.7-0.8-6.7-2.2-9
C267.1,60.3,264.7,59.2,261.4,59.2z"/>
<path class="st2" d="M307.5,106c-0.2,0.5-0.4,1-0.5,1.6c-0.1,0.5-0.2,1-0.2,1.6c0,1.4,0.9,2.6,2.6,3.4s4.7,1.3,8.9,1.3
c2.5,0,4.6-0.1,6.2-0.4s2.8-0.6,3.7-1.1s1.5-1,1.8-1.6c0.3-0.6,0.5-1.2,0.5-2c0-0.8-0.2-1.5-0.5-2c-0.3-0.6-0.9-1-1.8-1.5
c-0.9-0.4-2.1-0.8-3.6-1.1c-1.5-0.3-3.5-0.5-5.8-0.6c-4-0.3-7.3-0.7-9.9-1.3c-2.6-0.6-4.7-1.4-6.2-2.3c-1.5-1-2.6-2.1-3.2-3.4
c-0.6-1.3-0.9-2.8-0.9-4.5s0.5-3.3,1.5-4.9c1-1.5,2.4-2.9,4.2-4.1c-2.8-1.6-5-3.7-6.6-6.2c-1.7-2.5-2.5-5.5-2.5-9.1
c0-2.9,0.5-5.5,1.6-7.9c1-2.4,2.6-4.4,4.6-6.1c2-1.7,4.5-3,7.4-3.9c2.9-0.9,6.1-1.4,9.8-1.4c5.7,0,10.6-0.7,14.6-2
c4.1-1.3,7.6-2.9,10.7-4.8l3.8,11.7c-1.9,0.8-4.1,1.3-6.7,1.6c-2.5,0.3-5.3,0.4-8.3,0.4V56c2.9,1.2,5.2,2.9,6.9,5
c1.6,2.1,2.5,4.8,2.5,8.1c0,2.5-0.5,4.8-1.6,6.9c-1,2.1-2.5,4-4.5,5.5s-4.3,2.8-7.1,3.6s-5.9,1.3-9.3,1.3c-1,0-2.1,0-3.2-0.1
c-1.1-0.1-2.2-0.3-3.3-0.5c-0.8,0.8-1.2,1.6-1.2,2.4c0,0.6,0.1,1.1,0.4,1.5s0.9,0.7,1.9,1c1,0.3,2.4,0.6,4.4,0.8s4.5,0.4,7.8,0.6
c6.6,0.5,11.6,2.1,15,4.8c3.4,2.8,5.1,6.4,5.1,10.9c0,2.8-0.7,5.1-2,7.2c-1.3,2.1-3.2,3.8-5.5,5.2c-2.4,1.4-5.3,2.5-8.7,3.1
c-3.4,0.7-7.3,1-11.6,1c-4.7,0-8.7-0.3-11.9-1c-3.2-0.7-5.9-1.6-8-2.9s-3.5-2.6-4.5-4.2c-0.9-1.6-1.4-3.3-1.4-5.1
c0-1.5,0.2-2.9,0.5-4.2c0.4-1.3,1.1-2.6,2.2-3.9L307.5,106z M310.8,67.6c0,2.8,0.8,4.9,2.3,6.3c1.5,1.5,3.5,2.2,6,2.2
c2.4,0,4.3-0.7,5.7-2.2s2.1-3.6,2.1-6.5c0-5.4-2.6-8.1-7.8-8.1c-2.5,0-4.5,0.7-6,2.2C311.5,63,310.8,65,310.8,67.6z"/>
<path class="st2" d="M367.8,93.3c0-1.5,0.3-2.9,0.9-4.3s1.4-2.5,2.4-3.5s2.1-1.8,3.5-2.4s2.8-0.9,4.3-0.9c1.5,0,2.9,0.3,4.3,0.9
s2.5,1.4,3.5,2.4s1.8,2.1,2.4,3.5s0.9,2.8,0.9,4.3c0,1.5-0.3,2.9-0.9,4.3s-1.4,2.5-2.4,3.5s-2.1,1.8-3.5,2.4s-2.8,0.9-4.3,0.9
c-1.5,0-2.9-0.3-4.3-0.9S372,102,371,101s-1.8-2.1-2.4-3.5S367.8,94.8,367.8,93.3z"/>
<path class="st2" d="M460.2,102.6h-43.7V91.9h14.7V61.2H417V50.5H447v41.4h13.3L460.2,102.6L460.2,102.6z M439.1,40.8
c-2.7,0-4.9-0.9-6.7-2.5c-1.8-1.7-2.6-3.8-2.6-6.3s0.9-4.6,2.6-6.3s4-2.6,6.7-2.6c2.7,0,4.9,0.9,6.7,2.6c1.8,1.7,2.6,3.8,2.6,6.3
c0,2.5-0.9,4.6-2.6,6.3C444,39.9,441.8,40.8,439.1,40.8z"/>
<path class="st2" d="M518.7,52.3c0,2.5-0.5,5.1-1.4,7.7c-0.9,2.5-2.4,5.3-4.6,8.3c-2.1,3-4.9,6.3-8.4,10s-7.8,7.7-13,12.3h29
l-1.7,12h-45.3V91.5c5.6-5.4,10.2-10,14-13.8c3.8-3.9,6.8-7.2,9.1-10.1c2.3-2.9,3.9-5.4,4.9-7.6s1.5-4.3,1.5-6.3
c0-2.8-0.8-4.9-2.5-6.5c-1.6-1.6-3.8-2.4-6.6-2.4c-2.4,0-4.5,0.5-6.4,1.6s-3.8,2.8-5.7,5.3l-9.6-7.3c2.8-3.6,6-6.4,9.8-8.5
c3.8-2.1,8.3-3.1,13.6-3.1c3.9,0,7.3,0.5,10.2,1.6s5.3,2.5,7.3,4.2s3.4,3.8,4.4,6.2C518.2,47.2,518.7,49.7,518.7,52.3z"/>
<path class="st2" d="M560.8,104.3c-5.1,0-9.2-1.8-12.4-5.4v23.9l-15.7,1.7V50.5h13.9l0.7,5.8c2.1-2.8,4.4-4.7,6.9-5.8
c2.5-1.1,5-1.7,7.5-1.7c3.4,0,6.3,0.6,8.7,1.9c2.4,1.3,4.3,3.1,5.9,5.5c1.5,2.4,2.7,5.3,3.4,8.7c0.7,3.4,1.1,7.2,1.1,11.5
c0,4.1-0.4,7.8-1.3,11.2c-0.9,3.4-2.2,6.3-3.9,8.8c-1.7,2.5-3.8,4.4-6.3,5.8C566.8,103.5,564,104.3,560.8,104.3z M555.9,92.8
c2.8,0,4.9-1.2,6.4-3.7c1.5-2.5,2.3-6.6,2.3-12.5c0-3.2-0.2-5.9-0.5-8s-0.9-3.8-1.6-5S561,61.5,560,61s-2.1-0.7-3.3-0.7
c-1.7,0-3.2,0.5-4.6,1.6c-1.3,1-2.6,2.5-3.7,4.4v21.9c1.1,1.6,2.2,2.7,3.3,3.5C552.9,92.4,554.3,92.8,555.9,92.8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

43
public/index.php

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
<?php
use App\Router;
require __DIR__ . '/../vendor/autoload.php';
/* Initialize pages rounting */
$r = new Router();
$r->addRoute('^/$', function($url) {
require __DIR__ . '/../views/home.php';
});
$r->addRoute('^/add/?$', function($url) {
require __DIR__ . '/../views/add.php';
});
$r->addRoute('^/alive/?([0-9]+)?/?', function($url, $page = 1) {
require __DIR__ . '/../views/alive.php';
});
$r->addRoute('^/all/?([0-9]+)?/?', function($url, $page = 1) {
require __DIR__ . '/../views/all.php';
});
$r->addRoute('^/jump/?(.*)/?', function($url, $query = "") {
require __DIR__ . '/../views/jump.php';
});
$r->addRoute('^/latest/?$', function($url) {
require __DIR__ . '/../views/latest.php';
});
$r->addRoute('^/search/?(.*)/?', function($url, $query = "") {
require __DIR__ . '/../views/search.php';
});
$r->addErrorRoute(function() {
require __DIR__ . '/../views/404.php';
});
/* Process requests */
$r->run();

54
templates/404.twig

@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
{% extends "_page.twig" %}
{% block title %}404{% endblock %}
{% block content %}
<div class="container container_100vh">
<div class="error-msg">
<svg version="1.1" id="404" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 386.1 242.8" style="enable-background:new 0 0 386.1 242.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#707070;}
</style>
<g>
<path class="st0" d="M97.5,102.3H116v24.6H97.5v34.6H67v-34.6H0.8l-1.2-19l67-105.7h30.9L97.5,102.3L97.5,102.3z M29.5,102.3H67
V43.1l-2.9,5.3L29.5,102.3z"/>
<path class="st0" d="M246.8,100.9c0,10.3-1.2,19.4-3.6,27.2s-5.8,14.4-10.2,19.7s-9.9,9.3-16.3,12c-6.4,2.7-13.6,4-21.7,4
c-8,0-15.1-1.4-21.6-4c-6.4-2.7-11.9-6.7-16.4-12s-8-11.9-10.4-19.7c-2.4-7.8-3.7-16.9-3.7-27.2V62.8c0-10.3,1.2-19.4,3.6-27.2
s5.9-14.4,10.3-19.7s9.9-9.3,16.3-12c6.4-2.7,13.5-4.1,21.5-4.1c8,0,15.3,1.4,21.7,4.1s11.9,6.7,16.4,12
c4.5,5.3,7.9,11.9,10.3,19.7s3.6,16.9,3.6,27.2L246.8,100.9L246.8,100.9z M173.7,87.7l42.5-31.6c-0.2-10.7-2.1-18.7-5.6-23.9
c-3.5-5.2-8.8-7.8-15.8-7.8c-7.1,0-12.4,2.7-15.9,8.2s-5.3,13.9-5.3,25.3V87.7z M216.2,76.8l-42.5,31.5c0.4,10.5,2.3,18.3,5.8,23.4
c3.5,5.1,8.7,7.6,15.5,7.6c7.1,0,12.5-2.8,15.9-8.3c3.5-5.5,5.2-14,5.2-25.5L216.2,76.8L216.2,76.8z"/>
<path class="st0" d="M366.4,102.3h18.5v24.6h-18.5v34.6H336v-34.6h-66.2l-1.2-19l67-105.7h30.9L366.4,102.3L366.4,102.3z
M298.5,102.3H336V43.1l-3,5.3L298.5,102.3z"/>
</g>
<g>
<path class="st0" d="M34.8,241.6h-9.9l-14.3-33.1v33.1h-10v-51.3h10l14.2,33l0-33h9.9V241.6z"/>
<path class="st0" d="M80.1,219.7c0,3.3-0.4,6.3-1.3,9.1c-0.9,2.8-2.1,5.2-3.8,7.2c-1.6,2-3.6,3.6-6,4.7s-5,1.7-8,1.7
c-3,0-5.6-0.6-7.9-1.7s-4.2-2.7-5.8-4.7s-2.8-4.4-3.6-7.2s-1.2-5.8-1.2-9.1v-7.5c0-3.3,0.4-6.3,1.2-9.1c0.8-2.8,2-5.2,3.6-7.2
c1.6-2,3.5-3.6,5.8-4.7c2.3-1.1,4.9-1.7,7.9-1.7c3,0,5.7,0.6,8,1.7s4.4,2.7,6,4.7c1.6,2,2.9,4.4,3.8,7.2c0.9,2.8,1.3,5.8,1.3,9.1
L80.1,219.7L80.1,219.7z M70.1,212.2c0-2.1-0.2-4-0.5-5.8s-0.9-3.3-1.6-4.5c-0.7-1.3-1.7-2.3-2.8-3s-2.5-1.1-4.1-1.1
c-1.6,0-2.9,0.4-4,1.1s-1.9,1.7-2.6,3c-0.7,1.3-1.1,2.8-1.4,4.5c-0.3,1.8-0.4,3.7-0.4,5.8v7.5c0,2.1,0.1,4,0.4,5.8
c0.3,1.8,0.8,3.3,1.4,4.6c0.7,1.3,1.5,2.3,2.6,3s2.4,1.1,4.1,1.1c1.6,0,3-0.4,4.1-1.1s2.1-1.7,2.8-3c0.7-1.3,1.3-2.8,1.6-4.6
s0.5-3.7,0.5-5.8L70.1,212.2L70.1,212.2z"/>
<path class="st0" d="M125.4,198.4h-15.8v43.3h-9.9v-43.3H84v-8.1h41.4V198.4z"/>
<path class="st0" d="M206.9,220.4h-22v21.3h-9.9v-51.3h34.7v8.1h-24.8v14h22V220.4z"/>
<path class="st0" d="M253.5,219.7c0,3.3-0.4,6.3-1.3,9.1c-0.9,2.8-2.1,5.2-3.8,7.2c-1.6,2-3.6,3.6-6,4.7s-5,1.7-8,1.7
s-5.6-0.6-7.9-1.7s-4.2-2.7-5.8-4.7c-1.6-2-2.8-4.4-3.6-7.2c-0.8-2.8-1.2-5.8-1.2-9.1v-7.5c0-3.3,0.4-6.3,1.2-9.1
c0.8-2.8,2-5.2,3.6-7.2c1.6-2,3.5-3.6,5.8-4.7c2.3-1.1,4.9-1.7,7.9-1.7c3,0,5.7,0.6,8,1.7s4.4,2.7,6,4.7c1.6,2,2.9,4.4,3.8,7.2
c0.9,2.8,1.3,5.8,1.3,9.1L253.5,219.7L253.5,219.7z M243.4,212.2c0-2.1-0.2-4-0.5-5.8s-0.9-3.3-1.6-4.5s-1.7-2.3-2.8-3
c-1.1-0.7-2.5-1.1-4.1-1.1c-1.6,0-2.9,0.4-4,1.1s-2,1.7-2.6,3c-0.7,1.3-1.1,2.8-1.4,4.5s-0.4,3.7-0.4,5.8v7.5c0,2.1,0.1,4,0.4,5.8
s0.8,3.3,1.4,4.6c0.7,1.3,1.5,2.3,2.6,3s2.4,1.1,4,1.1c1.6,0,3-0.4,4.1-1.1c1.1-0.7,2.1-1.7,2.8-3s1.3-2.8,1.6-4.6s0.5-3.7,0.5-5.8
L243.4,212.2L243.4,212.2z"/>
<path class="st0" d="M295.2,190.3l0,34c0,2.9-0.4,5.4-1.2,7.6c-0.8,2.2-1.9,4.1-3.4,5.7c-1.5,1.5-3.3,2.7-5.5,3.5s-4.6,1.2-7.4,1.2
c-2.6,0-4.9-0.4-7-1.2s-3.9-2-5.3-3.5s-2.6-3.4-3.4-5.7c-0.8-2.2-1.2-4.8-1.2-7.7l0-34h9.9l0,34c0,3.3,0.6,5.8,1.8,7.5
s2.9,2.5,5.2,2.5c2.4,0,4.3-0.9,5.5-2.5s1.9-4.2,2-7.5l0-34H295.2z"/>
<path class="st0" d="M338.1,241.6h-9.9L314,208.5v33.1h-10v-51.3h10l14.2,33l0-33h9.9L338.1,241.6L338.1,241.6z"/>
<path class="st0" d="M347.4,241.6v-51.3h13.3c3.4,0,6.5,0.6,9.3,1.8c2.8,1.2,5.1,2.8,7.1,4.9s3.5,4.6,4.5,7.5
c1.1,2.9,1.6,6.1,1.6,9.5v4.2c0,3.5-0.5,6.6-1.6,9.5c-1.1,2.9-2.6,5.4-4.5,7.4c-1.9,2.1-4.2,3.7-7,4.9s-5.7,1.7-9,1.7L347.4,241.6
L347.4,241.6z M357.4,198.3v35.3h3.8c1.9,0,3.5-0.4,5-1.1s2.7-1.7,3.7-3.1s1.8-3,2.3-4.9c0.5-1.9,0.8-4.1,0.8-6.5v-4.3
c0-2.3-0.3-4.4-0.8-6.3s-1.3-3.5-2.4-4.9s-2.3-2.4-3.9-3.2s-3.3-1.1-5.3-1.1L357.4,198.3L357.4,198.3z"/>
</g>
</svg>
</div>
</div>
{% endblock %}

105
templates/_page.twig

@ -0,0 +1,105 @@ @@ -0,0 +1,105 @@
<!DOCTYPE html>
<html>
<head>
{% block head %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %} - Registry</title>
<link rel="preload" href="/css/style.css" as="style">
<link rel="preload" href="/img/logo.svg" as="image">
<style>
@media screen and (max-width: 920px) {
.main-menu {transform: translateX(-100%);}
}
</style>
<link rel="stylesheet" href="/css/style.css"/>
{% endblock %}
</head>
<body>
<!-- <div id="loadOverlay" style="background-color:#333; position:absolute; top:0px; left:0px; width:100%; height:100%; z-index:2000;"></div> -->
<header class="header">
<div class="header__top">
<div class="logo header__logo">
<a href="/" class="logo__link">
<img class="svg" src="/img/logo.svg" />
</a>
</div>
<form action="/search" method="post" class="search header__search">
<input type="text" name="q" class="text-input search__text-input" maxlength="67" placeholder="Search address">
<button type="submit" class="search__btn">
<svg enable-background="new 0 0 32 32" id="search_svg" version="1.1" viewBox="0 0 32 32" width="20px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="search_1_">
<path d="M20,0.005c-6.627,0-12,5.373-12,12c0,2.026,0.507,3.933,1.395,5.608l-8.344,8.342l0.007,0.006 C0.406,26.602,0,27.49,0,28.477c0,1.949,1.58,3.529,3.529,3.529c0.985,0,1.874-0.406,2.515-1.059l-0.002-0.002l8.341-8.34 c1.676,0.891,3.586,1.4,5.617,1.4c6.627,0,12-5.373,12-12C32,5.378,26.627,0.005,20,0.005z M4.795,29.697 c-0.322,0.334-0.768,0.543-1.266,0.543c-0.975,0-1.765-0.789-1.765-1.764c0-0.498,0.21-0.943,0.543-1.266l-0.009-0.008l8.066-8.066 c0.705,0.951,1.545,1.791,2.494,2.498L4.795,29.697z M20,22.006c-5.522,0-10-4.479-10-10c0-5.522,4.478-10,10-10 c5.521,0,10,4.478,10,10C30,17.527,25.521,22.006,20,22.006z"/>
<path d="M20,5.005c-3.867,0-7,3.134-7,7c0,0.276,0.224,0.5,0.5,0.5s0.5-0.224,0.5-0.5c0-3.313,2.686-6,6-6 c0.275,0,0.5-0.224,0.5-0.5S20.275,5.005,20,5.005z" />
</g>
</svg>
</button>
</form>
</div>
<div class="header__bottom">
<div class="hamburger">
<label for="header-bottom__main-menu-switch" class="hamburger__label">
<div class="hamburger__stripes">
</div>
</label>
</div>
<input type="checkbox" class="header-bottom__main-menu-switch" id="header-bottom__main-menu-switch">
<nav class="main-menu">
<ul class="main-menu__list">
<li class="main-menu__item">
<a href="/" class="main-menu__link">
home
</a>
</li>
<li class="main-menu__item">
<a href="/add" class="main-menu__link">
add
</a>
</li>
<li class="main-menu__item">
<a href="/jump" class="main-menu__link">
jump
</a>
</li>
<li class="main-menu__item">
<a href="/latest" class="main-menu__link">
latest
</a>
</li>
<li class="main-menu__item">
<a href="/alive" class="main-menu__link">
alive
</a>
</li>
<li class="main-menu__item">
<a href="/all" class="main-menu__link">
all
</a>
</li>
</ul>
</nav>
<div class="logo header__mobile-logo">
<a href="/" class="logo__link">
<img src="/img/logo.svg" />
</a>
</div>
<form action="/search" method="post" class="search header__mobile-search">
<input type="text" name="q" class="text-input search__text-input" maxlength="67" placeholder="Search address">
<button type="submit" class="search__btn">
<svg enable-background="new 0 0 32 32" id="search_svg" version="1.1" viewBox="0 0 32 32" width="32px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="search_1_">
<path d="M20,0.005c-6.627,0-12,5.373-12,12c0,2.026,0.507,3.933,1.395,5.608l-8.344,8.342l0.007,0.006 C0.406,26.602,0,27.49,0,28.477c0,1.949,1.58,3.529,3.529,3.529c0.985,0,1.874-0.406,2.515-1.059l-0.002-0.002l8.341-8.34 c1.676,0.891,3.586,1.4,5.617,1.4c6.627,0,12-5.373,12-12C32,5.378,26.627,0.005,20,0.005z M4.795,29.697 c-0.322,0.334-0.768,0.543-1.266,0.543c-0.975,0-1.765-0.789-1.765-1.764c0-0.498,0.21-0.943,0.543-1.266l-0.009-0.008l8.066-8.066 c0.705,0.951,1.545,1.791,2.494,2.498L4.795,29.697z M20,22.006c-5.522,0-10-4.479-10-10c0-5.522,4.478-10,10-10 c5.521,0,10,4.478,10,10C30,17.527,25.521,22.006,20,22.006z"/>
<path d="M20,5.005c-3.867,0-7,3.134-7,7c0,0.276,0.224,0.5,0.5,0.5s0.5-0.224,0.5-0.5c0-3.313,2.686-6,6-6 c0.275,0,0.5-0.224,0.5-0.5S20.275,5.005,20,5.005z" />
</g>
</svg>
</button>
</form>
</div>
</header>
<main id="content">
{% block content %}{% endblock %}
</main>
<footer style="color: #666666" class="footer" id="footer">
Powered using i2pd by PurpleI2P team, {{ "now"|date("Y") == '2021' ? "now"|date("Y") : '2021-' ~ "now"|date("Y") }}
</footer>
</body>
</html>

60
templates/_pagination.twig

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
{% macro pagination(total, current, url, nearbyPagesLimit = 4) %}
{# Create "main_url" variable with link for the first page #}
{% set foo = url|split('/') %}
{% set foo = foo|slice(0, -1) %}
{% set main_url = foo|join('/') ~ "/" %}
{% apply spaceless %}
{% if total > 1 %}
<div class="row">
<nav>
<ul class="pagination">
{% if current > 1 %}
<li class="pagination__item pagination__item_arrow">
<a class="prev pagination__link pagination__link_arrow pagination__link_arrow_prev" href="{{ (url ~ (current-1))|e }}">&laquo;</a>
</li>
{% endif %}
{% for i in 1..total %}
{% if 0 == (current - nearbyPagesLimit) - loop.index %}
<li class="pagination__item">
<a href="{{ (url ~ 1)|e }}" class="pagination__link">1
</a>
</li>
{% if 1 != loop.index %}
<li class="pagination__item">
<a href="#" class="pagination__link"><span>...</span>
</a>
</li>
{% endif %}
{% elseif 0 == (current + nearbyPagesLimit) - loop.index and (current + nearbyPagesLimit) < total %}
<li class="pagination__item pagination__item_see-more"><a href="#" class=" pagination__link_see-more pagination__link"><span>...</span></a></li>
{% elseif 0 < (current - nearbyPagesLimit) - loop.index %}
{% elseif 0 > (current + nearbyPagesLimit) - loop.index %}
{% else %}
{% if current == loop.index %}
<li class="active pagination__item pagination__item_active"><a href="#" class="pagination__link"><span aria-current="page">{{ loop.index }}</span></a></li>
{% else %}
{% if loop.index == 1 %}
<li class="pagination__item"><a href="{{ main_url }}" class="pagination__link">{{ loop.index }}</a></li>
{% else %}
<li class="pagination__item"><a href="{{ url ~ loop.index }}" class="pagination__link">{{ loop.index }}</a></li>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% if current != total and (current + nearbyPagesLimit) < total %}
<li class="pagination__item"><a href="{{ (url ~ total)|e }}" class="pagination__link">{{ total }}</a></li>
{% endif %}
{% if current < total %}
<li class="pagination__item pagination__item_arrow"><a class="next pagination__link pagination__link_arrow pagination__link_arrow_next" href="{{ (url ~ (current+1))|e }}">&raquo;</a></li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
{% endapply %}
{% endmacro %}
{{ _self.pagination(total, current, url) }}

74
templates/add.twig

@ -0,0 +1,74 @@ @@ -0,0 +1,74 @@
{% extends "_page.twig" %}
{% block title %}Add record{% endblock %}
{% block content %}
<div class="container">
<div class="adder">
{% if result|length > 0 %}
{% if result.error|length > 0 %}
<div class="adder__err">
<h3 class="adder__title title">Error</h3>
<b style="color: red;">{{ result.error }}</b>
</div>
{% else %}
<div class="adder__succ">
<div class="adder__succ-body">
<h3 class="adder__title title">Domain successfuly added</h3>
<div class="adder__line line adder__line_reshost">
<span>
Domain:
</span>
<span>
{{ result.host }}
</span>
</div>
<div class="adder__line line adder__line_adrhelper">
<span>
Addresshelper:
</span>
<a href="http://{{ result.host }}/?i2paddresshelper={{ result.base64 }}">Go to site</a>
</div>
<div class="adder__line line adder__line_base32">
<span>
Base32:
</span>
<span class="crazy-base64-span">
{{ result.base32 }}.b32.i2p
</span>
</div>
<div class="adder__line line adder__line_base64">
<span>
Base64:
</span>
<span class="crazy-base64-span">
{{ result.base64 }}
</span>
</div>
</div>
</div>
{% endif %}
{% endif %}
<div class="adder__adder-itself">
<p class="adder__title title important">
Domain adding
</p>
<form action="/add" method="post" class="adder__form">
<div class="form__field">
<label for="record">
<span>
Auth string:
</span>
</label>
<input class="text-input adder__text-input" type="text" id="record" name="record" placeholder="domain.i2p=base64#!sig=hash"{% if record|length > 0 %} value="{{ record }}"{% endif %} required>
</div>
<div class="form__field">
<label for="desc">Description:</label>
<input class="text-input adder__text-input" type="text" id="desc" name="desc" placeholder="A bit information about service on domain"{% if desc|length > 0 %} value="{{ desc }}"{% endif %}>
</div>
<input class="adder__submit adder__btn btn" type="submit" value="Submit">
</form>
</div>
</div>
</div>
{% endblock %}

40
templates/alive.twig

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
{% extends "_page.twig" %}
{% block title %}Alive hosts{% endblock %}
{% block content %}
<div class="container">
<div class="alive-hosts">
<p class="important title" align="center">
Alive domains
</p>
<table class="table">
<thead class="table__head">
<tr class="table__row">
<th class="table__cell">Domain</th>
<th class="table__cell">Base32</th>
<th class="table__cell">Last seen</th>
</tr>
</thead>
<tbody class="table__body">
{% for host in hosts %}
<tr class="table__row">
<td class="table__cell"><a href="http://{{ host.host }}/?i2paddresshelper={{ host.base64 }}">{{ host.host }}</a></td>
<td class="table__cell table__cell_long-ass"><a href="http://{{ host.base32 }}.b32.i2p/">{{ host.base32 }}.b32.i2p</a></td>
<td class="table__cell">{{ host.last_seen }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if total > 1 %}
{% include "_pagination.twig" with {
total,
current,
url: "/alive/"
} only %}
{% endif %}
</div>
</div>
{% endblock %}

41
templates/all.twig

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
{% extends "_page.twig" %}
{% block title %}All hosts{% endblock %}
{% block content %}
<div class="container">
<div class="all-hosts">
<p class="important title" align="center">
All domains
</p>
<table class="table">
<thead class="table__head">
<tr class="table__row">
<th class="table__cell">Domain</th>
<th class="table__cell">Base32</th>
<th class="table__cell">Last seen</th>
</tr>
</thead>
<tbody class="table__body">
{% for host in hosts %}
<tr class="table__row">
<td class="table__cell"><a href="http://{{ host.host }}/?i2paddresshelper={{ host.base64 }}">{{ host.host }}</a></td>
<td class="table__cell table__cell_long-ass"><a href="http://{{ host.base32 }}.b32.i2p/">{{ host.base32 }}.b32.i2p</a></td>
<td class="table__cell">{{ host.last_seen != '0000-00-00 00:00:00' ? host.last_seen : 'Never' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if total > 1 %}
{% include "_pagination.twig" with {
total,
current,
url: "/all/"
} only %}
{% endif %}
</div>
</div>
{% endblock %}

67
templates/home.twig

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
{% extends "_page.twig" %}
{% block title %}Home{% endblock %}
{% block content %}
<div class="container container_main">
<p class="important title" align="center">
This is reg.i2p registry service.<br>
</p>
<p>
<b>Info:</b><br>
Service works, hosts check done every hour.<br>
Supported commands:
<ul>
<li>adding of hosts for 2LD domains (example.i2p)</li>
<li>adding subdomains (addsubdomain)</li>
<li>adding/changing destination (adddest, changedest) - destination will be replaced with new one, old will be purged</li>
</ul>
</p>
<p>
<b>Subscription:</b><br>
For addressbook subscription use <a href="/hosts.txt">/hosts.txt</a> file.<br>
That link returns basic addressbook if request sent first time or without <code>If-Match-None</code> header.<br>
On next requests it will return full alive hosts list.
</p>
<p>
<b>Static links to lists:</b><br>
Lists updated every 4 hours.
<ul>
<li>Basic list: <a href="/export/hosts-basic.txt">/export/hosts-basic.txt</a> - contains short list of alive popular services</li>
<li>Alive list: <a href="/export/hosts.txt">/export/hosts.txt</a></li>
<li>Whole list: <a href="/export/hosts-all.txt">/export/hosts-all.txt</a></li>
</ul>
</p>
<p>
<b>Rules:</b><br>
Here no particular rules about registering domains, but we have some limitations about handling dead domain records.<br>
Your domain will be open for registration if it has been dead for:
<ul>
<li>{{ delnewdays }} days if registered less than {{ newdays }} days ago</li>
<li>{{ delolddays }} days if registered more than {{ newdays }} days ago</li>
</ul>
</p>
{% if approval %}
<p>
<b>Approval:</b><br>
Domain will be approved after {{ apprdelay }} hours, but only if it has been alive not more than {{ apprseen }} hours ago when approval time is reached.
</p>
{% endif %}
{% if fetcher and subscrs|length > 0 %}
<p>
<b>External subscriptions:</b><br>
Our service fetching updates from external subscriptions once a day. List of external services:
<ul>
{% for subscr in subscrs %}
<li>{{ subscr.name }}</li>
{% endfor %}
</ul>
</p>
</div>
{% endif %}
{% endblock %}

57
templates/jump.twig

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
{% extends "_page.twig" %}
{% block title %}Jump to site{% endblock %}
{% block content %}
<div class="container">
<div class="jumper">
{% if result|length > 0 %}
{% if result.error|length > 0 %}
<div class="jumper__err">
<h3 class="jumper__title title">Error</h3>
<b style="color: red;">{{ result.error }}</b>
</div>
{% else %}
<div class="jumper__succ">
<h3 class="jumper__title title">Query result for "{{ result.host }}"</h3>
<div class="jumper__line line">
<span>
Addresshelper:
</span>
<a href="http://{{ result.host }}/?i2paddresshelper={{ result.base64 }}">Go to site</a>
</div>
<div class="jumper__line line">
<span>
Base32:
</span>
<span class="crazy-base64-span">
{{ result.base32 }}.b32.i2p
</span>
</div>
<div class="jumper__line line">
<span>
Base64:
</span>
<span class="crazy-base64-span">
{{ result.base64 }}
</span>
</div>
</div>
{% endif %}
{% endif %}
<div class="jumper__itself">
<p class="important jumper__title title">
Query domain
</p>
<form action="/jump" method="post" class="jumper__form">
<div class="form__field">
<label for="q">Domain:</label>
<input class="text-input jumper__text-input" type="text" id="q" name="q" maxlength="67" placeholder="domain.i2p" required>
</div>
<input type="submit" value="Submit" class="btn jumper__btn">
</form>
</div>
</div>
</div>
{% endblock %}

33
templates/latest.twig

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
{% extends "_page.twig" %}
{% block title %}Latest hosts{% endblock %}
{% block content %}
<div class="container">
<div class="latest-hosts">
<p class="important title" align="center">
Latest added domains
</p>
<table class="table">
<thead class="table__head">
<tr class="table__row">
<th class="table__cell">Domain</th>
<th class="table__cell">Base32</th>
<th class="table__cell">Added</th>
</tr>
</thead>
<tbody class="table__body">
{% for host in hosts %}
<tr class="table__row">
<td class="table__cell"><a href="http://{{ host.host }}/?i2paddresshelper={{ host.base64 }}">{{ host.host }}</a></td>
<td class="table__cell table__cell_long-ass"><a href="http://{{ host.base32 }}.b32.i2p/">{{ host.base32 }}.b32.i2p</a></td>
<td class="table__cell">{{ host.add_date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

53
templates/search.twig

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
{% extends "_page.twig" %}
{% block title %}Search host{% endblock %}
{% block content %}
<div class="container">
{% if result|length > 0 %}
<h3 class="title">Query result for "{{ query }}"</h3>
<div class="disclaimer">
<span>
Note: only first {{ limit }} record(s) are shown
</span>
</div>
{% if result.error|length > 0 %}
<div class="disclaimer">
<span>
<b>{{ result.error }}</b>
</span>
</div>
{% else %}
<table class="table">
<thead class="table__head">
<tr class="table__row">
<td class="table__cell">Domain</td>
<td class="table__cell">Base32</td>
<td class="table__cell">Last seen</td>
</tr>
</thead>
<tbody class="table__body">
{% for host in result %}
<tr class="table__row">
<td class="table__cell"><a href="http://{{ host.host }}/?i2paddresshelper={{ host.base64 }}">{{ host.host }}</a></td>
<td class="table__cell table__cell_long-ass"><a href="http://{{ host.base32 }}.b32.i2p/">{{ host.base32 }}.b32.i2p</a></td>
<td class="table__cell">{{ host.last_seen != '0000-00-00 00:00:00' ? host.last_seen : 'Never' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endif %}
<p class="important title">
Search domain
</p>
<form action="/search" method="post">
<div class="form__field">
<label for="q">Query:</label>
<input class="text-input search-page__text-input" type="text" id="q" name="q" maxlength="67" placeholder="domain.i2p" required>
</div>
<input type="submit" value="Submit" class="btn search-page__btn">
</form>
</div>
{% endblock %}

0
tmp/.gitkeep

15
views/404.php

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../config.php';
/* Initialize Twig engine */
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../templates');
$twig = new \Twig\Environment($loader, [
'cache' => __DIR__ . '/../cache',
'auto_reload' => true,
]);
$template = $twig->load('404.twig');
echo $template->render();

176
views/add.php

@ -0,0 +1,176 @@ @@ -0,0 +1,176 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../config.php';
/* Initialize Twig engine */
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../templates');
$twig = new \Twig\Environment($loader, [
'cache' => __DIR__ . '/../cache',
'auto_reload' => true,
]);
$record = "";
$desc = "";
$error = "";
$result = [];
if (isset($_POST["record"]) && !empty($_POST["record"])) {
$record = (string) $_POST["record"];
if (isset($_POST["desc"]) && !empty($_POST["desc"])) {
$desc = (string) $_POST["desc"];
}
$pdo = (new App\DB($options))->pdo;
$util = new App\Utils;
$parsed = $util->parseHostRecord($record);
if (!$util->isValidAddress($parsed['host'], $error)) {
$result["error"] = "Error while validating: " . $error;
} else {
if ($util->isPunycodeDomain($parsed['host'])) {
$domain = idn_to_utf8($parsed['host']);
} else {
$domain = $parsed['host'];
}
/* Check if such domain name already registered */
$STH = $pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "' LIMIT 1");
if($STH->fetchColumn() == 1) {
$result["error"] = "Error while validating: That domain is already registered.";
} else {
if (!isset($parsed["commands"]) || !isset($parsed["commands"]["sig"])) {
$result["error"] = "Error while validating: No extended record fields or signature is found.";
} else if (!$util->verifyHostRecord($record, $error)) {
$result["error"] = "Error while validating: " . $error[0];
} else {
if (isset($parsed["commands"]["action"])) {
switch ($parsed["commands"]["action"]) {
case 'addsubdomain':
if (!isset($parsed["commands"]["oldname"]) || !isset($parsed["commands"]["olddest"]) || !isset($parsed["commands"]["oldsig"])) {
$result["error"] = "Error while validating: required fields not found. Re-check your registration string.";
} else {
/* Getting domain at higher level (2LD for registering 3LD and etc.) and validating that domain is lower than 2LD. */
$darr = explode(".", $domain);
$dtop = "";
for ($i = 1; $i < sizeof ($darr); $i++) {
$dtop .= $darr[$i];
if ((sizeof ($darr) - 1) != $i) $dtop .= ".";
}
if (sizeof($darr) < 3) {
$result["error"] = "Error while validating: you can't register second level domain (example.i2p) using addsubdomain action.";
} else if ($dtop != $parsed["commands"]["oldname"]) {
$result["error"] = "Error while validating: oldname value is not same as your higher level domain.";
} else if (!$pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $parsed["commands"]["oldname"] . "' AND `base64` = '" . $parsed["commands"]["olddest"] . "' LIMIT 1")->fetchColumn()) {
$result["error"] = "Error while validating: can't find higher level domain with values from oldname and olddest.";
} else {
$base32 = $util->b32from64($parsed["b64"]);
if (!$pdo->exec("INSERT INTO `hosts` (`host`, `base64`, `base32`) VALUES ('" . $domain . "', '" . $parsed["b64"] . "', '" . $base32 . "')")) {
$result["error"] = "Error happened while inserting record to database. Please try again later.";
} else {
$result["host"] = $domain;
$result["base64"] = $parsed["b64"];
$result["base32"] = $base32;
}
}
}
break;
case 'adddest':
case 'changedest':
if (!isset($parsed["commands"]["olddest"]) || !isset($parsed["commands"]["oldsig"])) {
$result["error"] = "Error while validating: required fields not found. Re-check your registration string.";
} else {
if (!$pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "' AND `base64` = '" . $parsed["commands"]["olddest"] . "' LIMIT 1")->fetchColumn()) {
$result["error"] = "Error while validating: old base64 and value in olddest field does not match..";
} else {
$base32 = $util->b32from64($parsed["b64"]);
if (!$pdo->exec("UPDATE `hosts` SET `base64` = '" . $parsed["b64"] . "', `base32` = '" . $base32 . "' WHERE `host` = '" . $domain . "'")) {
$result["error"] = "Error happened while updating record in database. Please try again later.";
} else {
$result["host"] = $domain;
$result["base64"] = $parsed["b64"];
$result["base32"] = $base32;
}
}
}
break;
case 'addname':
if (!isset($parsed["commands"]["olddest"]) || !isset($parsed["commands"]["oldsig"])) {
$result["error"] = "Error while validating: required fields not found. Re-check your registration string.";
} else {
if (!$pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "' AND `base64` = '" . $parsed["commands"]["olddest"] . "' LIMIT 1")->fetchColumn()) {
$result["error"] = "Error while validating: old base64 and value in olddest field does not match..";
} else {
$base32 = $util->b32from64($parsed["b64"]);
if (!$pdo->exec("UPDATE `hosts` SET `base64` = '" . $parsed["b64"] . "', `base32` = '" . $base32 . "' WHERE `host` = '" . $domain . "'")) {
$result["error"] = "Error happened while updating record in database. Please try again later.";
} else {
$result["host"] = $domain;
$result["base64"] = $parsed["b64"];
$result["base32"] = $base32;
}
}
}
break;
default:
$result["error"] = "Error while validating: extended record fields are NOT supported for now.";
break;
}
} else {
if (isset($parsed["commands"]["oldname"]) || isset($parsed["commands"]["olddest"]) || isset($parsed["commands"]["oldsig"])) {
$result["error"] = "Error while validating: unexpected fields found.";
} else if (sizeof(explode(".", $domain)) > 2) {
$result["error"] = "Error while validating: you can't register subdomain without specific action field.";
} else {
$base32 = $util->b32from64($parsed["b64"]);
/* Adding to database 2LD domain */
if (!$pdo->exec("INSERT INTO `hosts` (`host`, `base64`, `base32`) VALUES ('" . $domain . "', '" . $parsed["b64"] . "', '" . $base32 . "')")) {
$result["error"] = "Error happened while inserting record to database. Please try again later.";
} else {
$result["host"] = $domain;
$result["base64"] = $parsed["b64"];
$result["base32"] = $base32;
}
}
}
}
}
}
} else if (isset($_POST["desc"]) && !empty($_POST["desc"])) {
$result["error"] = "Authentication string is required for registering record.";
}
if (!empty($result)) {
$record = $desc = ""; // clear them if any result present
}
$pdo = null;
$template = $twig->load('add.twig');
echo $template->render(['record' => $record, 'desc' => $desc, 'result' => $result]);

41
views/alive.php

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../config.php';
/* Initialize Twig engine */
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../templates');
$twig = new \Twig\Environment($loader, [
'cache' => __DIR__ . '/../cache',
'auto_reload' => true,
]);
$offset = $options["tableitems"] * ($page - 1);
$newregoffs = date ("Y-m-d H:i:s", strtotime ("-7 day"));
$newseenlim = date ("Y-m-d H:i:s", strtotime ("-3 day"));
$oldseenlim = date ("Y-m-d H:i:s", strtotime ("-7 day"));
$pdo = (new App\DB($options))->pdo;
/* Get records amount */
$STH = $pdo->query ("SELECT COUNT(*) as `count` FROM `hosts` " .
"WHERE `approved` = 1 AND (" .
" (`add_date` < '" . $newregoffs . "' AND `last_seen` > '" . $oldseenlim . "') OR" .
" (`add_date` > '" . $newregoffs . "' AND `last_seen` > '" . $newseenlim . "')" .
")");
$STH->setFetchMode (PDO::FETCH_ASSOC);
$records = $STH->fetch()["count"];
$pages = intdiv($records, $options["tableitems"]) + 1;
/* Get records with limit */
$STH = $pdo->query ("SELECT `host`, `base64`, `base32`, `last_seen` FROM `hosts` " .
"WHERE `approved` = 1 AND (" .
" (`add_date` < '" . $newregoffs . "' AND `last_seen` > '" . $oldseenlim . "') OR" .
" (`add_date` > '" . $newregoffs . "' AND `last_seen` > '" . $newseenlim . "')" .
") LIMIT " . $offset . ", " . $options["tableitems"]);
$STH->setFetchMode(PDO::FETCH_ASSOC);
$rows = $STH->fetchAll();
$template = $twig->load('alive.twig');
echo $template->render(['current' => $page, 'total' => $pages, 'hosts' => $rows]);

33
views/all.php

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../config.php';
/* Initialize Twig engine */
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../templates');
$twig = new \Twig\Environment($loader, [
'cache' => __DIR__ . '/../cache',
'auto_reload' => true,
]);
$offset = $options["tableitems"] * ($page - 1);
$newregoffs = date ("Y-m-d H:i:s", strtotime ("-7 day"));
$newseenlim = date ("Y-m-d H:i:s", strtotime ("-3 day"));
$oldseenlim = date ("Y-m-d H:i:s", strtotime ("-7 day"));
$pdo = (new App\DB($options))->pdo;
/* Get records amount */
$STH = $pdo->query ("SELECT COUNT(*) as `count` FROM `hosts`");
$STH->setFetchMode (PDO::FETCH_ASSOC);
$records = $STH->fetch()["count"];
$pages = intdiv($records, $options["tableitems"]) + 1;
/* Get records with limit */
$STH = $pdo->query ("SELECT `host`, `base64`, `base32`, `last_seen` FROM `hosts` LIMIT " . $offset . ", " . $options["tableitems"]);
$STH->setFetchMode(PDO::FETCH_ASSOC);
$rows = $STH->fetchAll();
$template = $twig->load('all.twig');
echo $template->render(['current' => $page, 'total' => $pages, 'hosts' => $rows]);

35
views/home.php

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../config.php';
/* Initialize Twig engine */
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../templates');
$twig = new \Twig\Environment($loader, [
'cache' => __DIR__ . '/../cache',
'auto_reload' => true,
]);
$subscrs = array();
if ($options['fetcher']) {
$pdo = (new App\DB($options))->pdo;
$STH = $pdo->query ("SELECT `name` FROM `subscriptions` WHERE `active` = 1");
$STH->setFetchMode(PDO::FETCH_ASSOC);
$subscrs = $STH->fetchAll();
}
$vars = array(
'approval' => $options['approval'],
'apprdelay' => $options['approvedelay'],
'apprseen' => $options['approveseen'],
'newdays' => $options['newdays'],
'delnewdays' => $options['delnewdays'],
'delolddays' => $options['delolddays'],
'fetcher' => $options['fetcher'],
'subscrs' => $subscrs
);
$template = $twig->load('home.twig');
echo $template->render($vars);

43
views/jump.php

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../config.php';
/* Initialize Twig engine */
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../templates');
$twig = new \Twig\Environment($loader, [
'cache' => __DIR__ . '/../cache',
'auto_reload' => true,
]);
$utils = new App\Utils;
$domain = "";
$result = [];
$error = "";
if (isset($query) && strlen($query) > 67 || isset($_POST["q"]) && strlen($_POST["q"]) > 67)
$result["error"] = "Request is too long, max length is 67 chars";
else if (isset($query) && !empty($query))
$domain = htmlspecialchars($query);
else if (isset($_POST["q"]))
$domain = htmlspecialchars($_POST["q"]);
if(!empty($domain) && !$utils->isValidDomain($domain, $error)) {
$domain = "";
$result["error"] = 'Not valid query: ' . $error;
}
else if(!empty($domain) && $utils->isValidDomain($domain, $error)) {
$pdo = (new App\DB($options))->pdo;
$STH = $pdo->query("SELECT `host`, `base64`, `base32`, `last_seen` FROM `hosts` WHERE `host` = '" . $domain . "' LIMIT 1");
$STH->setFetchMode(PDO::FETCH_ASSOC);
$row = $STH->fetchAll();
if (empty($row))
$result["error"] = "No such host is found";
else
$result = array_merge($result, $row[0]);
}
$template = $twig->load('jump.twig');
echo $template->render(['domain' => $domain, 'result' => $result]);

21
views/latest.php

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../config.php';
/* Initialize Twig engine */
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../templates');
$twig = new \Twig\Environment($loader, [
'cache' => __DIR__ . '/../cache',
'auto_reload' => true,
]);
$pdo = (new App\DB($options))->pdo;
/* Get records with limit */
$STH = $pdo->query ("SELECT `host`, `base64`, `base32`, `add_date` FROM `hosts` ORDER BY `add_date` DESC LIMIT " . $options["tableitems"]);
$STH->setFetchMode(PDO::FETCH_ASSOC);
$rows = $STH->fetchAll();
$template = $twig->load('latest.twig');
echo $template->render(['limit' => $options["tableitems"], 'hosts' => $rows]);

40
views/search.php

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../config.php';
/* Initialize Twig engine */
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../templates');
$twig = new \Twig\Environment($loader, [
'cache' => __DIR__ . '/../cache',
'auto_reload' => true,
]);
$utils = new App\Utils;
$result = [];
$q = "";
if (isset($query) && strlen($query) > 67 || isset($_POST["q"]) && strlen($_POST["q"]) > 67) {
$result["error"] = "Request is too long, max length is 67 chars";
} else if (isset($query) && !empty($query)) {
$q = htmlspecialchars($query);
} else if (isset($_POST["q"])) {
$q = htmlspecialchars($_POST["q"]);
}
if(!empty($q)) {
$pdo = (new App\DB($options))->pdo;
$STH = $pdo->query("SELECT `host`, `base64`, `base32`, `last_seen` FROM `hosts` WHERE `host` LIKE '%" . $q . "%' OR `base32` LIKE '%" . $q . "%' LIMIT " . $options["tableitems"]);
$STH->setFetchMode(PDO::FETCH_ASSOC);
$row = $STH->fetchAll();
if (empty($row))
$result["error"] = "Nothing was found";
else
$result = array_merge($result, $row);
}
$template = $twig->load('search.twig');
echo $template->render(['query' => $q ?: 'removed request', 'result' => $result, 'limit' => $options["tableitems"]]);
Loading…
Cancel
Save