diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..564a87c
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,16 @@
+# Dependabot configuration.
+#
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 5
+ commit-message:
+ prefix: "GH Actions:"
+ labels:
+ - "testing/chores/QA"
diff --git a/.github/workflows/cs.yml b/.github/workflows/cs.yml
new file mode 100644
index 0000000..1af918d
--- /dev/null
+++ b/.github/workflows/cs.yml
@@ -0,0 +1,67 @@
+name: CS
+
+on:
+ # Run on all pushes and on all pull requests.
+ push:
+ pull_request:
+ # Allow manually triggering the workflow.
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ # The concurrency group contains the workflow name and the branch name.
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ actionlint: #----------------------------------------------------------------------
+ name: 'Check GHA workflows'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Add problem matcher
+ if: ${{ github.event_name == 'pull_request' }}
+ shell: bash
+ run: |
+ curl -o actionlint-matcher.json https://raw.githubusercontent.com/rhysd/actionlint/main/.github/actionlint-matcher.json
+ echo "::add-matcher::actionlint-matcher.json"
+
+ - name: Check workflow files
+ uses: docker://rhysd/actionlint:latest
+ with:
+ args: -color
+
+ phpcs: #----------------------------------------------------------------------
+ name: 'PHPCS'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 'latest'
+ coverage: none
+ tools: cs2pr
+
+ # Install dependencies and handle caching in one go.
+ # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
+ - name: Install Composer dependencies
+ uses: "ramsey/composer-install@v3"
+ with:
+ # Bust the cache at least once a month - output format: YYYY-MM.
+ custom-cache-suffix: $(date -u "+%Y-%m")
+
+ # Check the code-style consistency of the PHP files.
+ - name: Check PHP code style
+ id: phpcs
+ run: composer checkcs -- --report-full --report-checkstyle=./phpcs-report.xml
+
+ - name: Show PHPCS results in PR
+ if: ${{ always() && steps.phpcs.outcome == 'failure' }}
+ run: cs2pr ./phpcs-report.xml
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..0dda897
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,50 @@
+name: Lint
+
+on:
+ # Run on all pushes and on all pull requests.
+ push:
+ pull_request:
+ # Allow manually triggering the workflow.
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ # The concurrency group contains the workflow name and the branch name.
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ lint: #----------------------------------------------------------------------
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ php: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
+
+ name: "Lint: PHP ${{ matrix.php }}"
+ continue-on-error: ${{ matrix.php == '8.5' }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ ini-values: error_reporting=-1, display_errors=On, log_errors_max_len=0
+ coverage: none
+ tools: cs2pr
+
+ # Install dependencies and handle caching in one go.
+ # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
+ - name: Install Composer dependencies
+ uses: "ramsey/composer-install@v3"
+ with:
+ # For PHP "nightly", we need to install with ignore platform reqs.
+ composer-options: ${{ matrix.php == '8.5' && '--ignore-platform-req=php+' || '' }}
+ # Bust the cache at least once a month - output format: YYYY-MM.
+ custom-cache-suffix: $(date -u "+%Y-%m")
+
+ - name: Lint against parse errors
+ run: composer lint -- --checkstyle | cs2pr
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a99da57
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+# Ignore temporary files.
+/bin/http.log
+/bin/http.pid
+
+# Ignore composer related files
+/composer.lock
+/vendor
+
+# Ignore local overrides of the PHPCS config file.
+.phpcs.xml
+phpcs.xml
diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist
new file mode 100644
index 0000000..ed4f5cf
--- /dev/null
+++ b/.phpcs.xml.dist
@@ -0,0 +1,72 @@
+
+
+
+ Requests Test Server rules for PHP_CodeSniffer
+
+
+
+
+ .
+
+
+ */vendor/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/.gitignore b/bin/.gitignore
deleted file mode 100644
index 216b80e..0000000
--- a/bin/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-http.log
-http.pid
\ No newline at end of file
diff --git a/bin/serve.php b/bin/serve.php
index 071148c..e7121cf 100644
--- a/bin/serve.php
+++ b/bin/serve.php
@@ -1,19 +1,19 @@
$value) {
if ($name === 'CONTENT_TYPE') {
if ($value !== '') {
@@ -66,7 +64,9 @@
'url' => $scheme . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
'headers' => $headers,
'origin' => $_SERVER['REMOTE_ADDR'],
- 'args' => empty($_SERVER['QUERY_STRING']) ? new stdClass : Requests\TestServer\parse_params_rfc( $_SERVER['QUERY_STRING'] ),
+ 'args' => empty($_SERVER['QUERY_STRING'])
+ ? new stdClass()
+ : Requests\TestServer\parse_params_rfc($_SERVER['QUERY_STRING']),
];
$routes = Requests\TestServer\get_routes();
@@ -81,8 +81,9 @@
foreach ($routes as $route => $callback) {
$route = preg_replace('#<(\w+)>#i', '(?P<\1>\w+)', $route);
$match = preg_match('#^' . $route . '$#i', $here, $matches);
- if (empty($match))
+ if (empty($match)) {
continue;
+ }
$data = $callback;
break;
@@ -95,10 +96,9 @@
while (is_callable($data)) {
$data = call_user_func($data, $matches);
}
-}
-catch (Exception $e) {
+} catch (Exception $e) {
http_response_code($e->getCode());
- $data = [ 'message' => $e->getMessage() ];
+ $data = ['message' => $e->getMessage()];
}
-echo json_encode($data, JSON_PRETTY_PRINT);
\ No newline at end of file
+echo json_encode($data, JSON_PRETTY_PRINT);
diff --git a/composer.json b/composer.json
index 7e4aa01..791bf66 100644
--- a/composer.json
+++ b/composer.json
@@ -1,18 +1,65 @@
{
"name": "requests/test-server",
"description": "Test server to respond to Requests' tests!",
- "homepage": "http://github.com/RequestsPHP/test-server",
"license": "ISC",
- "keywords": ["http", "test"],
+ "type": "library",
+ "keywords": [
+ "http",
+ "test"
+ ],
"authors": [
{
"name": "Ryan McCue",
"homepage": "http://ryanmccue.info"
+ },
+ {
+ "name": "Alain Schlesser",
+ "homepage": "https://github.com/schlessera"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/RequestsPHP/test-server/graphs/contributors"
}
],
+ "homepage": "http://github.com/RequestsPHP/test-server",
"require": {
"php": ">=5.6"
},
- "type": "library",
- "bin": [ "bin/start.sh", "bin/stop.sh", "bin/serve.php" ]
+ "require-dev": {
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.4",
+ "phpcompatibility/php-compatibility": "dev-develop",
+ "squizlabs/php_codesniffer": "^3.11"
+ },
+ "bin": [
+ "bin/serve.php",
+ "bin/start.sh",
+ "bin/stop.sh"
+ ],
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ },
+ "lock": false
+ },
+ "scripts": {
+ "checkcs": [
+ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs"
+ ],
+ "fixcs": [
+ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf"
+ ],
+ "lint": [
+ "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . --show-deprecated -e php --exclude vendor --exclude .git"
+ ]
+ },
+ "scripts-descriptions": {
+ "checkcs": "Check the entire codebase for code-style issues.",
+ "fixcs": "Fix all auto-fixable code-style issues in the entire codebase.",
+ "lint": "Lint PHP files to find parse errors."
+ }
}
diff --git a/lib/routes.php b/lib/routes.php
index 0c57d3c..02bf549 100644
--- a/lib/routes.php
+++ b/lib/routes.php
@@ -4,12 +4,13 @@
use Exception;
-function get_routes() {
+function get_routes()
+{
global $request_data, $base_url;
$routes = [];
// Request data!
- $routes['/get'] = function () use ($request_data) {
+ $routes['/get'] = static function () use ($request_data) {
if ($_SERVER['REQUEST_METHOD'] === 'HEAD') {
exit;
}
@@ -19,70 +20,70 @@ function get_routes() {
}
return $request_data;
};
- $routes['/post'] = function () {
+ $routes['/post'] = static function () {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
throw new Exception('Method not allowed', 405);
}
- return Response::generate_post_data();
+ return Response::generatePostData();
};
- $routes['/put'] = function () {
+ $routes['/put'] = static function () {
if ($_SERVER['REQUEST_METHOD'] !== 'PUT') {
throw new Exception('Method not allowed', 405);
}
- return Response::generate_post_data();
+ return Response::generatePostData();
};
- $routes['/patch'] = function () {
+ $routes['/patch'] = static function () {
if ($_SERVER['REQUEST_METHOD'] !== 'PATCH') {
throw new Exception('Method not allowed', 405);
}
- return Response::generate_post_data();
+ return Response::generatePostData();
};
- $routes['/delete'] = function () {
+ $routes['/delete'] = static function () {
if ($_SERVER['REQUEST_METHOD'] !== 'DELETE') {
throw new Exception('Method not allowed', 405);
}
- return Response::generate_post_data();
+ return Response::generatePostData();
};
- $routes['/options'] = function () {
+ $routes['/options'] = static function () {
if ($_SERVER['REQUEST_METHOD'] !== 'OPTIONS') {
throw new Exception('Method not allowed', 405);
}
- return Response::generate_post_data();
+ return Response::generatePostData();
};
- $routes['/trace'] = function () use ($request_data) {
+ $routes['/trace'] = static function () use ($request_data) {
if ($_SERVER['REQUEST_METHOD'] !== 'TRACE') {
throw new Exception('Method not allowed', 405);
}
return $request_data;
};
- $routes['/purge'] = function () {
+ $routes['/purge'] = static function () {
if ($_SERVER['REQUEST_METHOD'] !== 'PURGE') {
throw new Exception('Method not allowed', 405);
}
- return Response::generate_post_data();
+ return Response::generatePostData();
};
- $routes['/lock'] = function () {
+ $routes['/lock'] = static function () {
if ($_SERVER['REQUEST_METHOD'] !== 'LOCK') {
throw new Exception('Method not allowed', 405);
}
- return Response::generate_post_data();
+ return Response::generatePostData();
};
// Cookies!
- $routes['/cookies'] = function () {
+ $routes['/cookies'] = static function () {
return [
'cookies' => $_COOKIE,
];
};
- $routes['/cookies/set'] = function () {
+ $routes['/cookies/set'] = static function () {
foreach ($_GET as $key => $value) {
setcookie($key, $value, 0, '/');
}
@@ -90,14 +91,14 @@ function get_routes() {
Response::redirect('/cookies');
exit;
};
- $routes['/cookies/set//'] = function ($args) {
+ $routes['/cookies/set//'] = static function ($args) {
$expiry = isset($_GET['expiry']) ? (int) $_GET['expiry'] : 0;
setcookie($args['key'], $args['value'], $expiry, '/');
Response::redirect('/cookies');
exit;
};
- $routes['/cookies/delete'] = function () {
+ $routes['/cookies/delete'] = static function () {
foreach ($_GET as $key => $value) {
setcookie($key, '', time() - 3600, '/');
}
@@ -106,7 +107,7 @@ function get_routes() {
exit;
};
- $routes['/basic-auth//'] = function ($args) {
+ $routes['/basic-auth//'] = static function ($args) {
$supplied = [
'user' => empty($_SERVER['PHP_AUTH_USER']) ? false : $_SERVER['PHP_AUTH_USER'],
'password' => empty($_SERVER['PHP_AUTH_PW']) ? false : $_SERVER['PHP_AUTH_PW'],
@@ -114,7 +115,7 @@ function get_routes() {
if ($args['user'] !== $supplied['user'] || $args['password'] !== $supplied['password']) {
http_response_code(401);
- header( 'WWW-Authenticate: Basic realm="Fake Realm"' );
+ header('WWW-Authenticate: Basic realm="Fake Realm"');
return;
}
@@ -125,7 +126,7 @@ function get_routes() {
};
// Redirects!
- $routes['/redirect/'] = function ($args) use ($routes) {
+ $routes['/redirect/'] = static function ($args) use ($routes) {
$num = (int) max((int) $args['number'], 1);
if ($num === 1) {
Response::redirect('/get');
@@ -137,12 +138,12 @@ function get_routes() {
Response::redirect(sprintf('/redirect/%d', $num));
exit;
};
- $routes['/redirect-to'] = function () {
+ $routes['/redirect-to'] = static function () {
$location = $_GET['url'];
header('Location: ' . $location, true, 302);
exit;
};
- $routes['/relative-redirect/'] = function ($args) {
+ $routes['/relative-redirect/'] = static function ($args) {
$num = (int) max((int) $args['number'], 1);
if ($num === 1) {
Response::redirect('/get', 302, true);
@@ -156,13 +157,13 @@ function get_routes() {
};
// Miscellaneous!
- $routes['/delay/'] = function ($args) use ($routes) {
+ $routes['/delay/'] = static function ($args) use ($routes) {
$delay = min($args['delay'], 10);
sleep($delay);
return $routes['/get'];
};
- $routes['/status/'] = function ($args) use ($base_url) {
+ $routes['/status/'] = static function ($args) use ($base_url) {
$code = (int) $args['code'];
switch ($code) {
@@ -186,25 +187,25 @@ function get_routes() {
http_response_code($code);
exit;
};
- $routes['/stream/'] = function ($args) use ($request_data) {
+ $routes['/stream/'] = static function ($args) use ($request_data) {
$response = $request_data;
$num = min($args['num'], 100);
- $generate_stream = function () use ($num, $response) {
+ $generate_stream = static function () use ($num, $response) {
foreach (range(0, $num - 1) as $n) {
$response['id'] = $n;
- yield json_encode( $response, JSON_PRETTY_PRINT ) . "\n";
+ yield json_encode($response, JSON_PRETTY_PRINT) . "\n";
}
};
header('Transfer-Encoding: chunked');
- foreach ( $generate_stream() as $response ) {
+ foreach ($generate_stream() as $response) {
printf("%x\r\n%s\r\n", strlen($response), $response);
flush();
}
echo "0\r\n\r\n";
exit;
};
- $routes['/gzip'] = function () use ($request_data) {
+ $routes['/gzip'] = static function () use ($request_data) {
$response = $request_data;
$response['gzipped'] = true;
@@ -217,7 +218,7 @@ function get_routes() {
echo $response;
exit;
};
- $routes['/bytes/'] = function ($args) {
+ $routes['/bytes/'] = static function ($args) {
header('Content-Type: application/octet-stream');
mt_srand(0);
@@ -232,12 +233,12 @@ function get_routes() {
};
// Finally, the index!
- $routes['/'] = function () use ($routes) {
+ $routes['/'] = static function () use ($routes) {
header('Content-Type: text/html; charset=utf-8');
echo '';
foreach ($routes as $url => $_) {
- echo '' . htmlspecialchars( $url ) . ' ';
+ echo '' . htmlspecialchars($url, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401) . ' ';
}
echo '
';
exit;
diff --git a/lib/utils.php b/lib/utils.php
index 96544a2..e57a866 100644
--- a/lib/utils.php
+++ b/lib/utils.php
@@ -2,8 +2,10 @@
namespace Requests\TestServer;
-class Response {
- public static function redirect ($path, $code = 302, $relative = false) {
+class Response
+{
+ public static function redirect($path, $code = 302, $relative = false)
+ {
global $base_url;
$url = $path;
if (!$relative) {
@@ -13,31 +15,39 @@ public static function redirect ($path, $code = 302, $relative = false) {
header('Location: ' . $url, true, $code);
}
- public static function generate_post_data() {
+ public static function generatePostData()
+ {
global $request_data;
$data = $request_data;
$data['data'] = file_get_contents('php://input');
$data['form'] = '';
- if (strpos($data['data'], '&') !== false)
+ if (strpos($data['data'], '&') !== false) {
$data['form'] = parse_params_rfc($data['data']);
+ }
$data['json'] = json_decode($data['data']);
- $data['files'] = array_map(function ($data) {
- return file_get_contents($data['tmp_name']);
- }, $_FILES);
+ $data['files'] = array_map(
+ static function ($data) {
+ return file_get_contents($data['tmp_name']);
+ },
+ $_FILES
+ );
return $data;
}
}
-function parse_params_rfc($input) {
- if (!isset($input) || !$input) return array();
+function parse_params_rfc($input)
+{
+ if (!isset($input) || !$input) {
+ return [];
+ }
$pairs = explode('&', $input);
- $parsed = array();
+ $parsed = [];
foreach ($pairs as $pair) {
$split = explode('=', $pair, 2);
$parameter = urldecode($split[0]);
@@ -45,4 +55,4 @@ function parse_params_rfc($input) {
$parsed[$parameter] = $value;
}
return $parsed;
-}
\ No newline at end of file
+}