Browse Source

reg.i2p files

Signed-off-by: r4sas <r4sas@i2pmail.org>
master
R4SAS 3 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("-" => "+", "~" => "/");