Search code examples
javascriptphphttpencodingbase64

Base64 Not encoding/decoding correctly


I have a website called Pastebin, that is a minimalistic version of the original Pastebin, but I am having trouble pasting code. My server won't accept it.

I tried encoding it in base64 which worked, and stored the data as base 64, and when I went to decode it through a tool it decoded perfectly, but when I view it at the generated pastebin link it displays like �������Y� ��T��T��ԑTUQT and I can't figure it out. I has a mostly working version that encoded and decoded perfectly but it wasn't just displaying the code, but displaying it with the html in it. So if I had <h1>Test</h1> in my code, it would display it as a header and not the plain code. I tried to find this again but it got lose in my revisions. Below I have my current (not working) code that has base64 encoding/decoding/displaying errors, I cant figure out which.

JS

    window.onload = function() {
        document.querySelector('#pasteForm').addEventListener('submit', function(e) {
            e.preventDefault();
            var textArea = document.querySelector('#textToPaste');
            var formData = new FormData(e.target);

            var text = formData.get('text').replace(/\n/g, '<br>').replace(/^ /gm, '&nbsp;').replace(/^\t/gm, '&emsp;');
            var encodedText = btoa(text); // Encode the text in base64
            formData.set('text', '<pre>' + encodedText + '</pre>'); // Use the encoded text

            var xhr = new XMLHttpRequest();
            xhr.open('POST', 'create.php', true);

            xhr.onload = function() {
                if (xhr.status === 200) {
                    var password = formData.get('password');
                    if (password) {
                        document.querySelector('#linkBox').value = xhr.responseURL + '?password=' + encodeURIComponent(password);
                    } else {
                        document.querySelector('#linkBox').value = xhr.responseURL;
                    }
                    document.querySelector('#overlay').style.display = 'block';
                    document.querySelector('#popup').style.display = 'block';
                } else {
                    console.error('An error occurred');
                }
            };

            xhr.send(formData);
        });

        document.querySelector('#closeButton').addEventListener('click', function() {
            document.querySelector('#overlay').style.display = 'none';
            document.querySelector('#popup').style.display = 'none';
        });

        document.querySelector('#copyButton').addEventListener('click', function() {
            var linkBox = document.querySelector('#linkBox');
            linkBox.select();
            document.execCommand('copy');

            var copyButtonIcon = document.querySelector('#copyButton img');
            var originalIconSrc = copyButtonIcon.src;
            copyButtonIcon.src = 'https://cdn.cirrus.center/main/icons/check.png';

            setTimeout(function() {
                copyButtonIcon.src = originalIconSrc;
            }, 2000);
        });
    };

PHP

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!file_exists('paste')) {
        mkdir('paste');

        $redirectIndex = '<?php' . PHP_EOL;
        $redirectIndex .= 'header("Location: ..");' . PHP_EOL;
        $redirectIndex .= 'exit;' . PHP_EOL;
        file_put_contents('paste/index.php', $redirectIndex);
    }

    $dir = uniqid();
    mkdir("paste/$dir");

    $decodedText = '';
    if (isset($_POST['text']) && $_POST['text'] !== '') {
        $decodedText = base64_decode($_POST['text']); // Decode the base64 text
        $decodedText = mb_convert_encoding($decodedText, 'UTF-8', 'auto'); // Convert the encoding to UTF-8
    }

    $index = '<?php' . PHP_EOL;

    if (isset($_POST['password']) && $_POST['password'] !== '') {
        $index .= 'if (!isset($_GET["password"]) || $_GET["password"] !== "' . $_POST['password'] . '") {' . PHP_EOL;
        $index .= '    echo "<h1>Incorrect password</h1>";' . PHP_EOL;
        $index .= '    exit;' . PHP_EOL;
        $index .= '}' . PHP_EOL;
    }

    $index .= 'echo <<<EOT' . PHP_EOL; // Use the decoded text
    $index .= $decodedText . PHP_EOL;
    $index .= 'EOT;' . PHP_EOL;

    if (isset($_POST['burn'])) {
        $index .= 'file_put_contents(__FILE__, "<h1>This paste has been deleted</h1>");' . PHP_EOL;
        $index .= 'exit;' . PHP_EOL;
    }

    $index .= '?>' . PHP_EOL;

    $index .= '<!DOCTYPE html>' . PHP_EOL;
    $index .= '<html>' . PHP_EOL;
    $index .= '<head>' . PHP_EOL;
    $index .= '    <title>' . ($_POST['title'] ?? 'Pastebin') . '</title>' . PHP_EOL;
    $index .= '</head>' . PHP_EOL;
    $index .= '<body>' . PHP_EOL;
    $index .= '' . PHP_EOL;
    $index .= '</body>' . PHP_EOL;
    $index .= '</html>';

    file_put_contents("paste/$dir/index.php", $index);

    header("Location: /paste/$dir");
    echo "/paste/$dir";
}

Solution

  • Here is what I am seeing, try it and let me know. I can revise this answer as needed.

    In the javascript you seem to be passing a form field like so:

    formData.set('text', '<pre>' + encodedText + '</pre>'); // Use the encoded text
    

    Then it makes a form post to the server side PHP code and the form field 'text' is decoded like so:

    $decodedText = base64_decode($_POST['text']); // Decode the base64 text
    $decodedText = mb_convert_encoding($decodedText, 'UTF-8', 'auto'); // Convert the encoding to UTF-8
    

    So a few potential issues here:

    1-I think you need to remove the 'pre' tags when encoding, so:

    formData.set('text', encodedText); // Use the encoded text
    

    You can put the 'pre' tags in the PHP so the text renders properly:

    $index .= '<pre>' . $decodedText . '</pre>' . PHP_EOL;
    

    2-On javascript side you use btoa, but on PHP side you use base64_decode. These two commands could perhaps be incompatible, so you can use a tool to verify that PHP base64_decode is decoding the javascript btoa properly. You do mention using a tool to verify, but there can be cases where it sometimes decodes properly and sometimes does not, due to subtle incompatibility issues, so it's good to keep this in mind.

    3-Not sure there is any need to use mb_convert_encoding. Try removing it.

    Let me know what works for you.

    *** Update: See comments. The Pre tags in the javascript were causing the garbled text, they simply needed to be removed. After that, the server was generating an error 418 because it was flagging the PHP line that's doing the decoding, detecting a security vulnerability. They are following up with their server provider to resolve that part.