[PHP]stripslashes関数を完全解説!バックスラッシュのエスケープを解除

PHP

こんにちは!今回は、PHPの標準関数であるstripslashes()について詳しく解説していきます。addslashes()でエスケープされた文字列を元に戻すための関数です!

stripslashes関数とは?

stripslashes()関数は、addslashes()によって追加されたバックスラッシュを削除する関数です。

シングルクォート(')、ダブルクォート(")、バックスラッシュ(\)、NULL文字(\0)の前に付けられたバックスラッシュを取り除きます!

基本的な構文

stripslashes(string $string): string
  • $string: バックスラッシュを削除する文字列
  • 戻り値: バックスラッシュが削除された文字列

基本的な使用例

シンプルな使用例

// addslashes()でエスケープ
$original = "It's a beautiful day";
$escaped = addslashes($original);
echo "エスケープ後: " . $escaped . "\n";
// 出力: It\'s a beautiful day

// stripslashes()で元に戻す
$restored = stripslashes($escaped);
echo "復元後: " . $restored . "\n";
// 出力: It's a beautiful day

// 完全に一致するか確認
var_dump($original === $restored);  // bool(true)

各種クォートの処理

// シングルクォート
$text = "She said, 'Hello'";
$escaped = addslashes($text);
echo $escaped . "\n";  // She said, \'Hello\'
echo stripslashes($escaped) . "\n";  // She said, 'Hello'

// ダブルクォート
$text = 'He said, "Hi"';
$escaped = addslashes($text);
echo $escaped . "\n";  // He said, \"Hi\"
echo stripslashes($escaped) . "\n";  // He said, "Hi"

// バックスラッシュ
$text = "Path: C:\\Users\\Documents";
$escaped = addslashes($text);
echo $escaped . "\n";  // Path: C:\\\\Users\\\\Documents
echo stripslashes($escaped) . "\n";  // Path: C:\\Users\\Documents

NULL文字の処理

// NULL文字
$text = "Hello\0World";
$escaped = addslashes($text);

echo "元の長さ: " . strlen($text) . "\n";  // 11
echo "エスケープ後の長さ: " . strlen($escaped) . "\n";  // 12
echo "復元後の長さ: " . strlen(stripslashes($escaped)) . "\n";  // 11

実践的な使用例

例1: フォーム入力の処理

class FormDataHandler {
    /**
     * フォームデータをエスケープ
     */
    public static function escapeFormData($data) {
        if (is_array($data)) {
            return array_map([self::class, 'escapeFormData'], $data);
        }
        
        return is_string($data) ? addslashes($data) : $data;
    }
    
    /**
     * フォームデータを復元
     */
    public static function unescapeFormData($data) {
        if (is_array($data)) {
            return array_map([self::class, 'unescapeFormData'], $data);
        }
        
        return is_string($data) ? stripslashes($data) : $data;
    }
    
    /**
     * magic_quotes_gpc対策
     */
    public static function removeMagicQuotes($data) {
        // PHP 5.4より前のバージョンでmagic_quotes_gpcが有効な場合の対策
        if (is_array($data)) {
            return array_map([self::class, 'removeMagicQuotes'], $data);
        }
        
        return stripslashes($data);
    }
    
    /**
     * 安全にデータを取得
     */
    public static function getSafeInput($key, $default = '') {
        if (!isset($_POST[$key])) {
            return $default;
        }
        
        $value = $_POST[$key];
        
        // magic_quotes_gpc対策(古いPHPバージョン用)
        if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
            $value = self::removeMagicQuotes($value);
        }
        
        return $value;
    }
}

// 使用例
$formData = [
    'name' => "O'Brien",
    'comment' => 'He said, "Hello"',
    'path' => 'C:\\Users\\Documents'
];

echo "=== 元のデータ ===\n";
print_r($formData);

// エスケープ
$escaped = FormDataHandler::escapeFormData($formData);
echo "\n=== エスケープ後 ===\n";
print_r($escaped);

