Skip to content

Commit 1fe6bcb

Browse files
committed
Add a simple base64 implementation
This keeps us from adding a crate for one function. It's mostly based on https://github.com/marshallpierce/rust-base64/blob/a675443d327e175f735a37f574de803d6a332591/src/engine/naive.rs#L42
1 parent d409018 commit 1fe6bcb

File tree

7 files changed

+215
-10
lines changed

7 files changed

+215
-10
lines changed

Cargo.lock

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

helix-view/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ homepage = "https://helix-editor.com"
1111

1212
[features]
1313
default = []
14-
term = ["crossterm", "base64"]
14+
term = ["crossterm"]
1515

1616
[dependencies]
1717
bitflags = "1.3"
@@ -20,7 +20,6 @@ helix-core = { version = "0.6", path = "../helix-core" }
2020
helix-lsp = { version = "0.6", path = "../helix-lsp" }
2121
helix-dap = { version = "0.6", path = "../helix-dap" }
2222
crossterm = { version = "0.24", optional = true }
23-
base64 = { version = "0.13", optional = true }
2423

2524
# Conversion traits
2625
once_cell = "1.13"

helix-view/src/base64/LICENSE-MIT

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015 Alice Maz
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

helix-view/src/base64/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
A minimal base64 implementation to keep from pulling in a crate for just that. It's based on
2+
https://github.com/marshallpierce/rust-base64 but without all the customization options.
3+
The biggest portion comes from
4+
https://github.com/marshallpierce/rust-base64/blob/a675443d327e175f735a37f574de803d6a332591/src/engine/naive.rs#L42
5+
Thanks, rust-base64!

