Compare commits

..

24 commits
1.2.0 ... main

Author SHA1 Message Date
yggverse
5ae447bcf0 fix absolute address detection 2024-07-11 10:10:13 +03:00
yggverse
b28658d041 update readme 2024-06-25 00:10:36 +03:00
yggverse
9524c8fdec add new project integration 2024-06-24 23:04:50 +03:00
yggverse
0079e2a755 replace getAbsolute with toAbsolute 2024-06-23 00:17:31 +03:00
yggverse
0acff609f4 implement custom Address::get attributes 2024-04-16 18:44:00 +03:00
yggverse
4f369bac45 add integration 2024-04-09 10:32:46 +03:00
yggverse
7c918ff99c update readme 2024-04-07 21:08:28 +03:00
yggverse
5e17d40245 update readme 2024-04-07 21:00:17 +03:00
yggverse
27bf47ec0d fix variable name 2024-04-07 03:47:36 +03:00
yggverse
b37f28ce4f implement validation class 2024-04-07 02:45:35 +03:00
yggverse
f6a3b1bbfa use build in ip6 validator 2024-04-07 02:44:41 +03:00
yggverse
18ec72362e update readme 2024-04-07 02:27:57 +03:00
yggverse
d3bea00b02 implement url string resolve method 2024-04-07 02:20:23 +03:00
yggverse
38f33eb584 update address method api (outside address init) 2024-04-07 02:19:54 +03:00
yggverse
96cd4163be implement resolve feature 2024-04-07 00:58:48 +03:00
yggverse
1e5b42d407 add timeout attribute 2024-04-06 11:35:40 +03:00
yggverse
a57028f609 add custom provider support 2024-04-06 10:30:22 +03:00
yggverse
16f90cb23b fix missed path slash 2024-04-06 02:33:09 +03:00
yggverse
92eb9b3ba2 implement absolute address converter 2024-04-06 02:04:22 +03:00
yggverse
3329a90819 fix data types, add fragment support, draft absolute method 2024-04-05 11:17:14 +03:00
yggverse
bbdc276db6 add cross-platform separator support 2024-04-05 10:10:19 +03:00
yggverse
b2e0750d1e init Address class 2024-04-05 09:52:50 +03:00
ghost
fc2b0822af update link 2023-12-17 11:45:25 +02:00
ghost
c602ad503f update readme 2023-12-17 10:21:20 +02:00
5 changed files with 852 additions and 16 deletions

182
README.md
View file