// 復元
$restored = FormDataHandler::unescapeFormData($escaped);
echo "\n=== 復元後 ===\n";
print_r($restored);

// 一致確認
var_dump($formData === $restored);  // bool(true)

例2: データベース操作

class DatabaseHelper {
    /**
     * クエリ用にエスケープ(注意: 実際にはPDOやmysqli_real_escape_stringを使用すべき)
     */
    public static function escapeForQuery($value) {
        return addslashes($value);
    }
    
    /**
     * データベースから取得したデータを復元
     */
    public static function unescapeFromDatabase($value) {
        return stripslashes($value);
    }
    
    /**
     * レコード全体を復元
     */
    public static function unescapeRecord($record) {
        $unescaped = [];
        
        foreach ($record as $key => $value) {
            if (is_string($value)) {
                $unescaped[$key] = stripslashes($value);
            } else {
                $unescaped[$key] = $value;
            }
        }
        
        return $unescaped;
    }
    
    /**
     * 複数レコードを復元
     */
    public static function unescapeRecords($records) {
        return array_map([self::class, 'unescapeRecord'], $records);
    }
}

// 使用例
$userData = [
    'name' => "O'Connor",
    'bio' => 'Developer who loves "coding"',
    'company' => 'Tech\\Solutions'
];

echo "=== 元のデータ ===\n";
print_r($userData);

// データベース保存用にエスケープ
$forDb = [];
foreach ($userData as $key => $value) {
    $forDb[$key] = DatabaseHelper::escapeForQuery($value);
}

echo "\n=== DB保存用 ===\n";
print_r($forDb);

// データベースから取得したと仮定して復元
$restored = DatabaseHelper::unescapeRecord($forDb);

echo "\n=== 復元後 ===\n";
print_r($restored);

var_dump($userData === $restored);  // bool(true)

例3: JSONデータの処理

class JsonHandler {
    /**
     * JSONエンコード前のエスケープ処理
     */
    public static function prepareForJson($data) {
        // JSONはバックスラッシュを自動的にエスケープするため、
        // 二重エスケープを防ぐ
        if (is_array($data)) {
            return array_map([self::class, 'prepareForJson'], $data);
        }
        
        if (is_string($data)) {
            // 既にエスケープされている場合は元に戻す
            return stripslashes($data);
        }
        
        return $data;
    }
    
    /**
     * JSONデコード後の処理
     */
    public static function processFromJson($data) {
        if (is_array($data)) {
            return array_map([self::class, 'processFromJson'], $data);
        }
        
        return $data;
    }
    
    /**
     * 安全にJSONエンコード
     */
    public static function safeEncode($data) {
        $prepared = self::prepareForJson($data);
        return json_encode($prepared, JSON_UNESCAPED_UNICODE);
    }
    
    /**
     * 安全にJSONデコード
     */
    public static function safeDecode($json) {
        $data = json_decode($json, true);
        
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception('JSON decode error: ' . json_last_error_msg());
        }
        
        return self::processFromJson($data);
    }
}

// 使用例
$data = [
    'title' => "Book: \"PHP Guide\"",
    'author' => "O'Brien",
    'path' => 'C:\\Books\\PHP'
];

// 誤ってエスケープされたデータ
$wrongEscaped = [
    'title' => "Book: \\\"PHP Guide\\\"",
    'author' => "O\\'Brien",
    'path' => 'C:\\\\Books\\\\PHP'
];

echo "=== 正しいデータ ===\n";
$json1 = JsonHandler::safeEncode($data);
echo $json1 . "\n";

echo "\n=== 二重エスケープを修正 ===\n";
$json2 = JsonHandler::safeEncode($wrongEscaped);
echo $json2 . "\n";

// デコード
$decoded = JsonHandler::safeDecode($json2);
print_r($decoded);

例4: ファイルパスの処理