helix-view/src/base64/mod.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use std::ops::{BitAnd, BitOr, Shl, Shr};
2+
3+
pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
4+
let encoded_size =
5+
encoded_len(input.as_ref().len()).expect("integer overflow when calculating buffer size");
6+
let mut buf = vec![0; encoded_size];
7+
8+
encode_with_padding(input.as_ref(), &mut buf[..], encoded_size);
9+
10+
String::from_utf8(buf).expect("Invalid UTF8")
11+
}
12+
13+
pub fn encoded_len(bytes_len: usize) -> Option<usize> {
14+
let rem = bytes_len % 3;
15+
16+
let complete_input_chunks = bytes_len / 3;
17+
let complete_chunk_output = complete_input_chunks.checked_mul(4);
18+
19+
if rem > 0 {
20+
complete_chunk_output.and_then(|c| c.checked_add(4))
21+
} else {
22+
complete_chunk_output
23+
}
24+
}
25+
26+
fn encode_with_padding(input: &[u8], output: &mut [u8], expected_encoded_size: usize) {
27+
debug_assert_eq!(expected_encoded_size, output.len());
28+
29+
let b64_bytes_written = encode_bytes(input, output);
30+
31+
let padding_bytes = add_padding(input.len(), &mut output[b64_bytes_written..]);
32+
33+
let encoded_bytes = b64_bytes_written
34+
.checked_add(padding_bytes)
35+
.expect("usize overflow when calculating b64 length");
36+
37+
debug_assert_eq!(expected_encoded_size, encoded_bytes);
38+
}
39+
40+
pub fn add_padding(input_len: usize, output: &mut [u8]) -> usize {
41+
// TODO base on encoded len to use cheaper mod by 4 (aka & 7)
42+
let rem = input_len % 3;
43+
let mut bytes_written = 0;
44+
for _ in 0..((3 - rem) % 3) {
45+
output[bytes_written] = PAD_BYTE;
46+
bytes_written += 1;
47+
}
48+
49+
bytes_written
50+
}
51+
const PAD_BYTE: u8 = b'=';
52+
const ENCODE_TABLE: &[u8] =
53+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".as_bytes();
54+
55+
fn encode_bytes(input: &[u8], output: &mut [u8]) -> usize {
56+
// complete chunks first
57+
58+
const LOW_SIX_BITS: u32 = 0x3F;
59+
60+
let rem = input.len() % 3;
61+
// will never underflow
62+
let complete_chunk_len = input.len() - rem;
63+
64+
let mut input_index = 0_usize;
65+
let mut output_index = 0_usize;
66+
if let Some(last_complete_chunk_index) = complete_chunk_len.checked_sub(3) {
67+
while input_index <= last_complete_chunk_index {
68+
let chunk = &input[input_index..input_index + 3];
69+
70+
// populate low 24 bits from 3 bytes
71+
let chunk_int: u32 =
72+
(chunk[0] as u32).shl(16) | (chunk[1] as u32).shl(8) | (chunk[2] as u32);
73+
// encode 4x 6-bit output bytes
74+
output[output_index] = ENCODE_TABLE[chunk_int.shr(18) as usize];
75+
output[output_index + 1] =
76+
ENCODE_TABLE[chunk_int.shr(12_u8).bitand(LOW_SIX_BITS) as usize];
77+
output[output_index + 2] =
78+
ENCODE_TABLE[chunk_int.shr(6_u8).bitand(LOW_SIX_BITS) as usize];
79+
output[output_index + 3] = ENCODE_TABLE[chunk_int.bitand(LOW_SIX_BITS) as usize];
80+
81+
input_index += 3;
82+
output_index += 4;
83+
}
84+
}
85+
86+
// then leftovers
87+
if rem == 2 {
88+
let chunk = &input[input_index..input_index + 2];
89+
90+
// high six bits of chunk[0]
91+
output[output_index] = ENCODE_TABLE[chunk[0].shr(2) as usize];
92+
// bottom 2 bits of [0], high 4 bits of [1]
93+
output[output_index + 1] = ENCODE_TABLE
94+
[(chunk[0].shl(4_u8).bitor(chunk[1].shr(4_u8)) as u32).bitand(LOW_SIX_BITS) as usize];
95+
// bottom 4 bits of [1], with the 2 bottom bits as zero
96+
output[output_index + 2] =
97+
ENCODE_TABLE[(chunk[1].shl(2_u8) as u32).bitand(LOW_SIX_BITS) as usize];
98+
99+
output_index += 3;
100+
} else if rem == 1 {
101+
let byte = input[input_index];
102+
output[output_index] = ENCODE_TABLE[byte.shr(2) as usize];
103+
output[output_index + 1] =
104+
ENCODE_TABLE[(byte.shl(4_u8) as u32).bitand(LOW_SIX_BITS) as usize];
105+
output_index += 2;
106+
}
107+
108+
output_index
109+
}
110+
111+
#[cfg(test)]
112+
mod tests {
113+
fn compare_encode(expected: &str, target: &[u8]) {
114+
assert_eq!(expected, super::encode(target));
115+
}
116+
117+
#[test]
118+
fn encode_rfc4648_0() {
119+
compare_encode("", b"");
120+
}
121+
122+
#[test]
123+
fn encode_rfc4648_1() {
124+
compare_encode("Zg==", b"f");
125+
}
126+
127+
#[test]
128+
fn encode_rfc4648_2() {
129+
compare_encode("Zm8=", b"fo");
130+
}
131+
132+
#[test]
133+
fn encode_rfc4648_3() {
134+
compare_encode("Zm9v", b"foo");
135+
}
136+
137+
#[test]
138+
fn encode_rfc4648_4() {
139+
compare_encode("Zm9vYg==", b"foob");
140+
}
141+
142+
#[test]
143+
fn encode_rfc4648_5() {
144+
compare_encode("Zm9vYmE=", b"fooba");
145+
}
146+
147+
#[test]
148+
fn encode_rfc4648_6() {
149+
compare_encode("Zm9vYmFy", b"foobar");
150+
}
151+
152+
#[test]
153+
fn encode_all_ascii() {
154+
let mut ascii = Vec::<u8>::with_capacity(128);
155+
156+
for i in 0..128 {
157+
ascii.push(i);
158+
}
159+
160+
compare_encode(
161+
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7P\
162+
D0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8\
163+
=",
164+
&ascii,
165+
);
166+
}
167+
168+
#[test]
169+
fn encode_all_bytes() {
170+
let mut bytes = Vec::<u8>::with_capacity(256);
171+
172+
for i in 0..255 {
173+
bytes.push(i);
174+
}
175+
bytes.push(255); //bug with "overflowing" ranges?
176+
177+
compare_encode(
178+
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7P\
179+
D0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn\
180+
+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6\
181+
/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==",
182+
&bytes,
183+
);
184+
}
185+
}

helix-view/src/clipboard.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ mod provider {
148148

149149
#[cfg(feature = "term")]
150150
mod osc52 {
151-
use {super::ClipboardType, base64, crossterm};
151+
use {super::ClipboardType, crate::base64, crossterm};
152152

153153
#[derive(Debug)]
154154
pub struct SetClipboardCommand {

helix-view/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub mod handlers {
1010
pub mod dap;
1111
pub mod lsp;
1212
}
13+
#[cfg(feature = "term")]
14+
mod base64;
1315
pub mod info;
1416
pub mod input;
1517
pub mod keyboard;

0 commit comments

Comments
 (0)