The clean & modern RSS server that doesn't give you any crap. https://thearsse.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

131 lines
4.6 KiB

<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\REST;
use JKingWeb\Arsse\Misc\ValueInfo;
class Target {
public $relative = false;
public $index = false;
public $path = [];
public $query = "";
public $fragment = "";
public function __construct(string $target) {
$target = $this->parseFragment($target);
$target = $this->parseQuery($target);
$this->path = $this->parsePath($target);
}
public function __toString(): string {
$out = "";
$path = [];
foreach ($this->path as $segment) {
if (is_null($segment)) {
if (!$path) {
$path[] = "..";
} else {
continue;
}
} elseif ($segment === ".") {
$path[] = "%2E";
} elseif ($segment === "..") {
$path[] = "%2E%2E";
} else {
$path[] = rawurlencode(ValueInfo::normalize($segment, ValueInfo::T_STRING));
}
}
$path = implode("/", $path);
if (!$this->relative) {
$out .= "/";
}
$out .= $path;
if ($this->index && strlen($path)) {
$out .= "/";
}
if (strlen($this->query)) {
$out .= "?".$this->query;
}
if (strlen($this->fragment)) {
$out .= "#".rawurlencode($this->fragment);
}
return $out;
}
public static function normalize(string $target): string {
return (string) new self($target);
}
protected function parseFragment(string $target): string {
// store and strip off any fragment identifier and return the target without a fragment
$pos = strpos($target, "#");
if ($pos !== false) {
$this->fragment = rawurldecode(substr($target, $pos + 1));
$target = substr($target, 0, $pos);
}
return $target;
}
protected function parseQuery(string $target): string {
// store and strip off any query string and return the target without a query
// note that the function assumes any fragment identifier has already been stripped off
// unlike the other parts the query string is currently neither parsed nor normalized
$pos = strpos($target, "?");
if ($pos !== false) {
$this->query = substr($target, $pos + 1);
$target = substr($target, 0, $pos);
}
return $target;
}
protected function parsePath(string $target): array {
// note that the function assumes any fragment identifier or query has already been stripped off
// syntax-based normalization is applied to the path segments (see RFC 3986 sec. 6.2.2)
// duplicate slashes are NOT collapsed
if (substr($target, 0, 1) === "/") {
// if the path starts with a slash, strip it off
$target = substr($target, 1);
} else {
// otherwise this is a relative target
$this->relative = true;
}
if (!strlen($target)) {
// if the target is an empty string, this is an index target
$this->index = true;
} elseif (substr($target, -1, 1) === "/") {
// if the path ends in a slash, this is an index target and the slash should be stripped off
$this->index = true;
$target = substr($target, 0, strlen($target) -1);
}
// after stripping, explode the path parts
if (strlen($target)) {
$target = explode("/", $target);
$out = [];
// resolve relative path segments and decode each retained segment
foreach ($target as $index => $segment) {
if ($segment === ".") {
// self-referential segments can be ignored
continue;
} elseif ($segment === "..") {
if ($index == 0) {
// if the first path segment refers to its parent (which we don't know about) we cannot output a correct path, so we do the best we can
$out[] = null;
} else {
// for any other segments after the first we pop off the last stored segment
array_pop($out);
}
} else {
// any other segment is decoded and retained
$out[] = rawurldecode($segment);
}
}
return $out;
} else {
return [];
}
}
}