class PathHandler {
    /**
     * Windowsパスのバックスラッシュを処理
     */
    public static function normalizePath($path) {
        // 二重バックスラッシュを単一に
        return stripslashes($path);
    }
    
    /**
     * パス配列を正規化
     */
    public static function normalizePaths($paths) {
        return array_map([self::class, 'normalizePath'], $paths);
    }
    
    /**
     * パスの結合
     */
    public static function joinPath(...$parts) {
        $normalized = array_map([self::class, 'normalizePath'], $parts);
        return implode(DIRECTORY_SEPARATOR, $normalized);
    }
    
    /**
     * エスケープされたパスを表示用に整形
     */
    public static function formatForDisplay($path) {
        $normalized = stripslashes($path);
        
        // バックスラッシュをスラッシュに変換(表示用)
        return str_replace('\\', '/', $normalized);
    }
}

// 使用例
$paths = [
    'C:\\\\Users\\\\Documents',
    'D:\\\\Projects\\\\PHP',
    '/var/www/html'
];

echo "=== 元のパス ===\n";
print_r($paths);

$normalized = PathHandler::normalizePaths($paths);
echo "\n=== 正規化後 ===\n";
print_r($normalized);

// パス結合
$fullPath = PathHandler::joinPath('C:\\\\Users', 'Documents', 'file.txt');
echo "\n結合パス: " . $fullPath . "\n";

// 表示用フォーマット
echo "表示用: " . PathHandler::formatForDisplay('C:\\\\Users\\\\Documents') . "\n";

例5: CSVデータの処理

class CsvProcessor {
    /**
     * CSVフィールドからエスケープを除去
     */
    public static function unescapeCsvField($field) {
        // CSVで二重引用符がエスケープされている場合
        $field = str_replace('""', '"', $field);
        
        // その他のバックスラッシュエスケープを除去
        return stripslashes($field);
    }
    
    /**
     * CSV行全体を処理
     */
    public static function processCsvLine($line) {
        $fields = str_getcsv($line);
        return array_map([self::class, 'unescapeCsvField'], $fields);
    }
    
    /**
     * CSVファイルを読み込んで処理
     */
    public static function readCsv($filename) {
        if (!file_exists($filename)) {
            throw new Exception("ファイルが見つかりません: {$filename}");
        }
        
        $lines = file($filename, FILE_IGNORE_NEW_LINES);
        $data = [];
        
        foreach ($lines as $line) {
            $data[] = self::processCsvLine($line);
        }
        
        return $data;
    }
    
    /**
     * CSVデータをクリーンアップ
     */
    public static function cleanCsvData($data) {
        $cleaned = [];
        
        foreach ($data as $row) {
            $cleanedRow = [];
            
            foreach ($row as $field) {
                $cleanedRow[] = self::unescapeCsvField($field);
            }
            
            $cleaned[] = $cleanedRow;
        }
        
        return $cleaned;
    }
}

// 使用例
$csvData = [
    ["O\\'Brien", "Developer", "He said \\\"Hello\\\""],
    ["Jane", "Designer", "Path: C:\\\\Work"],
];

echo "=== 元のデータ ===\n";
print_r($csvData);

$cleaned = CsvProcessor::cleanCsvData($csvData);
echo "\n=== クリーンアップ後 ===\n";
print_r($cleaned);

例6: ログファイルの処理

class LogCleaner {
    /**
     * ログメッセージからエスケープを除去
     */
    public static function cleanLogMessage($message) {
        return stripslashes($message);
    }
    
    /**
     * ログファイルを読み込んでクリーンアップ
     */
    public static function cleanLogFile($inputFile, $outputFile) {
        if (!file_exists($inputFile)) {
            throw new Exception("入力ファイルが見つかりません: {$inputFile}");
        }
        
        $lines = file($inputFile);
        $cleaned = [];
        
        foreach ($lines as $line) {
            $cleaned[] = stripslashes($line);
        }
        
        return file_put_contents($outputFile, implode('', $cleaned));
    }
    
