複数ネームスペースを可能に
This commit is contained in:
693
src/Std/Lib/Curl.php
Normal file
693
src/Std/Lib/Curl.php
Normal file
@@ -0,0 +1,693 @@
|
||||
<?php
|
||||
namespace Std\Lib;
|
||||
|
||||
/**
|
||||
* php_curlへの依存を排除するための独自のCURL実装
|
||||
*/
|
||||
class Curl {
|
||||
// リクエスト関連のプロパティ
|
||||
private string $url = '';
|
||||
private string $method = 'GET';
|
||||
private int $timeout = 30;
|
||||
private array $headers = [];
|
||||
private array $cookies = [];
|
||||
private array $postFields = [];
|
||||
private string $postRaw = '';
|
||||
private string $userAgent = 'LittleBeast/1.0';
|
||||
private bool $followRedirects = true;
|
||||
private int $maxRedirects = 5;
|
||||
private bool $verbose = false;
|
||||
private $stderr = null;
|
||||
private string $caInfoPath = '';
|
||||
private bool $verifySSL = true;
|
||||
private string $username = '';
|
||||
private string $password = '';
|
||||
private string $referer = '';
|
||||
|
||||
// レスポンス関連のプロパティ
|
||||
private array $responseHeaders = [];
|
||||
private string $responseBody = '';
|
||||
private int $responseCode = 0;
|
||||
private string $responseError = '';
|
||||
private array $info = [];
|
||||
|
||||
/**
|
||||
* コンストラクタ
|
||||
*
|
||||
* @param string|null $url リクエスト先のURL
|
||||
*/
|
||||
public function __construct(?string $url = null) {
|
||||
if (!CURL_ENABLED) return;
|
||||
if ($url !== null) {
|
||||
$this->url = $url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* リクエスト先のURLを設定する
|
||||
*
|
||||
* @param string $url リクエスト先のURL
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setUrl(string $url): Curl {
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* リクエストメソッドを設定する
|
||||
*
|
||||
* @param string $method GE又はPOST等のHTTPメソッド
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setMethod(string $method): Curl {
|
||||
$this->method = strtoupper($method);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* リクエストのタイムアウト秒数を設定する
|
||||
*
|
||||
* @param int $seconds タイムアウト秒数
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setTimeout(int $seconds): Curl {
|
||||
$this->timeout = (int)$seconds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* リクエストヘッダーを設定する
|
||||
*
|
||||
* @param array $headers リクエストヘッダーの配列
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setHeaders(array $headers): Curl {
|
||||
$this->headers = $headers;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 単一のヘッダーを追加する
|
||||
*
|
||||
* @param string $name ヘッダー名
|
||||
* @param mixed $value ヘッダー値
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function addHeader(string $name, mixed $value): Curl {
|
||||
$this->headers[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* リクエストのクッキーを設定する
|
||||
*
|
||||
* @param array $cookies クッキーの配列
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setCookies(array $cookies): Curl {
|
||||
$this->cookies = $cookies;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 単一のクッキーを追加する
|
||||
*
|
||||
* @param string $name クッキー名
|
||||
* @param mixed $value クッキー値
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function addCookie(string $name, mixed $value): Curl {
|
||||
$this->cookies[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* POSTフィールドを設定する
|
||||
*
|
||||
* @param array $fields POSTデータの配列
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setPostFields(array $fields): Curl {
|
||||
$this->postFields = $fields;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生のPOSTデータを設定する
|
||||
*
|
||||
* @param string $data 生のPOSTデータ
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setPostRaw(string $data): Curl {
|
||||
$this->postRaw = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* ユーザーエージェントを設定する
|
||||
*
|
||||
* @param string $userAgent カスタムユーザーエージェント
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setUserAgent(string $userAgent): Curl {
|
||||
$this->userAgent = $userAgent;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* リダイレクトを追跡するかどうかを設定する
|
||||
*
|
||||
* @param bool $follow 追跡するかどうか(デフォルトはtrue)
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setFollowRedirects(bool $follow): Curl {
|
||||
$this->followRedirects = (bool)$follow;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追跡するリダイレクトの最大数を設定する
|
||||
*
|
||||
* @param int $max リダイレクトの最大数
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setMaxRedirects(int $max): Curl {
|
||||
$this->maxRedirects = (int)$max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* SSL証明書を検証するかどうかを設定する
|
||||
*
|
||||
* @param bool $verify SSL検証を行うかどうか(デフォルトはtrue)
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setVerifySSL(bool $verify): Curl {
|
||||
$this->verifySSL = (bool)$verify;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基本認証の資格情報を設定する
|
||||
*
|
||||
* @param string $username ユーザー名
|
||||
* @param string $password パスワード
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setBasicAuth(string $username, string $password): Curl {
|
||||
$this->username = $username;
|
||||
$this->password = $password;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* リファラーURLを設定する
|
||||
*
|
||||
* @param string $referer リファラーURL
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setReferer(string $referer): Curl {
|
||||
$this->referer = $referer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 詳細ログを有効にする
|
||||
*
|
||||
* @param bool $verbose 詳細ログを有効にするかどうか
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setVerbose(bool $verbose): Curl {
|
||||
$this->verbose = (bool)$verbose;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* エラー出力先を設定する
|
||||
*
|
||||
* @param resource $handle エラー出力先のファイルハンドル
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setStderr($handle): Curl {
|
||||
$this->stderr = $handle;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* SSL証明書のファイルパスを設定する
|
||||
*
|
||||
* @param string $path 証明書ファイルのパス
|
||||
* @return Curl このインスタンス(メソッドチェーン用)
|
||||
*/
|
||||
public function setCaInfo(string $path): Curl {
|
||||
$this->caInfoPath = $path;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* リクエストを実行する
|
||||
*
|
||||
* @return Result 成功または失敗
|
||||
*/
|
||||
public function execute(): \Result {
|
||||
if (!CURL_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
|
||||
if (empty($this->url)) {
|
||||
$this->responseError = 'URLがありません';
|
||||
return \Result::Error($this->responseError);
|
||||
}
|
||||
|
||||
// レスポンスデータのリセット
|
||||
$this->responseHeaders = [];
|
||||
$this->responseBody = '';
|
||||
$this->responseCode = 0;
|
||||
$this->responseError = '';
|
||||
$this->info = [
|
||||
'url' => $this->url,
|
||||
'content_type' => '',
|
||||
'http_code' => 0,
|
||||
'header_size' => 0,
|
||||
'request_size' => 0,
|
||||
'total_time' => 0,
|
||||
'redirect_count' => 0,
|
||||
'redirect_url' => '',
|
||||
];
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
// ソケットベースの実装を使用する
|
||||
$redirectCount = 0;
|
||||
$currentUrl = $this->url;
|
||||
$originalMethod = $this->method;
|
||||
|
||||
do {
|
||||
if ($this->verbose && $this->stderr) {
|
||||
fwrite($this->stderr, "* 接続中: {$currentUrl}\n");
|
||||
}
|
||||
|
||||
$parsed = parse_url($currentUrl);
|
||||
if (!$parsed) {
|
||||
$this->responseError = "無効なURL: {$currentUrl}";
|
||||
return \Result::Error($this->responseError);
|
||||
}
|
||||
|
||||
$scheme = isset($parsed['scheme']) ? strtolower($parsed['scheme']) : 'http';
|
||||
$host = $parsed['host'];
|
||||
$port = isset($parsed['port'])
|
||||
? $parsed['port'] : ($scheme === 'https' ? 443 : 80);
|
||||
$path = isset($parsed['path']) ? $parsed['path'] : '/';
|
||||
if (isset($parsed['query'])) {
|
||||
$path .= '?'.$parsed['query'];
|
||||
}
|
||||
|
||||
// Basic認証
|
||||
$authHeader = '';
|
||||
if (!empty($this->username) && !empty($this->password)) {
|
||||
$authHeader = "Authorization: Basic "
|
||||
.base64_encode($this->username.':'.$this->password)."\r\n";
|
||||
} elseif (isset($parsed['user']) && isset($parsed['pass'])) {
|
||||
$authHeader = "Authorization: Basic "
|
||||
.base64_encode($parsed['user'].':'.$parsed['pass'])."\r\n";
|
||||
}
|
||||
|
||||
// 送信するHTTPリクエストを構築
|
||||
$method = $this->method;
|
||||
$httpData = '';
|
||||
|
||||
if ($method === 'POST' || $method === 'PUT') {
|
||||
if (!empty($this->postRaw)) {
|
||||
$httpData = $this->postRaw;
|
||||
} elseif (!empty($this->postFields)) {
|
||||
$httpData = http_build_query($this->postFields);
|
||||
if (!isset($this->headers['Content-Type'])) {
|
||||
$this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
}
|
||||
$this->headers['Content-Length'] = strlen($httpData);
|
||||
}
|
||||
|
||||
// HTTPリクエストを構築
|
||||
$accept = 'Accept: */*';
|
||||
foreach ($this->headers as $h) {
|
||||
if (str_contains($h, 'Accept:')) $accept = $h;
|
||||
}
|
||||
|
||||
$request = "{$method} {$path} HTTP/1.1\r\n";
|
||||
$request .= "Host: {$host}\r\n";
|
||||
$request .= "User-Agent: {$this->userAgent}\r\n";
|
||||
$request .= "{$accept}\r\n";
|
||||
$request .= "Connection: close\r\n";
|
||||
|
||||
if (!empty($authHeader)) {
|
||||
$request .= $authHeader;
|
||||
}
|
||||
|
||||
// ヘッダーを追加
|
||||
foreach ($this->headers as $name => $value) {
|
||||
$request .= "{$name}: {$value}\r\n";
|
||||
}
|
||||
|
||||
// リファラーが設定されていれば追加
|
||||
if (!empty($this->referer) && !isset($this->headers['Referer'])) {
|
||||
$request .= "Referer: {$this->referer}\r\n";
|
||||
}
|
||||
|
||||
// クッキーヘッダーを追加
|
||||
if (!empty($this->cookies) && !isset($this->headers['Cookie'])) {
|
||||
$cookieStrings = [];
|
||||
foreach ($this->cookies as $name => $value) {
|
||||
$cookieStrings[] = $name.'='.urlencode($value);
|
||||
}
|
||||
$request .= 'Cookie: '.implode('; ', $cookieStrings)."\r\n";
|
||||
}
|
||||
|
||||
$request .= "\r\n";
|
||||
|
||||
// POSTデータを追加
|
||||
if ($method === 'POST' || $method === 'PUT') {
|
||||
$request .= $httpData;
|
||||
}
|
||||
|
||||
if ($this->verbose && $this->stderr) {
|
||||
fwrite($this->stderr, "* リクエストヘッダー:\n{$request}\n");
|
||||
}
|
||||
|
||||
// ソケット接続を確立
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
|
||||
if ($scheme === 'https') {
|
||||
$sslOptions = [
|
||||
'verify_peer' => $this->verifySSL,
|
||||
'verify_peer_name' => $this->verifySSL
|
||||
];
|
||||
|
||||
if (!empty($this->caInfoPath) && file_exists($this->caInfoPath)) {
|
||||
$sslOptions['cafile'] = $this->caInfoPath;
|
||||
}
|
||||
|
||||
$context = stream_context_create(['ssl' => $sslOptions]);
|
||||
|
||||
$socket = @stream_socket_client(
|
||||
"tls://{$host}:{$port}",
|
||||
$errno,
|
||||
$errstr,
|
||||
$this->timeout,
|
||||
STREAM_CLIENT_CONNECT,
|
||||
$context
|
||||
);
|
||||
} else {
|
||||
$socket = @fsockopen($host, $port, $errno, $errstr, $this->timeout);
|
||||
}
|
||||
|
||||
if (!$socket) {
|
||||
$this->responseError = "接続出来ません: {$errstr} ({$errno})";
|
||||
if ($this->verbose && $this->stderr) {
|
||||
fwrite($this->stderr, "* エラー: {$this->responseError}\n");
|
||||
}
|
||||
return \Result::Error($this->responseError);
|
||||
}
|
||||
|
||||
// タイムアウトを設定
|
||||
stream_set_timeout($socket, $this->timeout);
|
||||
|
||||
// リクエストを送信
|
||||
fwrite($socket, $request);
|
||||
|
||||
// レスポンスを読み込む
|
||||
$rawResponse = '';
|
||||
$headers = '';
|
||||
$body = '';
|
||||
$headersComplete = false;
|
||||
|
||||
// ヘッダーとボディを分けて読み込む
|
||||
while (!feof($socket)) {
|
||||
$line = fgets($socket);
|
||||
if ($line === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
$rawResponse .= $line;
|
||||
|
||||
if (!$headersComplete) {
|
||||
if (trim($line) === '') {
|
||||
$headersComplete = true;
|
||||
} else {
|
||||
$headers .= $line;
|
||||
}
|
||||
} else {
|
||||
$body .= $line;
|
||||
}
|
||||
}
|
||||
|
||||
fclose($socket);
|
||||
|
||||
// レスポンスヘッダーを解析
|
||||
$headerLines = explode("\r\n", $headers);
|
||||
|
||||
// ステータスコードを取得
|
||||
$statusLine = isset($headerLines[0]) ? $headerLines[0] : '';
|
||||
$statusParts = explode(' ', $statusLine, 3);
|
||||
$this->responseCode = isset($statusParts[1]) ? (int)$statusParts[1] : 0;
|
||||
$this->info['http_code'] = $this->responseCode;
|
||||
|
||||
// ヘッダーを解析
|
||||
$this->responseHeaders = [];
|
||||
$redirectUrl = '';
|
||||
|
||||
foreach ($headerLines as $index => $header) {
|
||||
if ($index === 0) continue;
|
||||
|
||||
if (strpos($header, ':') !== false) {
|
||||
list($name, $value) = explode(':', $header, 2);
|
||||
$name = trim($name);
|
||||
$value = trim($value);
|
||||
$this->responseHeaders[$name] = $value;
|
||||
|
||||
if (strtolower($name) === 'content-type') {
|
||||
$this->info['content_type'] = $value;
|
||||
}
|
||||
|
||||
// リダイレクトをチェック
|
||||
if ($this->followRedirects &&
|
||||
strtolower($name) === 'location' &&
|
||||
$this->responseCode >= 300 &&
|
||||
$this->responseCode < 400) {
|
||||
|
||||
$redirectUrl = $value;
|
||||
|
||||
// 相対URLを絶対URLに変換
|
||||
if (strpos($redirectUrl, 'http') !== 0) {
|
||||
if ($redirectUrl[0] === '/') {
|
||||
$redirectUrl = "{$scheme}://{$host}"
|
||||
.($port != 80 && $port != 443 ? ":{$port}" : '').$redirectUrl;
|
||||
} else {
|
||||
$redirectUrl = "{$scheme}://{$host}"
|
||||
.($port != 80 && $port != 443 ? ":{$port}" : '')
|
||||
.dirname($path).'/'.$redirectUrl;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info['redirect_url'] = $redirectUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->info['header_size'] += strlen($headers);
|
||||
$this->responseBody .= $body;
|
||||
|
||||
if ($this->verbose && $this->stderr) {
|
||||
fwrite($this->stderr, "* レスポンスコード: {$this->responseCode}\n");
|
||||
fwrite($this->stderr, "* レスポンスヘッダー:\n{$headers}\n");
|
||||
if (!empty($redirectUrl)) {
|
||||
fwrite($this->stderr, "* リダイレクト先: {$redirectUrl}\n");
|
||||
}
|
||||
}
|
||||
|
||||
// リダイレクトが必要な場合
|
||||
if (!empty($redirectUrl) && $redirectCount < $this->maxRedirects) {
|
||||
$currentUrl = $redirectUrl;
|
||||
$redirectCount++;
|
||||
$this->info['redirect_count'] = $redirectCount;
|
||||
|
||||
// 302や303リダイレクトはGETにメソッドを変更
|
||||
if ($this->responseCode == 302 || $this->responseCode == 303) {
|
||||
$this->method = 'GET';
|
||||
$this->postRaw = '';
|
||||
$this->postFields = [];
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
// リクエスト完了後、元のメソッドに戻す
|
||||
$this->method = $originalMethod;
|
||||
$this->info['total_time'] = microtime(true) - $startTime;
|
||||
|
||||
return \Result::Success();
|
||||
}
|
||||
|
||||
/**
|
||||
* レスポンスボディを取得する
|
||||
*
|
||||
* @return string レスポンスボディ
|
||||
*/
|
||||
public function getResponseBody(): string {
|
||||
return $this->responseBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* レスポンスヘッダーを取得する
|
||||
*
|
||||
* @return array レスポンスヘッダーの配列
|
||||
*/
|
||||
public function getResponseHeaders(): array {
|
||||
return $this->responseHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTPレスポンスコードを取得する
|
||||
*
|
||||
* @return int HTTPレスポンスコード
|
||||
*/
|
||||
public function getResponseCode(): int {
|
||||
return $this->responseCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* エラーメッセージがあれば取得する
|
||||
*
|
||||
* @return string エラーメッセージ
|
||||
*/
|
||||
public function getError(): string {
|
||||
return $this->responseError;
|
||||
}
|
||||
|
||||
/**
|
||||
* リクエスト/レスポンス情報を取得する
|
||||
*
|
||||
* @return array 情報の配列
|
||||
*/
|
||||
public function getInfo(): array {
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
// 機能性メソッド
|
||||
|
||||
/**
|
||||
* リダイレクトURLを確認する
|
||||
*
|
||||
* @param string $name ヘッダー名
|
||||
* @param string $value ヘッダー値
|
||||
* @param string $currentUrl 現在のURL
|
||||
* @return string リダイレクトURL、リダイレクトがない場合は空文字
|
||||
*/
|
||||
private function checkReds(string $name, string $value, string $currentUrl): string {
|
||||
$redirectUrl = '';
|
||||
|
||||
if ($this->followRedirects && (strtolower($name) === 'location'
|
||||
&& $this->responseCode >= 300 && $this->responseCode < 400)) {
|
||||
$redirectUrl = $value;
|
||||
|
||||
if (strpos($redirectUrl, 'http') !== 0) {
|
||||
if ($redirectUrl[0] === '/') {
|
||||
$parsed = parse_url($currentUrl);
|
||||
$redirectUrl = $parsed['scheme'].'://'.$parsed['host']
|
||||
.(isset($parsed['port']) ? ':'.$parsed['port'] : '')
|
||||
.$redirectUrl;
|
||||
} else {
|
||||
$redirectUrl = dirname($currentUrl).'/'.$redirectUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $redirectUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* ヘッダー文字列を構築する
|
||||
*
|
||||
* @return string 構築されたヘッダー文字列
|
||||
*/
|
||||
private function buildHeaderString(): string {
|
||||
$headers = [];
|
||||
|
||||
// ユーザー指定のヘッダーを追加
|
||||
foreach ($this->headers as $name => $value) {
|
||||
$headers[] = "{$name}: {$value}";
|
||||
}
|
||||
|
||||
// リファラーが設定されていれば追加
|
||||
if (!empty($this->referer) && !isset($this->headers['Referer'])) {
|
||||
$headers[] = "Referer: {$this->referer}";
|
||||
}
|
||||
|
||||
// 必要に応じてクッキーヘッダーを追加
|
||||
if (!empty($this->cookies) && !isset($this->headers['Cookie'])) {
|
||||
$cookieStrings = [];
|
||||
foreach ($this->cookies as $name => $value) {
|
||||
$cookieStrings[] = $name.'='.urlencode($value);
|
||||
}
|
||||
|
||||
$headers[] = 'Cookie: '.implode('; ', $cookieStrings);
|
||||
}
|
||||
|
||||
return implode("\r\n", $headers)."\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* レスポンスを解析してヘッダーとボディに分割する
|
||||
*
|
||||
* @param string $response 完全なHTTPレスポンス
|
||||
* @return array [ヘッダー配列, ボディ文字列]
|
||||
*/
|
||||
private function parseResponse(string $response): array {
|
||||
$parts = explode("\r\n\r\n", $response, 2);
|
||||
|
||||
if (count($parts) < 2) {
|
||||
return [[], ''];
|
||||
}
|
||||
|
||||
$headers = explode("\r\n", $parts[0]);
|
||||
$body = $parts[1];
|
||||
|
||||
// チャンク転送エンコーディングを処理
|
||||
if (isset($this->responseHeaders['Transfer-Encoding']) &&
|
||||
strtolower($this->responseHeaders['Transfer-Encoding']) === 'chunked') {
|
||||
$body = $this->decodeChunkedBody($body);
|
||||
}
|
||||
|
||||
return [$headers, $body];
|
||||
}
|
||||
|
||||
/**
|
||||
* チャンク転送エンコーディングされたボディをデコードする
|
||||
*
|
||||
* @param string $body チャンクエンコードされたボディ
|
||||
* @return string デコードされたボディ
|
||||
*/
|
||||
private function decodeChunkedBody(string $body): string {
|
||||
$decodedBody = '';
|
||||
$position = 0;
|
||||
|
||||
while ($position < strlen($body)) {
|
||||
$lineEnd = strpos($body, "\r\n", $position);
|
||||
if ($lineEnd === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
$chunkSize = hexdec(substr($body, $position, $lineEnd - $position));
|
||||
|
||||
if ($chunkSize === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
$position = $lineEnd + 2;
|
||||
$decodedBody .= substr($body, $position, $chunkSize);
|
||||
$position += $chunkSize + 2; // チャンクサイズ + CRLF
|
||||
}
|
||||
|
||||
return $decodedBody;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user