Compare commits
8 Commits
5b69ce554c
...
fb22e14524
Author | SHA1 | Date | |
---|---|---|---|
fb22e14524 | |||
9d33e22ae0 | |||
b48d1024a8 | |||
24f4ce9669 | |||
099f6131d1 | |||
36bc217056 | |||
cb28e2a385 | |||
fb09eb97ce |
@ -25,3 +25,4 @@ luna_app(mkdir.cpp mkdir OFF)
|
||||
luna_app(rm.cpp rm OFF)
|
||||
luna_app(stat.cpp stat OFF)
|
||||
luna_app(uname.cpp uname OFF)
|
||||
luna_app(base64.cpp base64 OFF)
|
||||
|
41
apps/base64.cpp
Normal file
41
apps/base64.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include <luna/Base64.h>
|
||||
#include <os/ArgumentParser.h>
|
||||
#include <os/File.h>
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
{
|
||||
StringView path;
|
||||
bool decode { false };
|
||||
bool allow_garbage { false };
|
||||
|
||||
os::ArgumentParser parser;
|
||||
parser.add_description("Encode or decode Base64 data. If not given a file, reads from standard input.");
|
||||
parser.add_positional_argument(path, "file", "-"_sv);
|
||||
parser.add_switch_argument(decode, 'd', "decode", "decode data");
|
||||
parser.add_switch_argument(allow_garbage, ' ', "allow-garbage", "ignore non-base64 characters");
|
||||
parser.parse(argc, argv);
|
||||
|
||||
auto file = TRY(os::File::open_input_file(path));
|
||||
|
||||
auto output = os::File::standard_output();
|
||||
|
||||
if (!decode)
|
||||
{
|
||||
auto data = TRY(file->read_all());
|
||||
|
||||
auto encoded = TRY(Base64::encode(data));
|
||||
|
||||
output->write(encoded.view());
|
||||
output->write("\n"_sv);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto data = TRY(file->read_all_as_string());
|
||||
|
||||
auto decoded = TRY(Base64::decode(data.view(), allow_garbage));
|
||||
|
||||
output->write(decoded);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -6,14 +6,10 @@ using os::File;
|
||||
|
||||
static Result<void> do_cat(StringView path)
|
||||
{
|
||||
SharedPtr<File> f;
|
||||
SharedPtr<File> f = TRY(File::open_input_file(path));
|
||||
|
||||
auto out = File::standard_output();
|
||||
|
||||
if (path == "-") f = File::standard_input();
|
||||
else
|
||||
f = TRY(File::open(path, File::ReadOnly));
|
||||
|
||||
auto buf = TRY(Buffer::create_sized(4096));
|
||||
while (1)
|
||||
{
|
||||
|
@ -59,7 +59,7 @@ void ConsoleDevice::did_press_key(char key)
|
||||
// Ctrl+D
|
||||
if (key == 'd' && (Keyboard::modifiers() & Keyboard::LeftControl))
|
||||
{
|
||||
g_eof = true;
|
||||
if (g_temp_input.size() == 0) g_eof = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "boot/bootboot.h"
|
||||
#include "video/Framebuffer.h"
|
||||
#include <luna/CString.h>
|
||||
#include <luna/CType.h>
|
||||
#include <luna/Format.h>
|
||||
#include <luna/Result.h>
|
||||
#include <luna/ScopeGuard.h>
|
||||
@ -109,6 +110,7 @@ namespace TextConsole
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
if (_iscntrl(c)) return;
|
||||
putwchar_at(c, g_x_position, g_y_position);
|
||||
next_char();
|
||||
if (at_end_of_screen())
|
||||
|
@ -23,6 +23,7 @@ set(FREESTANDING_SOURCES
|
||||
src/Spinlock.cpp
|
||||
src/PathParser.cpp
|
||||
src/UBSAN.cpp
|
||||
src/Base64.cpp
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
|
13
libluna/include/luna/Base64.h
Normal file
13
libluna/include/luna/Base64.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include <luna/Buffer.h>
|
||||
#include <luna/String.h>
|
||||
|
||||
namespace Base64
|
||||
{
|
||||
Result<String> encode(StringView data);
|
||||
Result<String> encode(Slice<const u8> data);
|
||||
Result<String> encode(const Buffer& data);
|
||||
|
||||
Result<Buffer> decode(StringView data, bool allow_garbage_chars = false);
|
||||
Result<String> decode_string(StringView data, bool allow_garbage_chars = false);
|
||||
}
|
@ -11,6 +11,10 @@ template <typename T> class Slice
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Tp> Slice(const Slice<Tp>& other) : m_data((T*)other.m_data), m_size(other.m_size)
|
||||
{
|
||||
}
|
||||
|
||||
const T* data() const
|
||||
{
|
||||
return m_data;
|
||||
@ -46,6 +50,18 @@ template <typename T> class Slice
|
||||
return m_data + m_size;
|
||||
}
|
||||
|
||||
const T& operator[](usize index) const
|
||||
{
|
||||
check(index < m_size);
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
T& operator[](usize index)
|
||||
{
|
||||
check(index < m_size);
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
private:
|
||||
T* const m_data;
|
||||
const usize m_size;
|
||||
|
144
libluna/src/Base64.cpp
Normal file
144
libluna/src/Base64.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
#include <luna/Base64.h>
|
||||
#include <luna/CType.h>
|
||||
#include <luna/DebugLog.h>
|
||||
#include <luna/Slice.h>
|
||||
#include <luna/StringBuilder.h>
|
||||
|
||||
static const char g_base64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static Option<u8> value_from_base64_character(char letter)
|
||||
{
|
||||
if (_isupper(letter)) return (u8)(letter - 'A');
|
||||
if (_islower(letter)) return (u8)(26 + (letter - 'a'));
|
||||
if (_isdigit(letter)) return (u8)(52 + (letter - '0'));
|
||||
if (letter == '+') return 62;
|
||||
if (letter == '/') return 63;
|
||||
if (letter == '=') return {};
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static Result<void> decode_base64_buffer(const Vector<char>& input, Buffer& output)
|
||||
{
|
||||
const usize bytes_to_decode = 4;
|
||||
|
||||
Option<u8> base64_data[bytes_to_decode];
|
||||
|
||||
for (usize i = 0; i < 4; i++) { base64_data[i] = value_from_base64_character(input[i]); }
|
||||
|
||||
u8 decoded_bytes[3];
|
||||
usize total_decoded_bytes = 0;
|
||||
|
||||
decoded_bytes[0] = (base64_data[0].value() << 2) | (base64_data[1].value() >> 4);
|
||||
total_decoded_bytes++;
|
||||
|
||||
if (base64_data[2].has_value())
|
||||
{
|
||||
decoded_bytes[1] = ((base64_data[1].value() & 0b1111) << 4) | (base64_data[2].value() >> 2);
|
||||
total_decoded_bytes++;
|
||||
}
|
||||
|
||||
if (base64_data[3].has_value())
|
||||
{
|
||||
decoded_bytes[2] = ((base64_data[2].value() & 0b11) << 6) | base64_data[3].value();
|
||||
total_decoded_bytes++;
|
||||
}
|
||||
|
||||
return output.append_data(decoded_bytes, total_decoded_bytes);
|
||||
}
|
||||
|
||||
namespace Base64
|
||||
{
|
||||
Result<String> encode(StringView data)
|
||||
{
|
||||
return encode(Slice<const u8> { (const u8*)data.chars(), data.length() });
|
||||
}
|
||||
|
||||
Result<String> encode(const Buffer& data)
|
||||
{
|
||||
return encode(Slice<const u8> { data.data(), data.size() });
|
||||
}
|
||||
|
||||
Result<String> encode(Slice<const u8> data)
|
||||
{
|
||||
StringBuilder sb;
|
||||
|
||||
for (usize i = 0; i < data.size(); i += 3)
|
||||
{
|
||||
usize bytes_to_encode = data.size() - i;
|
||||
if (bytes_to_encode > 3) bytes_to_encode = 3;
|
||||
|
||||
TRY(sb.add(g_base64_alphabet[data[i] >> 2]));
|
||||
if (bytes_to_encode > 1) TRY(sb.add(g_base64_alphabet[((data[i] & 0b11) << 4) | (data[i + 1] >> 4)]));
|
||||
else
|
||||
{
|
||||
TRY(sb.add(g_base64_alphabet[(data[i] & 0b11) << 6]));
|
||||
TRY(sb.add("=="_sv));
|
||||
break;
|
||||
}
|
||||
|
||||
if (bytes_to_encode > 2) TRY(sb.add(g_base64_alphabet[((data[i + 1] & 0b1111) << 2) | (data[i + 2] >> 6)]));
|
||||
else
|
||||
{
|
||||
TRY(sb.add(g_base64_alphabet[(data[i + 1] & 0b1111) << 2]));
|
||||
TRY(sb.add("="_sv));
|
||||
break;
|
||||
}
|
||||
|
||||
TRY(sb.add(g_base64_alphabet[data[i + 2] & 0b111111]));
|
||||
}
|
||||
|
||||
return sb.string();
|
||||
}
|
||||
|
||||
Result<Buffer> decode(StringView data, bool allow_garbage_chars)
|
||||
{
|
||||
Buffer buf;
|
||||
|
||||
char* padding = strchr(data.chars(), '=');
|
||||
if (padding)
|
||||
{
|
||||
// If the string ends with padding, it must be either one or two equals signs.
|
||||
if (padding[1] != '=' && padding[1] != '\0') return err(EINVAL);
|
||||
if (padding[1])
|
||||
{
|
||||
if (padding[strspn(&padding[2], "\n") + 2]) return err(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<char> chars_read;
|
||||
|
||||
for (const auto& c : data)
|
||||
{
|
||||
if (c == '\n') continue;
|
||||
|
||||
if (!_isalnum(c) && c != '+' && c != '/' && c != '=')
|
||||
{
|
||||
if (allow_garbage_chars) continue;
|
||||
return err(EINVAL);
|
||||
}
|
||||
|
||||
TRY(chars_read.try_append(c));
|
||||
|
||||
if (chars_read.size() == 4)
|
||||
{
|
||||
TRY(decode_base64_buffer(chars_read, buf));
|
||||
chars_read.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Unterminated input
|
||||
if (chars_read.size() > 0) return err(EINVAL);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
Result<String> decode_string(StringView data, bool allow_garbage_chars)
|
||||
{
|
||||
auto buf = TRY(decode(data, allow_garbage_chars));
|
||||
|
||||
u8 nul_byte = '\0';
|
||||
TRY(buf.append_data(&nul_byte, 1));
|
||||
|
||||
return String::from_cstring((char*)buf.data());
|
||||
}
|
||||
}
|
@ -24,6 +24,14 @@ namespace os
|
||||
static Result<SharedPtr<File>> open_or_create(StringView path, OpenMode flags, mode_t mode = 0644);
|
||||
static Result<SharedPtr<File>> create(StringView path, OpenMode flags, mode_t mode = 0644);
|
||||
|
||||
/*
|
||||
If path is "-", return standard input (as is common for many CLI apps). Otherwise, open path for reading.
|
||||
|
||||
This function is a convenience function for CLI apps, so that they don't need to check path and open standard
|
||||
input if necessary.
|
||||
*/
|
||||
static Result<SharedPtr<File>> open_input_file(StringView path);
|
||||
|
||||
static SharedPtr<File> standard_input();
|
||||
static SharedPtr<File> standard_output();
|
||||
static SharedPtr<File> standard_error();
|
||||
@ -34,8 +42,10 @@ namespace os
|
||||
Result<void> write(const Buffer& buf);
|
||||
|
||||
Result<String> read_line();
|
||||
Result<String> read_all_as_string();
|
||||
|
||||
Result<void> read(Buffer& buf, usize size);
|
||||
Result<Buffer> read_all();
|
||||
|
||||
Result<int> getchar();
|
||||
|
||||
|
@ -82,6 +82,13 @@ namespace os
|
||||
return construct(path, (int)flags | (O_CREAT | O_EXCL), mode);
|
||||
}
|
||||
|
||||
Result<SharedPtr<File>> File::open_input_file(StringView path)
|
||||
{
|
||||
if (path == "-"_sv) return standard_input();
|
||||
|
||||
return construct(path, O_RDONLY, 0);
|
||||
}
|
||||
|
||||
Result<usize> File::raw_read(u8* buf, usize length)
|
||||
{
|
||||
long rc = syscall(SYS_read, m_fd, buf, length);
|
||||
@ -131,6 +138,36 @@ namespace os
|
||||
return String::from_cstring(data.data());
|
||||
}
|
||||
|
||||
Result<String> File::read_all_as_string()
|
||||
{
|
||||
StringBuilder sb;
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto line = TRY(read_line());
|
||||
if (line.is_empty()) break;
|
||||
TRY(sb.add(line));
|
||||
}
|
||||
|
||||
return sb.string();
|
||||
}
|
||||
|
||||
Result<Buffer> File::read_all()
|
||||
{
|
||||
Vector<u8> data;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int c = TRY(getchar());
|
||||
if (c == -1) break;
|
||||
TRY(data.try_append((u8)c));
|
||||
}
|
||||
|
||||
Buffer buf;
|
||||
TRY(buf.append_data(data.data(), data.size()));
|
||||
return buf;
|
||||
}
|
||||
|
||||
Result<void> File::read(Buffer& buf, usize size)
|
||||
{
|
||||
u8* slice = TRY(buf.slice(0, size));
|
||||
@ -144,9 +181,9 @@ namespace os
|
||||
|
||||
Result<int> File::getchar()
|
||||
{
|
||||
char value;
|
||||
u8 value;
|
||||
|
||||
usize nread = TRY(raw_read((u8*)&value, 1));
|
||||
usize nread = TRY(raw_read(&value, 1));
|
||||
if (!nread) return -1;
|
||||
|
||||
return value;
|
||||
|
@ -17,3 +17,4 @@ function(luna_test SOURCE_FILE APP_NAME SETUID)
|
||||
endfunction()
|
||||
|
||||
luna_test(libluna/TestVector.cpp TestVector OFF)
|
||||
luna_test(libluna/TestBase64.cpp TestBase64 OFF)
|
||||
|
149
tests/libluna/TestBase64.cpp
Normal file
149
tests/libluna/TestBase64.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
#include <luna/Base64.h>
|
||||
#include <test.h>
|
||||
|
||||
TestResult test_base64_encode_unpadded_message()
|
||||
{
|
||||
auto encoded = TRY(Base64::encode("abc"_sv));
|
||||
|
||||
validate(encoded.view() == "YWJj");
|
||||
|
||||
test_success;
|
||||
}
|
||||
|
||||
TestResult test_base64_decode_unpadded_message()
|
||||
{
|
||||
auto rc = Base64::decode_string("YWJj"_sv);
|
||||
if (rc.has_error())
|
||||
{
|
||||
validate(rc.error() != EINVAL);
|
||||
return rc.release_error();
|
||||
}
|
||||
|
||||
auto decoded = rc.release_value();
|
||||
|
||||
validate(decoded.view() == "abc"_sv);
|
||||
|
||||
test_success;
|
||||
}
|
||||
|
||||
TestResult test_base64_encode_padded_message()
|
||||
{
|
||||
auto encoded = TRY(Base64::encode("abcd"_sv));
|
||||
|
||||
validate(encoded.view() == "YWJjZA==");
|
||||
|
||||
test_success;
|
||||
}
|
||||
|
||||
TestResult test_base64_encode_padded_message_2()
|
||||
{
|
||||
auto encoded = TRY(Base64::encode("abcde"_sv));
|
||||
|
||||
validate(encoded.view() == "YWJjZGU=");
|
||||
|
||||
test_success;
|
||||
}
|
||||
|
||||
TestResult test_base64_decode_padded_message()
|
||||
{
|
||||
auto rc = Base64::decode_string("YWJjZA=="_sv);
|
||||
if (rc.has_error())
|
||||
{
|
||||
validate(rc.error() != EINVAL);
|
||||
return rc.release_error();
|
||||
}
|
||||
|
||||
auto decoded = rc.release_value();
|
||||
|
||||
validate(decoded.view() == "abcd"_sv);
|
||||
|
||||
test_success;
|
||||
}
|
||||
|
||||
TestResult test_base64_decode_padded_message_2()
|
||||
{
|
||||
auto rc = Base64::decode_string("YWJjZGU="_sv);
|
||||
if (rc.has_error())
|
||||
{
|
||||
validate(rc.error() != EINVAL);
|
||||
return rc.release_error();
|
||||
}
|
||||
|
||||
auto decoded = rc.release_value();
|
||||
|
||||
validate(decoded.view() == "abcde"_sv);
|
||||
|
||||
test_success;
|
||||
}
|
||||
|
||||
TestResult test_base64_decode_padded_message_with_newlines()
|
||||
{
|
||||
auto rc = Base64::decode_string("YWJj\nZA==\n"_sv);
|
||||
if (rc.has_error())
|
||||
{
|
||||
validate(rc.error() != EINVAL);
|
||||
return rc.release_error();
|
||||
}
|
||||
|
||||
auto decoded = rc.release_value();
|
||||
|
||||
validate(decoded.view() == "abcd"_sv);
|
||||
|
||||
test_success;
|
||||
}
|
||||
|
||||
TestResult test_base64_disallow_characters_after_padding()
|
||||
{
|
||||
auto rc = Base64::decode_string("YWJjZA==bd"_sv);
|
||||
|
||||
validate(rc.has_error());
|
||||
|
||||
if (rc.error() != EINVAL) return rc.release_error();
|
||||
|
||||
test_success;
|
||||
}
|
||||
|
||||
TestResult test_base64_disallow_garbage_chars_by_default()
|
||||
{
|
||||
auto rc = Base64::decode_string("YWJj?-ZA=="_sv);
|
||||
|
||||
validate(rc.has_error());
|
||||
|
||||
if (rc.error() != EINVAL) return rc.release_error();
|
||||
|
||||
test_success;
|
||||
}
|
||||
|
||||
TestResult test_base64_skip_garbage_chars_if_allowed()
|
||||
{
|
||||
auto rc = Base64::decode_string("YWJj?-ZA=="_sv, true);
|
||||
if (rc.has_error())
|
||||
{
|
||||
validate(rc.error() != EINVAL);
|
||||
return rc.release_error();
|
||||
}
|
||||
|
||||
auto decoded = rc.release_value();
|
||||
|
||||
validate(decoded.view() == "abcd"_sv);
|
||||
|
||||
test_success;
|
||||
}
|
||||
|
||||
Result<void> test_main()
|
||||
{
|
||||
test_prelude;
|
||||
|
||||
run_test(test_base64_encode_unpadded_message);
|
||||
run_test(test_base64_decode_unpadded_message);
|
||||
run_test(test_base64_encode_padded_message);
|
||||
run_test(test_base64_encode_padded_message_2);
|
||||
run_test(test_base64_decode_padded_message);
|
||||
run_test(test_base64_decode_padded_message_2);
|
||||
run_test(test_base64_decode_padded_message_with_newlines);
|
||||
run_test(test_base64_disallow_characters_after_padding);
|
||||
run_test(test_base64_disallow_garbage_chars_by_default);
|
||||
run_test(test_base64_skip_garbage_chars_if_allowed);
|
||||
|
||||
return {};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user