    /**
     * ログエントリをパースして復元
     */
    public static function parseLogEntry($entry) {
        // [timestamp] [level] message 形式を想定
        if (preg_match('/^\[(.*?)\] \[(.*?)\] (.+)$/', $entry, $matches)) {
            return [
                'timestamp' => $matches[1],
                'level' => $matches[2],
                'message' => stripslashes($matches[3])
            ];
        }
        
        return null;
    }
}

// 使用例
$logEntries = [
    "[2024-02-23 10:30:00] [ERROR] File not found: C:\\\\Users\\\\file.txt",
    "[2024-02-23 10:31:00] [INFO] User \\'admin\\' logged in",
    "[2024-02-23 10:32:00] [WARN] Query: \\"SELECT * FROM users\\"",
];

echo "=== 元のログ ===\n";
foreach ($logEntries as $entry) {
    echo $entry . "\n";
}

echo "\n=== クリーンアップ後 ===\n";
foreach ($logEntries as $entry) {
    $parsed = LogCleaner::parseLogEntry($entry);
    if ($parsed) {
        echo "[{$parsed['timestamp']}] [{$parsed['level']}] {$parsed['message']}\n";
    }
}

例7: コンフィグファイルの処理

class ConfigParser {
    /**
     * 設定値からエスケープを除去
     */
    public static function unescapeValue($value) {
        return stripslashes($value);
    }
    
    /**
     * 設定ファイルを読み込む
     */
    public static function parseConfigFile($filename) {
        if (!file_exists($filename)) {
            throw new Exception("設定ファイルが見つかりません: {$filename}");
        }
        
        $lines = file($filename, FILE_IGNORE_NEW_LINES);
        $config = [];
        
        foreach ($lines as $line) {
            $line = trim($line);
            
            // コメントと空行をスキップ
            if (empty($line) || $line[0] === '#') {
                continue;
            }
            
            // key=value 形式
            if (strpos($line, '=') !== false) {
                list($key, $value) = explode('=', $line, 2);
                $key = trim($key);
                $value = trim($value);
                
                // エスケープを除去
                $config[$key] = self::unescapeValue($value);
            }
        }
        
        return $config;
    }
    
    /**
     * 設定ファイルに書き込む
     */
    public static function writeConfigFile($filename, $config) {
        $lines = [];
        
        foreach ($config as $key => $value) {
            // エスケープ
            $escapedValue = addslashes($value);
            $lines[] = "{$key}={$escapedValue}";
        }
        
        return file_put_contents($filename, implode("\n", $lines));
    }
    
    /**
     * 設定を更新
     */
    public static function updateConfig($filename, $key, $value) {
        $config = self::parseConfigFile($filename);
        $config[$key] = $value;
        return self::writeConfigFile($filename, $config);
    }
}

// 使用例(ファイルがあると仮定)
/*
# config.ini の内容
app_name=My\\'s App
database_path=C:\\\\Program Files\\\\MySQL
welcome_message=Welcome! \\"Enjoy\\"

$config = ConfigParser::parseConfigFile('config.ini');
print_r($config);

Output:
Array (
    [app_name] => My's App
    [database_path] => C:\Program Files\MySQL
    [welcome_message] => Welcome! "Enjoy"
)
*/

stripcslashes()との違い

// stripslashes(): バックスラッシュのみ除去
$str1 = "Hello\\nWorld";
echo stripslashes($str1) . "\n";
// 出力: Hello\nWorld(\nは解釈されない)

// stripcslashes(): エスケープシーケンスを解釈
$str2 = "Hello\\nWorld";
echo stripcslashes($str2) . "\n";
// 出力:
// Hello
// World(\nが改行に変換される)

// バックスラッシュの除去
$path = "C:\\\\Users\\\\Documents";
echo "stripslashes: " . stripslashes($path) . "\n";
// C:\Users\Documents

