Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions harmony/rn_quick_base64/src/main/cpp/base64.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ template <typename RetString>
inline RetString base64_encode(const unsigned char* bytes_to_encode, size_t in_len, bool url) {
size_t len_encoded = (in_len + 2) / 3 * 4;

unsigned char trailing_char = url ? '.' : '=';
const unsigned char trailing_char = '=';

//
// Choose set of base64 characters. They differ
Expand Down Expand Up @@ -200,12 +200,12 @@ inline RetString base64_encode(const unsigned char* bytes_to_encode, size_t in_l
ret.push_back(base64_chars_[bytes_to_encode[pos + 2] & 0x3f]);
} else {
ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]);
ret.push_back(trailing_char);
if (!url) ret.push_back(trailing_char);
}
} else {
ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]);
ret.push_back(trailing_char);
ret.push_back(trailing_char);
if (!url) ret.push_back(trailing_char);
if (!url) ret.push_back(trailing_char);
}

pos += 3;
Expand Down Expand Up @@ -250,12 +250,12 @@ inline RetString decode(const String& encoded_string, bool remove_linebreaks) {
RetString ret;
ret.reserve(approx_length_of_decoded_string);

while (pos < length_of_string) {
while (pos < length_of_string && encoded_string.at(pos) != '=') {
//
// Iterate over encoded input string in chunks. The size of all
// chunks except the last one is 4 bytes.
//
// The last chunk might be padded with equal signs or dots
// The last chunk might be padded with equal signs
// in order to make it 4 bytes in size as well, but this
// is not required as per RFC 2045.
//
Expand All @@ -273,17 +273,15 @@ inline RetString decode(const String& encoded_string, bool remove_linebreaks) {

if ((pos + 2 < length_of_string) &&
// Check for data that is not padded with equal signs (which is allowed by RFC 2045)
encoded_string.at(pos + 2) != '=' &&
encoded_string.at(pos + 2) != '.' ) { // accept URL-safe base 64 strings, too, so check for '.' also.
encoded_string.at(pos + 2) != '=') {
//
// Emit a chunk's second byte (which might not be produced in the last chunk).
//
unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos + 2));
ret.push_back(static_cast<typename RetString::value_type>(((pos_of_char_1 & 0x0f) << 4) + ((pos_of_char_2 & 0x3c) >> 2)));

if ((pos + 3 < length_of_string) &&
encoded_string.at(pos + 3) != '=' &&
encoded_string.at(pos + 3) != '.' ) {
encoded_string.at(pos + 3) != '=') {
//
// Emit a chunk's third byte (which might not be produced in the last chunk).
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ void installBase64(jsi::Runtime& jsiRuntime) {
1, // string
[](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
std::string str;
if(!valueToString(runtime, arguments[0], &str)) {
if(count > 0 && !valueToString(runtime, arguments[0], &str)) {
return jsi::Value(-1);
}
bool url = false;
if (arguments[1].isBool()) {
if (count > 1 && arguments[1].isBool()) {
url = arguments[1].asBool();
}
try {
Expand All @@ -59,13 +59,13 @@ void installBase64(jsi::Runtime& jsiRuntime) {
jsi::PropNameID::forAscii(jsiRuntime, "base64ToArrayBuffer"),
1, // string
[](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
if (!arguments[0].isString()) {
if (count > 0 && !arguments[0].isString()) {
return jsi::Value(-1);
}

std::string strBase64 = arguments[0].getString(runtime).utf8(runtime);
bool removeLinebreaks = false;
if (arguments[1].isBool()) {
if (count > 1 && arguments[1].isBool()) {
removeLinebreaks = arguments[1].asBool();
}
try {
Expand Down
28 changes: 13 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,24 @@
"registry": "https://registry.npmjs.org/"
},
"devDependencies": {
"@commitlint/config-conventional": "^15.0.0",
"@commitlint/config-conventional": "^19.6.0",
"@react-native-community/eslint-config": "^3.0.1",
"@release-it/conventional-changelog": "^3.3.0",
"@types/jest": "^26.0.0",
"@types/react": "18.2.6",
"@types/jest": "^29.5.13",
"@types/react": "^19.1.0",
"commitlint": "^11.0.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^7.2.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint": "^8.19.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-prettier": "^5.2.3",
"husky": "^6.0.0",
"jest": "^26.0.1",
"jest": "^29.7.0",
"pod-install": "^0.1.0",
"prettier": "^2.4.1",
"react": "18.2.0",
"react-native": "0.73.6",
"react-native-builder-bob": "^0.23.2",
"prettier": "3.0.0",
"react": "19.1.1",
"react-native": "0.82.1",
"react-native-builder-bob": "^0.40.11",
"release-it": "^14.2.2",
"typescript": "^4.5.2"
"typescript": "^5.8.3"
},
"peerDependencies": {
"react": "*",
Expand Down Expand Up @@ -134,7 +134,5 @@
]
]
},
"dependencies": {
"base64-js": "^1.5.1"
}
"dependencies": {}
}
12 changes: 6 additions & 6 deletions react-native-quick-base64.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ Pod::Spec.new do |s|

s.source_files = [
"ios/**/*.{h,m,mm}",
"cpp/**/*.{h,c,cpp}",
"ios/QuickBase64Module.h"
"cpp/**/*.{h,cpp}"
]

s.pod_target_xcconfig = {
"USE_HEADERMAP" => "NO",
s.header_mappings_dir = 'ios'
s.pod_target_xcconfig = {
"USE_HEADERMAP" => "NO"
}

if defined?(install_modules_dependencies()) != nil
if respond_to?(:install_modules_dependencies)
install_modules_dependencies(s)
else
s.dependency "React"
s.dependency "React-Core"
end

end
130 changes: 80 additions & 50 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,38 @@
*/

import { NativeModules } from 'react-native';
import fallback from 'base64-js';

// @ts-ignore We want to check whether __turboModuleProxy exitst, it may not
const isTurboModuleEnabled = global.nativeModuleProxy != null;

const Base64Module = isTurboModuleEnabled
? require('./NativeQuickBase64').default
: NativeModules.QuickBase64;

if (Base64Module && typeof Base64Module.install === 'function') {
Base64Module.install();
declare global {
var base64ToArrayBuffer: FuncBase64ToArrayBuffer
var base64FromArrayBuffer: FuncBase64FromArrayBuffer
}

type FuncBase64ToArrayBuffer = (
data: string,
removeLinebreaks?: boolean
) => ArrayBuffer

type FuncBase64FromArrayBuffer = (
data: string | ArrayBuffer,
urlSafe?: boolean
) => string

declare var base64ToArrayBuffer: FuncBase64ToArrayBuffer | undefined
declare const base64FromArrayBuffer: FuncBase64FromArrayBuffer | undefined
if (Base64Module && typeof global.base64FromArrayBuffer !== 'function') {
Base64Module.install()
}

// from https://github.com/beatgammit/base64-js/blob/master/index.js
/**
* Calculates valid length and placeholder length for base64 string
*/
function getLens(b64: string) {
let len = b64.length
const len = b64.length

if (len % 4 > 0) {
throw new Error('Invalid string. Length must be a multiple of 4')
Expand All @@ -58,93 +63,118 @@ function getLens(b64: string) {
let validLen = b64.indexOf('=')
if (validLen === -1) validLen = len

let placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4)
const placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4)

return [validLen, placeHoldersLen]
return [validLen, placeHoldersLen] as const
}

/**
* Converts Uint8Array to string
*/
function uint8ArrayToString(array: Uint8Array) {
let out = '',
i = 0,
len = array.length
while (i < len) {
const c = array[i++] as number
out += String.fromCharCode(c)
let out = ''
for (let i = 0; i < array.length; i++) {
const charCode = array[i]
if (charCode !== undefined) {
out += String.fromCharCode(charCode)
}
}
return out
}

/**
* Converts string to ArrayBuffer
*/
function stringToArrayBuffer(str: string) {
const buf = new ArrayBuffer(str.length)
const bufView = new Uint8Array(buf)
for (let i = 0, strLen = str.length; i < strLen; i++) {
for (let i = 0; i < str.length; i++) {
bufView[i] = str.charCodeAt(i)
}
return buf
}

export function byteLength(b64: string): number {
let lens = getLens(b64)
let validLen = lens[0] as number
let placeHoldersLen = lens[1] as number
/**
* Calculates the byte length of a base64 string
*/
export function byteLength(b64: string) {
const [validLen, placeHoldersLen] = getLens(b64)
return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen
}

/**
* Converts base64 string to Uint8Array
*/
export function toByteArray(
b64: string,
removeLinebreaks: boolean = false
): Uint8Array {
if (typeof base64ToArrayBuffer !== 'undefined') {
return new Uint8Array(base64ToArrayBuffer(b64, removeLinebreaks))
} else {
return fallback.toByteArray(b64)
}
return new Uint8Array(global.base64ToArrayBuffer(b64, removeLinebreaks))
}

/**
* Converts Uint8Array to base64 string
*/
export function fromByteArray(
uint8: Uint8Array,
urlSafe: boolean = false
): string {
if (typeof base64FromArrayBuffer !== 'undefined') {
if (uint8.buffer.byteLength > uint8.byteLength || uint8.byteOffset > 0) {
return base64FromArrayBuffer(
uint8.buffer.slice(
uint8.byteOffset,
uint8.byteOffset + uint8.byteLength
),
urlSafe
)
if (uint8.buffer.byteLength > uint8.byteLength || uint8.byteOffset > 0) {
const buffer =
uint8.buffer instanceof ArrayBuffer
? uint8.buffer.slice(
uint8.byteOffset,
uint8.byteOffset + uint8.byteLength
)
: new ArrayBuffer(uint8.byteLength)

if (buffer instanceof ArrayBuffer) {
return global.base64FromArrayBuffer(buffer, urlSafe)
}
return base64FromArrayBuffer(uint8.buffer, urlSafe)
} else {
return fallback.fromByteArray(uint8)
}

const buffer =
uint8.buffer instanceof ArrayBuffer
? uint8.buffer
: new ArrayBuffer(uint8.byteLength)
return global.base64FromArrayBuffer(buffer, urlSafe)
}

export function btoa(data: string): string {
const ab = stringToArrayBuffer(data)
if (typeof base64FromArrayBuffer !== 'undefined') {
return base64FromArrayBuffer(ab)
} else {
return fallback.fromByteArray(new Uint8Array(ab))
}
/**
* Base64 encode a string
* @deprecated Use native btoa() instead - now supported in Hermes
*/
export function btoa(data: string) {
return global.base64FromArrayBuffer(stringToArrayBuffer(data), false)
}

export function atob(b64: string): string {
const ua = toByteArray(b64)
return uint8ArrayToString(ua)
/**
* Base64 decode a string
* @deprecated Use native atob() instead - now supported in Hermes
*/
export function atob(b64: string) {
return uint8ArrayToString(toByteArray(b64))
}

/**
* Adds btoa and atob to global scope
*/
export function shim() {
;(global as any).btoa = btoa
;(global as any).atob = atob
}

/**
* Returns native base64 functions
*/
export const getNative = () => ({
base64FromArrayBuffer,
base64ToArrayBuffer,
base64FromArrayBuffer: global.base64FromArrayBuffer,
base64ToArrayBuffer: global.base64ToArrayBuffer
})

export const trimBase64Padding = (str: string): string => {
/**
* Removes padding characters from base64 string
*/
export const trimBase64Padding = (str: string) => {
return str.replace(/[.=]{1,2}$/, '')
}
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
"skipLibCheck": true,
"strict": true,
"target": "esnext",
"outDir": "lib",
"outDir": "lib"
},
"include": [
"src",
".eslintrc.js",
"babel.config.js",
"babel.config.js"
],
"exclude": [
"node_modules",
Expand Down