@ -12,7 +12,7 @@ Network Library for PHP with native Yggdrasil support
#### Check socket is open
```
``` php
var_dump(
\Yggverse\Net\Socket::isOpen('yo.index', 80)
);
@ -20,7 +20,7 @@ var_dump(
#### Check host valid
```
``` php
var_dump(
\Yggverse\Net\Socket::isHost('yo.index')
);
@ -28,7 +28,7 @@ var_dump(
#### Check port valid
```
``` php
var_dump(
\Yggverse\Net\Socket::isPort(80)
);
@ -38,15 +38,15 @@ var_dump(
#### Resolve records
```
``` php
var_dump(
\Yggverse\Net\Dig::records('yo.index', ['A', 'AAAA'])
\Yggverse\Net\Dig::records('yo.index', ['A', 'AAAA'], &$result = [], &$error = [], $provider = null, $timeout = 5)
);
```
#### Check hostname valid
```
``` php
var_dump(
\Yggverse\Net\Dig::isHostName('yo.index')
);
@ -54,7 +54,7 @@ var_dump(
#### Check record valid
```
``` php
var_dump(
\Yggverse\Net\Dig::isRecord('A')
);
@ -62,8 +62,172 @@ var_dump(
#### Check record value valid
```
``` php
var_dump(
\Yggverse\Net\Dig::isRecordValue('A', '127.0.0.1')
);
```
```
### Resolve
#### Init resolver
``` php
$resolve = new \Yggverse\Net\Resolve(
[
'A',
'AAAA'
],
[
'1.1.1.1',
'8.8.8.8'
],
// ..
);
```
#### Get resolved URL string
``` php
$resolved = $resolve->url(
'https://en.wikipedia.org/wiki/Domain_Name_System'
// next arguments contain debug variables and new address object
);
if ($resolved)
{
var_dump(
$resolved // https://185.15.59.224/wiki/Domain_Name_System
);
}
```
#### Resolve Address object
``` php
$resolved = $resolve->address(
new \Yggverse\Net\Address(
'https://en.wikipedia.org/wiki/Domain_Name_System'
)
);
if ($resolved)
{
var_dump(
$resolved->get() // https://185.15.59.224/wiki/Domain_Name_System
);
var_dump(
$resolved->getHost() // 185.15.59.224
);
}
```
### Address
Includes methods to work with network addresses.
#### Base address methods
Different operations with address parts:
* `scheme`
* `user`
* `password`
* `host`
* `protocol`
* `path`
* `query`
* `fragment`
#### Address conversion from relative to absolute format
**Document root**
``` php
$base = new \Yggverse\Net\Address(
'http://yo.ygg/a1/b1/c1'
);
$address = new \Yggverse\Net\Address(
'/a2'
);
var_dump(
$address->getAbsolute(
$base
) // return http://yo.ygg/a2
);
```
**Current folder**
``` php
$base = new \Yggverse\Net\Address(
'http://yo.ygg/a1/b1/c1'
);
$address = new \Yggverse\Net\Address(
'c2'
);
var_dump(
$address->getAbsolute(
$base
) // return http://yo.ygg/a1/b1/c2
);
```
**Ending slash**
``` php
$base = new \Yggverse\Net\Address(
'http://yo.ygg/a1/b1/c1/'
);
$address = new \Yggverse\Net\Address(
'../../b2/c2'
);
var_dump(
$address->getAbsolute(
$base
) // return http://yo.ygg/a1/b2/c2
);
```
**All options**
``` php
$base = new \Yggverse\Net\Address(
'http://user:password@yo.ygg/a1/b1/c1?attribute=value#anchor'
);
$address = new \Yggverse\Net\Address(
'../../a2/b2?attribute2=value2#anchor2'
);
var_dump(
$address->getAbsolute(
$base
) // return http://user:password@yo.ygg/a2/b2?attribute2=value2#anchor2
);
```
### Valid
Network entities validation
**Supported methods**
* `Valid::ip`
* `Valid::ip4`
* `Valid::ip6`
* `Valid::domainHostName`
## Integrations
* [gemini-dl](https://github.com/YGGverse/gemini-dl) - CLI batch downloader for Gemini protocol
* [web-api](https://github.com/YGGverse/web-api) - Network API with native Yggdrasil/IPv6 support
* [Yo!](https://github.com/YGGverse/Yo) - Crawler and search engine for different networks
* [Yoda](https://github.com/YGGverse/Yoda) - Experimental PHP-GTK browser for Gemini protocol

401
src/Address.php Normal file
View file

@ -0,0 +1,401 @@
<?php
declare(strict_types=1);
namespace Yggverse\Net;
class Address
{
private ?string $_scheme = null;
private ?string $_host = null;
private ?string $_user = null;
private ?string $_pass = null;
private ?int $_port = null;
private ?string $_path = null;
private ?string $_query = null;
private ?string $_fragment = null;
private string $_separator = '/';
private array $_segments = [];
public function __construct(?string $address = null)
{
if ($address)
{
if ($scheme = parse_url($address, PHP_URL_SCHEME))
{
$this->setScheme(
(string) $scheme
);
}
if ($user = parse_url($address, PHP_URL_USER))
{
$this->setUser(
(string) $user
);
}
if ($pass = parse_url($address, PHP_URL_PASS))
{
$this->setPass(
(string) $pass
);
}
if ($host = parse_url($address, PHP_URL_HOST))
{
$this->setHost(
(string) $host
);
}
if ($port = parse_url($address, PHP_URL_PORT))
{
$this->setPort(
(int) $port
);
}
if ($path = parse_url($address, PHP_URL_PATH))
{
$this->setPath(
(string) $path
);
}
if ($query = parse_url($address, PHP_URL_QUERY))
{
$this->setQuery(
(string) $query
);
}
if ($fragment = parse_url($address, PHP_URL_FRAGMENT))
{
$this->setFragment(
(string) $fragment
);
}
}
}
public function isAbsolute(): bool
{
return boolval(
$this->_scheme || $this->_host
);
}
public function isRelative(): bool
{
return !$this->isAbsolute();
}
public function getScheme(): ?string
{
return $this->_scheme;
}
public function setScheme(?string $value): void
{
$this->_scheme = $value;
}
public function getHost(): ?string
{
return $this->_host;
}
public function setHost(?string $value): void
{
$this->_host = $value;
}
public function getUser(): ?string
{
return $this->_user;
}
public function setUser(?string $value): void
{
$this->_user = $value;
}
public function getPass(): ?string
{
return $this->_pass;
}
public function setPass(?string $value): void
{
$this->_pass = $value;
}
public function getPort(): ?int
{
return $this->_port;
}
public function setPort(?int $value): void
{
$this->_port = $value;
}
public function getPath(): ?string
{
return $this->_path;
}
public function setPath(?string $value): void
{
if (false !== strpos($value, '\\'))
{
$this->setSeparator(
'\\'
);
}
$this->_segments = explode(
$this->_separator,
$value
);
$this->_path = $value;
}
public function getSegments(): array
{
return $this->_segments;
}
public function getQuery(): ?string
{
return $this->_query;
}
public function setQuery(?string $value): void
{
$this->_query = $value;
}
public function getFragment(): ?string
{
return $this->_fragment;
}
public function setFragment(?string $value): void
{
$this->_fragment = $value;
}
public function getSeparator(): string
{
return $this->_separator;
}
public function setSeparator(?string $value): void
{
$this->_separator = $value;
}
public function get(
bool $scheme = true,
bool $user = true,
bool $pass = true,
bool $host = true,
bool $port = true,
bool $path = true,
bool $query = true,
bool $fragment = true,
): string
{
$address = '';
if ($scheme && $this->getScheme())
{
$address .= sprintf(
'%s:%s%s',
$this->getScheme(),
$this->getSeparator(),
$this->getSeparator()
);
}
if ($user && $this->getUser())
{
if ($pass && $this->getPass())
{
$address .= sprintf(
'%s:%s@',
$this->getUser(),
$this->getPass()
);
}
else
{
$address .= sprintf(
'%s@',
$this->getUser()
);
}
}
if ($host && $this->getHost())
{
$address .= $this->getHost();
}
if ($port && $this->getPort())
{
$address .= sprintf(
':%d',
$this->getPort()
);
}
if ($path && $this->getPath())
{
if (!str_starts_with($this->getPath(), $this->getSeparator()))
{
$address .= $this->getSeparator();
}
$address .= $this->getPath();
}
if ($query && $this->getQuery())
{
$address .= sprintf(
'?%s',
$this->getQuery()
);
}
if ($fragment && $this->getFragment())
{
$address .= sprintf(
'#%s',
$this->getFragment()
);
}
return $address;
}
public function toAbsolute(
\Yggverse\Net\Address $base
): bool
{
if ($this->isAbsolute())
{
return true;
}
if ($base->isRelative())
{
return false;
}
$this->setScheme(
$base->getScheme()
);
$this->setUser(
$base->getUser()
);
$this->setPass(
$base->getPass()
);
$this->setHost(
$base->getHost()
);
$this->setPort(
$base->getPort()
);
$this->setSeparator(
$base->getSeparator()
);
if (str_starts_with((string) $this->getPath(), $this->getSeparator()))
{
return true;
}
if ($path = $this->getPath())
{
$prefix = array_reverse(
$base->getSegments()
);
array_shift(
$prefix
);
$navigate = true;
$postfix = [];
foreach ($this->getSegments() as $index => $segment)
{
if ($segment == '.')
{
continue;
}
if ($navigate && $segment == '..')
{
if (empty($prefix[$index]))
{
return false;
}
unset(
$prefix[$index]
);
continue;
}
$navigate = false;
$postfix[] = $segment;
}
$this->setPath(
implode(
$this->getSeparator(),
array_merge(
array_reverse(
$prefix
),
$postfix
)
)
);
return true;
}
return false;
}
// @TODO deprecated, legacy needs only
public function getAbsolute(
\Yggverse\Net\Address $base
): ?string
{
if ($this->toAbsolute($base))
{
return $this->get();
}
return null;
}
}

View file

@ -17,32 +17,59 @@ class Dig
];
}
public static function isProvider(mixed $value): bool
{
return
(
is_string($value) &&
false !== filter_var($value, FILTER_VALIDATE_IP)
);
}
public static function isHostName(mixed $value, array $find = ['_'], array $replace = []): bool
{
return is_string($value) && false !== filter_var(str_replace($find, $replace, $value), FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
return (
is_string($value) &&
false !== filter_var(str_replace($find, $replace, $value), FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)
);
}
public static function isRecord(mixed $value): bool
{
return is_string($value) && isset(self::_records()[$value]);
return
(
is_string($value) &&
isset(self::_records()[$value])
);
}
public static function isRecordValue(mixed $record, mixed $value): bool
{
return is_string($record) &&
is_string($value) &&
isset(self::_records()[$record]) && self::_records()[$record]($value);
return
(
is_string($record) &&
is_string($value) &&
isset(self::_records()[$record]) && self::_records()[$record]($value)
);
}
public static function records(string $hostname, array $records, array &$result = [], array &$error = []): array
public static function records(string $hostname, array $records, array &$result = [], array &$error = [], ?string $provider = null, int $timeout = 5): array
{
if (self::isProvider($provider))
{
$provider = sprintf(
'@%s',
$provider
);
}
if (self::isHostName($hostname))
{
foreach ($records as $record)
{
if (self::isRecord($record))
{
if ($values = exec(sprintf('dig %s %s +short', $record, $hostname)))
if ($values = exec(sprintf('dig %s %s %s +short +time=%d', (string) $provider, $record, $hostname, $timeout)))
{
foreach (explode(PHP_EOL, $values) as $value)
{

204
src/Resolve.php Normal file
View file

@ -0,0 +1,204 @@
<?php
declare(strict_types=1);
namespace Yggverse\Net;
class Resolve
{
private array $_providers =
[
'1.1.1.1',
'8.8.8.8'
];
private array $_records =
[
'A',
'AAAA'
];
private int $_timeout = 5;
private bool $_shuffle = false;
public function __construct(
?array $records = null,
?array $providers = null,
?int $timeout = null,
bool $shuffle = false
) {
if ($records)
{
$this->setRecords(
$records
);
}
if ($providers)
{
$this->setProviders(
$providers
);
}
if ($timeout)
{
$this->setTimeout(
$timeout
);
}
$this->setShuffle(
$shuffle
);
}
public function getRecords(): array
{
return $this->_records;
}
public function setRecords(array $records): void
{
foreach ($records as $record)
{
$this->addRecord(
$record
);
}
}
public function addRecord(string $record): void
{
$this->_records[] = $record;
$this->_records = array_unique(
$this->_records
);
}
public function getProviders(): array
{
return $this->_providers;
}
public function setProviders(array $providers): void
{
foreach ($providers as $provider)
{
$this->addProvider(
$provider
);
}
}
public function addProvider(string $provider): void
{
$this->_providers[] = $provider;
$this->_providers = array_unique(
$this->_providers
);
}
public function setTimeout(
int $timeout
): void
{
$this->_timeout = $timeout;
}
public function getTimeout(): int
{
return $this->_timeout;
}
public function setShuffle(
bool $shuffle
): void
{
$this->_shuffle = $shuffle;
}
public function isShuffle(): bool
{
return $this->_shuffle;
}
public function url(
string $url,
array &$result = [],
array &$error = [],
?\Yggverse\Net\Address &$resolved = null
): ?string
{
$resolved = $this->address(
new \Yggverse\Net\Address(
$url
),
$result,
$error
);
if ($resolved)
{
return $resolved->get();
}
return null;
}
public function address(
\Yggverse\Net\Address $address,
array &$result = [],
array &$error = []
): ?\Yggverse\Net\Address
{
foreach ($this->_providers as $provider)
{
foreach (
\Yggverse\Net\Dig::records(
$address->getHost(),
$this->_records,
$result,
$error,
$provider,
$this->_timeout
) as $record => $data
) {
if ($this->_shuffle)
{
shuffle(
$data
);
}
foreach ($data as $host)
{
if (\Yggverse\Net\Valid::ip6($host))
{
$address->setHost(
sprintf(
'[%s]',
$host
)
);
}
else
{
$address->setHost(
$host
);
}
return $address;
}
}
}
return null;
}
}

40
src/Valid.php Normal file
View file

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Yggverse\Net;
class Valid
{
public static function ip(mixed $value): bool
{
return self::ip4($value) || self::ip6($value);
}
public static function ip4(mixed $value): bool
{
return false !== filter_var(
$value,
FILTER_VALIDATE_IP,
FILTER_FLAG_IPV4
);
}
public static function ip6(mixed $value): bool
{
return false !== filter_var(
$value,
FILTER_VALIDATE_IP,
FILTER_FLAG_IPV6
);
}
public static function domainHostName(mixed $value): bool
{
return false !== filter_var(
$value,
FILTER_VALIDATE_DOMAIN,
FILTER_FLAG_HOSTNAME
);
}
}