echo "stripcslashes: " . stripcslashes($path) . "\n";
// C:\Users\Documents

// 16進数エスケープ
$hex = "\\x48\\x65\\x6C\\x6C\\x6F";
echo "stripslashes: " . stripslashes($hex) . "\n";
// \x48\x65\x6C\x6C\x6F(変化なし)

echo "stripcslashes: " . stripcslashes($hex) . "\n";
// Hello(16進数が解釈される)

注意点と制限事項

magic_quotes_gpcの影響

// PHP 5.4より前のバージョンでの対策
function removeSlashesIfNeeded($data) {
    // magic_quotes_gpcが有効な場合のみ処理
    if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
        if (is_array($data)) {
            return array_map('removeSlashesIfNeeded', $data);
        }
        return stripslashes($data);
    }
    
    return $data;
}

// 使用例(古いPHPバージョン)
// $_POST = removeSlashesIfNeeded($_POST);

二重エスケープの問題

// 二重エスケープを避ける
$text = "O'Brien";

// 正しい方法
$escaped = addslashes($text);      // O\'Brien
$restored = stripslashes($escaped); // O'Brien

// 間違った方法(二重エスケープ)
$doubleEscaped = addslashes($escaped);  // O\\\'Brien
$wrongRestore = stripslashes($doubleEscaped);  // O\'Brien(元に戻らない)

echo "正しい復元: " . $restored . "\n";
echo "間違った復元: " . $wrongRestore . "\n";

パフォーマンス

// 大量のデータ処理
$data = array_fill(0, 10000, "It\\'s a test");

$start = microtime(true);
$result = array_map('stripslashes', $data);
$time = microtime(true) - $start;

echo "処理時間: {$time}秒\n";
echo "処理件数: " . count($result) . "\n";

現代的な代替手段

// PDOを使用(推奨)
class ModernDatabaseHandler {
    private $pdo;
    
    public function __construct($dsn, $username, $password) {
        $this->pdo = new PDO($dsn, $username, $password);
        $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    }
    
    public function insert($name, $bio) {
        // プリペアドステートメントを使用(エスケープ不要)
        $stmt = $this->pdo->prepare(
            "INSERT INTO users (name, bio) VALUES (:name, :bio)"
        );
        
        return $stmt->execute([
            ':name' => $name,
            ':bio' => $bio
        ]);
    }
}

// htmlspecialchars()を使用(HTML出力用)
function safeHtmlOutput($text) {
    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}

// 使用例
$userInput = "O'Brien said \"Hello\"";
echo safeHtmlOutput($userInput);
// O'Brien said "Hello"

まとめ

stripslashes()関数の特徴をまとめると:

できること:

  • addslashes()で追加されたバックスラッシュの除去
  • クォートのエスケープ解除
  • パスのバックスラッシュ正規化

処理される文字:

  • \''(シングルクォート)
  • \""(ダブルクォート)
  • \\\(バックスラッシュ)
  • \0 → NULL文字

推奨される使用場面:

  • フォーム入力の処理
  • レガシーコードのメンテナンス
  • magic_quotes_gpc対策(古いPHP)
  • パスの正規化

注意点:

  • 現代のPHPではPDOやmysqli_real_escape_stringを使用すべき
  • magic_quotes_gpcは非推奨(PHP 5.4で削除)
  • 二重エスケープに注意
  • HTML出力にはhtmlspecialchars()を使用

関連関数:

  • addslashes(): バックスラッシュを追加
  • stripcslashes(): Cスタイルのエスケープを解除
  • htmlspecialchars(): HTML用エスケープ
  • mysqli_real_escape_string(): MySQL用エスケープ

現代的な代替手段:

  • PDOのプリペアドステートメント
  • filter_input()
  • htmlspecialchars()

stripslashes()は主にレガシーコードや特定の状況で使用されます。新しいコードでは、PDOなどのより安全な方法を使用することを強く推奨します!

タイトルとURLをコピーしました