Initial Commit
This commit is contained in:
44
dependencies/amphp/hpack/src/HPack.php
vendored
Normal file
44
dependencies/amphp/hpack/src/HPack.php
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Internal\HPackNative;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Internal\HPackNghttp2;
|
||||
/**
|
||||
* @psalm-type HeaderArray = list<array{string, string}>
|
||||
*/
|
||||
final class HPack
|
||||
{
|
||||
/** @var HPackNative|HPackNghttp2 */
|
||||
private $implementation;
|
||||
public function __construct(int $tableSizeLimit = 4096)
|
||||
{
|
||||
if (HPackNghttp2::isSupported()) {
|
||||
$this->implementation = new HPackNghttp2($tableSizeLimit);
|
||||
} else {
|
||||
$this->implementation = new HPackNative($tableSizeLimit);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param string $input Input to decode.
|
||||
* @param int $maxSize Maximum deflated size.
|
||||
*
|
||||
* @return array|null Decoded headers.
|
||||
*/
|
||||
public function decode(string $input, int $maxSize) : ?array
|
||||
{
|
||||
return $this->implementation->decode($input, $maxSize);
|
||||
}
|
||||
/**
|
||||
* @param HeaderArray $headers Headers to encode.
|
||||
*
|
||||
* @return string Encoded headers.
|
||||
*
|
||||
* @throws HPackException If encoding fails.
|
||||
*/
|
||||
public function encode(array $headers) : string
|
||||
{
|
||||
return $this->implementation->encode($headers);
|
||||
}
|
||||
}
|
8
dependencies/amphp/hpack/src/HPackException.php
vendored
Normal file
8
dependencies/amphp/hpack/src/HPackException.php
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http;
|
||||
|
||||
final class HPackException extends \Exception
|
||||
{
|
||||
}
|
1082
dependencies/amphp/hpack/src/Internal/HPackNative.php
vendored
Normal file
1082
dependencies/amphp/hpack/src/Internal/HPackNative.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
203
dependencies/amphp/hpack/src/Internal/HPackNghttp2.php
vendored
Normal file
203
dependencies/amphp/hpack/src/Internal/HPackNghttp2.php
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Internal;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\HPack;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\HPackException;
|
||||
use FFI;
|
||||
/**
|
||||
* @internal
|
||||
* @psalm-import-type HeaderArray from HPack
|
||||
*/
|
||||
final class HPackNghttp2
|
||||
{
|
||||
private const FLAG_NO_INDEX = 0x1;
|
||||
private const FLAG_NO_COPY_NAME = 0x2;
|
||||
private const FLAG_NO_COPY_VALUE = 0x4;
|
||||
private const FLAG_NO_COPY = self::FLAG_NO_COPY_NAME | self::FLAG_NO_COPY_VALUE;
|
||||
private const FLAG_NO_COPY_SENSITIVE = self::FLAG_NO_COPY | self::FLAG_NO_INDEX;
|
||||
private const SENSITIVE_HEADERS = ['authorization' => self::FLAG_NO_COPY_SENSITIVE, 'cookie' => self::FLAG_NO_COPY_SENSITIVE, 'proxy-authorization' => self::FLAG_NO_COPY_SENSITIVE, 'set-cookie' => self::FLAG_NO_COPY_SENSITIVE];
|
||||
private static $ffi;
|
||||
private static $deflatePtrType;
|
||||
private static $inflatePtrType;
|
||||
private static $nvType;
|
||||
private static $nvSize;
|
||||
private static $charType;
|
||||
private static $uint8Type;
|
||||
private static $uint8PtrType;
|
||||
private static $decodeNv;
|
||||
private static $decodeNvPtr;
|
||||
private static $decodeFlags;
|
||||
private static $decodeFlagsPtr;
|
||||
private static $supported;
|
||||
public static function isSupported() : bool
|
||||
{
|
||||
if (isset(self::$supported)) {
|
||||
return self::$supported;
|
||||
}
|
||||
if (!\extension_loaded('ffi')) {
|
||||
return self::$supported = \false;
|
||||
}
|
||||
if (!\class_exists(FFI::class)) {
|
||||
return self::$supported = \false;
|
||||
}
|
||||
try {
|
||||
self::init();
|
||||
return self::$supported = \true;
|
||||
} catch (\Throwable $e) {
|
||||
return self::$supported = \false;
|
||||
}
|
||||
}
|
||||
private static function init() : void
|
||||
{
|
||||
if (self::$ffi !== null) {
|
||||
return;
|
||||
}
|
||||
$header = \file_get_contents(__DIR__ . '/amp-hpack.h');
|
||||
$files = ['libnghttp2.so.14', 'libnghttp2.so', 'libnghttp2.dylib', '/opt/homebrew/lib/libnghttp2.dylib'];
|
||||
$error = null;
|
||||
foreach ($files as $file) {
|
||||
try {
|
||||
self::$ffi = FFI::cdef($header, $file);
|
||||
$error = null;
|
||||
break;
|
||||
} catch (\Throwable $exception) {
|
||||
$error = $error ?? $exception;
|
||||
}
|
||||
}
|
||||
if ($error) {
|
||||
throw $error;
|
||||
}
|
||||
self::$deflatePtrType = self::$ffi->type('nghttp2_hd_deflater*');
|
||||
self::$inflatePtrType = self::$ffi->type('nghttp2_hd_inflater*');
|
||||
self::$nvType = self::$ffi->type('nghttp2_nv');
|
||||
self::$nvSize = FFI::sizeof(self::$nvType);
|
||||
self::$charType = self::$ffi->type('char');
|
||||
self::$uint8Type = self::$ffi->type('uint8_t');
|
||||
self::$uint8PtrType = self::$ffi->type('uint8_t*');
|
||||
self::$decodeNv = self::$ffi->new(self::$nvType);
|
||||
self::$decodeNvPtr = FFI::addr(self::$decodeNv);
|
||||
self::$decodeFlags = self::$ffi->new('int');
|
||||
self::$decodeFlagsPtr = FFI::addr(self::$decodeFlags);
|
||||
}
|
||||
private static function createBufferFromString(string $value) : ?FFI\CData
|
||||
{
|
||||
$length = \strlen($value);
|
||||
if (!$length) {
|
||||
return null;
|
||||
}
|
||||
$buffer = self::$ffi->new(FFI::arrayType(self::$uint8Type, [$length]));
|
||||
FFI::memcpy($buffer, $value, $length);
|
||||
return $buffer;
|
||||
}
|
||||
private $deflatePtr;
|
||||
private $inflatePtr;
|
||||
/**
|
||||
* @param int $maxSize Upper limit on table size.
|
||||
*/
|
||||
public function __construct(int $maxSize = 4096)
|
||||
{
|
||||
self::init();
|
||||
$this->deflatePtr = self::$ffi->new(self::$deflatePtrType);
|
||||
$this->inflatePtr = self::$ffi->new(self::$inflatePtrType);
|
||||
$return = self::$ffi->nghttp2_hd_deflate_new(FFI::addr($this->deflatePtr), $maxSize);
|
||||
if ($return !== 0) {
|
||||
throw new \RuntimeException('Failed to init deflate context');
|
||||
}
|
||||
$return = self::$ffi->nghttp2_hd_inflate_new(FFI::addr($this->inflatePtr));
|
||||
if ($return !== 0) {
|
||||
throw new \RuntimeException('Failed to init inflate context');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param string $input Encoded headers.
|
||||
* @param int $maxSize Maximum length of the decoded header string.
|
||||
*
|
||||
* @return string[][]|null Returns null if decoding fails or if $maxSize is exceeded.
|
||||
*/
|
||||
public function decode(string $input, int $maxSize) : ?array
|
||||
{
|
||||
$ffi = self::$ffi;
|
||||
$pair = self::$decodeNv;
|
||||
$pairPtr = self::$decodeNvPtr;
|
||||
$flags = self::$decodeFlags;
|
||||
$flagsPtr = self::$decodeFlagsPtr;
|
||||
$inflate = $this->inflatePtr;
|
||||
$size = 0;
|
||||
$bufferLength = \strlen($input);
|
||||
$buffer = self::createBufferFromString($input);
|
||||
if ($buffer === null) {
|
||||
return [];
|
||||
}
|
||||
$offset = 0;
|
||||
$bufferPtr = $ffi->cast(self::$uint8PtrType, $buffer);
|
||||
$headers = [];
|
||||
while (\true) {
|
||||
$read = $ffi->nghttp2_hd_inflate_hd2($inflate, $pairPtr, $flagsPtr, $bufferPtr, $bufferLength - $offset, 1);
|
||||
if ($read < 0) {
|
||||
return null;
|
||||
}
|
||||
$offset += $read;
|
||||
$bufferPtr += $read;
|
||||
$cFlags = $flags->cdata;
|
||||
if ($cFlags & 0x2) {
|
||||
// NGHTTP2_HD_INFLATE_EMIT
|
||||
$nameLength = $pair->namelen;
|
||||
$valueLength = $pair->valuelen;
|
||||
$headers[] = [FFI::string($pair->name, $nameLength), FFI::string($pair->value, $valueLength)];
|
||||
$size += $nameLength + $valueLength;
|
||||
if ($size > $maxSize) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if ($cFlags & 0x1) {
|
||||
// NGHTTP2_HD_INFLATE_FINAL
|
||||
$ffi->nghttp2_hd_inflate_end_headers($inflate);
|
||||
FFI::memset($pair, 0, self::$nvSize);
|
||||
return $headers;
|
||||
}
|
||||
if ($read === 0 || $offset > $bufferLength) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* @param HeaderArray $headers
|
||||
*
|
||||
* @return string Encoded headers.
|
||||
*/
|
||||
public function encode(array $headers) : string
|
||||
{
|
||||
$ffi = self::$ffi;
|
||||
// To keep memory buffers
|
||||
$buffers = [];
|
||||
$headerCount = \count($headers);
|
||||
$current = 0;
|
||||
$pairs = $ffi->new(FFI::arrayType(self::$nvType, [$headerCount]));
|
||||
foreach ($headers as $index => [$name, $value]) {
|
||||
\assert($index === $current);
|
||||
$name = (string) $name;
|
||||
$value = (string) $value;
|
||||
$pair = $pairs[$current];
|
||||
$nameBuffer = self::createBufferFromString($name);
|
||||
$valueBuffer = self::createBufferFromString($value);
|
||||
$pair->name = $ffi->cast(self::$uint8PtrType, $nameBuffer);
|
||||
$pair->namelen = \strlen($name);
|
||||
$pair->value = $ffi->cast(self::$uint8PtrType, $valueBuffer);
|
||||
$pair->valuelen = \strlen($value);
|
||||
$pair->flags = self::SENSITIVE_HEADERS[$name] ?? self::FLAG_NO_COPY;
|
||||
$buffers[] = $nameBuffer;
|
||||
$buffers[] = $valueBuffer;
|
||||
$current++;
|
||||
}
|
||||
$bufferLength = $ffi->nghttp2_hd_deflate_bound($this->deflatePtr, $pairs, $headerCount);
|
||||
$buffer = $ffi->new(FFI::arrayType(self::$uint8Type, [$bufferLength]));
|
||||
$bufferLength = $ffi->nghttp2_hd_deflate_hd($this->deflatePtr, $buffer, $bufferLength, $pairs, $headerCount);
|
||||
if ($bufferLength < 0) {
|
||||
throw new HPackException('Failed to compress headers using nghttp2');
|
||||
}
|
||||
return FFI::string($buffer, $bufferLength);
|
||||
}
|
||||
}
|
29
dependencies/amphp/hpack/src/Internal/amp-hpack.h
vendored
Normal file
29
dependencies/amphp/hpack/src/Internal/amp-hpack.h
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
#define FFI_SCOPE "amphp-hpack-nghttp2"
|
||||
#define FFI_LIB "libnghttp2.so"
|
||||
|
||||
typedef struct nghttp2_hd_deflater nghttp2_hd_deflater;
|
||||
|
||||
typedef struct nghttp2_hd_inflater nghttp2_hd_inflater;
|
||||
|
||||
typedef struct {
|
||||
uint8_t *name;
|
||||
uint8_t *value;
|
||||
|
||||
size_t namelen;
|
||||
size_t valuelen;
|
||||
|
||||
uint8_t flags;
|
||||
} nghttp2_nv;
|
||||
|
||||
int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, size_t deflate_hd_table_bufsize_max);
|
||||
|
||||
ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf, size_t buflen, const nghttp2_nv *nva, size_t nvlen);
|
||||
|
||||
size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, const nghttp2_nv *nva, size_t nvlen);
|
||||
|
||||
int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr);
|
||||
|
||||
ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, int *inflate_flags, const uint8_t *in, size_t inlen, int in_final);
|
||||
|
||||
int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater);
|
6
dependencies/amphp/hpack/src/Internal/huffman-codes.php
vendored
Normal file
6
dependencies/amphp/hpack/src/Internal/huffman-codes.php
vendored
Normal file
File diff suppressed because one or more lines are too long
6
dependencies/amphp/hpack/src/Internal/huffman-lookup.php
vendored
Normal file
6
dependencies/amphp/hpack/src/Internal/huffman-lookup.php
vendored
Normal file
File diff suppressed because one or more lines are too long
114
dependencies/amphp/hpack/tools/compress.php
vendored
Normal file
114
dependencies/amphp/hpack/tools/compress.php
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies;
|
||||
|
||||
/**
|
||||
* The MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2017 Christian Lück
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
// Taken from https://github.com/leproxy/leproxy/blob/e2ca7917c17ac8b853800f5b390297a2a1525cf7/compile.php
|
||||
$small = '';
|
||||
$all = \token_get_all(\file_get_contents($argv[1]));
|
||||
// search next non-whitespace/non-comment token
|
||||
$next = function ($i) use(&$all) {
|
||||
for ($i = $i + 1; !isset($all[$i]) || \is_array($all[$i]) && ($all[$i][0] === \T_COMMENT || $all[$i][0] === \T_DOC_COMMENT || $all[$i][0] === \T_WHITESPACE); ++$i) {
|
||||
}
|
||||
return $i;
|
||||
};
|
||||
// search previous non-whitespace/non-comment token
|
||||
$prev = function ($i) use(&$all) {
|
||||
for ($i = $i - 1; $i >= 0 && (!isset($all[$i]) || \is_array($all[$i]) && ($all[$i][0] === \T_COMMENT || $all[$i][0] === \T_DOC_COMMENT || $all[$i][0] === \T_WHITESPACE)); --$i) {
|
||||
}
|
||||
return $i;
|
||||
};
|
||||
$first = \true;
|
||||
foreach ($all as $i => $token) {
|
||||
if (\is_array($token) && ($token[0] === \T_COMMENT || $token[0] === \T_DOC_COMMENT)) {
|
||||
// remove all comments except first
|
||||
if ($first === \true) {
|
||||
$first = \false;
|
||||
continue;
|
||||
}
|
||||
unset($all[$i]);
|
||||
} elseif (\is_array($token) && $token[0] === \T_PUBLIC) {
|
||||
// get next non-whitespace token after `public` visibility
|
||||
$token = $all[$next($i)];
|
||||
if (\is_array($token) && $token[0] === \T_VARIABLE) {
|
||||
// use shorter variable notation `public $a` => `var $a`
|
||||
$all[$i] = [\T_VAR, 'var'];
|
||||
} else {
|
||||
// remove unneeded public identifier `public static function a()` => `static function a()`
|
||||
unset($all[$i]);
|
||||
}
|
||||
} elseif (\is_array($token) && $token[0] === \T_LNUMBER) {
|
||||
// Use shorter integer notation `0x0F` => `15` and `011` => `9`.
|
||||
// Technically, hex codes may be shorter for very large ints, but adding
|
||||
// another 2 leading chars is rarely worth it.
|
||||
// Optimizing floats is not really worth it, as they have many special
|
||||
// cases, such as e-notation and we would lose types for `0.0` => `0`.
|
||||
$all[$i][1] = (string) \intval($token[1], 0);
|
||||
} elseif (\is_array($token) && $token[0] === \T_NEW) {
|
||||
// remove unneeded parenthesis for constructors without args `new a();` => `new a;`
|
||||
// jump over next token (class name), then next must be open parenthesis, followed by closing
|
||||
$open = $next($next($i));
|
||||
$close = $next($open);
|
||||
if ($all[$open] === '(' && $all[$close] === ')') {
|
||||
unset($all[$open], $all[$close]);
|
||||
}
|
||||
} elseif (\is_array($token) && $token[0] === \T_STRING) {
|
||||
// replace certain functions with their shorter alias function name
|
||||
// http://php.net/manual/en/aliases.php
|
||||
static $replace = ['implode' => 'join', 'fwrite' => 'fputs', 'array_key_exists' => 'key_exists', 'current' => 'pos'];
|
||||
// check this has a replacement and "looks like" a function call
|
||||
// this works on a number of assumptions, such as not being aliased/namespaced
|
||||
if (isset($replace[$token[1]])) {
|
||||
$p = $all[$prev($i)];
|
||||
if ($all[$next($i)] === '(' && (!\is_array($p) || !\in_array($p[0], [\T_FUNCTION, \T_OBJECT_OPERATOR, \T_DOUBLE_COLON, \T_NEW]))) {
|
||||
$all[$i][1] = $replace[$all[$i][1]];
|
||||
}
|
||||
}
|
||||
} elseif (\is_array($token) && $token[0] === \T_EXIT) {
|
||||
// replace `exit` with shorter alias `die`
|
||||
// it's a language construct, not a function (see above)
|
||||
$all[$i][1] = 'die';
|
||||
} elseif (\is_array($token) && $token[0] === \T_RETURN) {
|
||||
// replace `return null;` with `return;`
|
||||
$t = $next($i);
|
||||
if (\is_array($all[$t]) && $all[$t][0] === \T_STRING && $all[$t][1] === 'null' && $all[$next($t)] === ';') {
|
||||
unset($all[$t]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$all = \array_values($all);
|
||||
foreach ($all as $i => $token) {
|
||||
if (\is_array($token) && $token[0] === \T_WHITESPACE) {
|
||||
if (\strpos($token[1], "\n") !== \false) {
|
||||
$token = \strpos("()[]<>=+-*/%|,.:?!'\"\n", \substr($small, -1)) === \false ? "\n" : '';
|
||||
} else {
|
||||
$last = \substr($small, -1);
|
||||
$next = isset($all[$i + 1]) ? \substr(\is_array($all[$i + 1]) ? $all[$i + 1][1] : $all[$i + 1], 0, 1) : ' ';
|
||||
$token = \strpos('()[]{}<>;=+-*/%&|,.:?!@\'"' . "\r\n", $last) !== \false || \strpos('()[]{}<>;=+-*/%&|,.:?!@\'"' . '\\$', $next) !== \false ? '' : ' ';
|
||||
}
|
||||
}
|
||||
$small .= isset($token[1]) ? $token[1] : $token;
|
||||
}
|
||||
\file_put_contents($argv[1], $small);
|
7
dependencies/amphp/hpack/tools/php-fuzzer/decode-crash.php
vendored
Normal file
7
dependencies/amphp/hpack/tools/php-fuzzer/decode-crash.php
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies;
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
use WP_Ultimo\Dependencies\Amp\Http\HPack;
|
||||
(new HPack())->decode(\file_get_contents($argv[1]), 8192);
|
17
dependencies/amphp/hpack/tools/php-fuzzer/decode-oracle.php
vendored
Normal file
17
dependencies/amphp/hpack/tools/php-fuzzer/decode-oracle.php
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies;
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Internal;
|
||||
$fuzzer->setTarget(function (string $input) {
|
||||
$result1 = (new Internal\HPackNghttp2())->decode($input, 8192);
|
||||
$result2 = (new Internal\HPackNative())->decode($input, 8192);
|
||||
if ($result1 === null || $result2 === null) {
|
||||
return;
|
||||
}
|
||||
if ($result1 !== $result2) {
|
||||
throw new \Error('Mismatch: ' . \var_export($result1, \true) . ' / ' . \var_export($result2, \true));
|
||||
}
|
||||
});
|
||||
$fuzzer->setMaxLen(1024);
|
10
dependencies/amphp/hpack/tools/php-fuzzer/decode.php
vendored
Normal file
10
dependencies/amphp/hpack/tools/php-fuzzer/decode.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies;
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
use WP_Ultimo\Dependencies\Amp\Http\HPack;
|
||||
$fuzzer->setTarget(function (string $input) {
|
||||
(new HPack())->decode($input, 8192);
|
||||
});
|
||||
$fuzzer->setMaxLen(1024);
|
Reference in New Issue
Block a user