Current section
Files
Jump to
Current section
Files
c_src/stringprep.cpp
/*
* Copyright (C) 2002-2020 ProcessOne, SARL. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <string.h>
#include <stdint.h>
#include <erl_nif.h>
#include "uni_data.c"
#include "uni_norm.c"
/* Hangul constants */
#define SBase 0xAC00
#define LBase 0x1100
#define VBase 0x1161
#define TBase 0x11A7
#define LCount 19
#define VCount 21
#define TCount 28
#define NCount (VCount * TCount)
#define SCount (LCount * NCount)
static int compose(int ch1, int ch2) {
int info1, info2;
if (LBase <= ch1 && ch1 < LBase + LCount &&
VBase <= ch2 && ch2 < VBase + VCount) {
return SBase + ((ch1 - LBase) * VCount + (ch2 - VBase)) * TCount;
}
if (SBase <= ch1 && ch1 < SBase + SCount && ((ch1 - SBase) % TCount) == 0 &&
TBase <= ch2 && ch2 < TBase + TCount) {
return ch1 + ch2 - TBase;
}
info1 = GetUniCharCompInfo(ch1);
if (info1 != -1 && info1 & CompSingleMask) {
if (!(info1 & CompSecondMask) &&
ch2 == compFirstList[info1 & CompMask][0]) {
return compFirstList[info1 & CompMask][1];
} else
return 0;
}
info2 = GetUniCharCompInfo(ch2);
if (info2 != -1 && info2 & CompSingleMask) {
if ((info2 & CompSecondMask) &&
ch1 == compSecondList[info2 & CompMask][0]) {
return compSecondList[info2 & CompMask][1];
} else
return 0;
}
if (info1 != -1 && info2 != -1 &&
!(info1 & CompSecondMask) && (info2 & CompSecondMask))
return compBothList[info1][info2 & CompMask];
else
return 0;
}
template<class T, int N>
class MaybeStaticBuf {
public:
MaybeStaticBuf() : pos(0), size(N), len(0), buf(static_buf) { }
~MaybeStaticBuf() {
if (buf != static_buf)
enif_free(buf);
}
T init(T ch) {
len = 1;
pos = 0;
return buf[0] = ch;
}
T add(T ch) {
if (len >= size) {
if (buf == static_buf) {
T *old = buf;
buf = (T *) enif_alloc(sizeof(T) * size * 2);
if (!buf)
return -2;
memcpy(buf, old, size * sizeof(T));
} else {
buf = (T *) enif_realloc(buf, sizeof(T) * size * 2);
if (!buf)
return -2;
}
size *= 2;
}
buf[len++] = ch;
return ch;
}
void empty() {
pos = len = 0;
}
void swap(int p1, int p2) {
T ch = buf[p1];
buf[p1] = buf[p2];
buf[p2] = ch;
}
T operator[](int index) {
return buf[index];
}
int pos;
int size;
int len;
private:
T static_buf[N];
T *buf;
};
class UTF8DecoderStream {
public:
UTF8DecoderStream(ErlNifBinary *input) : input(input), pos(0) { };
void reset() {
pos = 0;
}
ErlNifBinary *getBinary() {
return input;
}
int32_t getNext() {
if (pos >= input->size)
return -1;
unsigned char c = input->data[pos++];
if (c <= 0x80) {
return c;
} else if (c < 0xC0) {
return -2;
} else if (c < 0xE0) {
if (pos < input->size && (input->data[pos] & 0xC0) == 0x80) {
return ((c & 0x1F) << 6) | (input->data[pos++] & 0x3F);
}
} else if (c < 0xF0) {
if (pos + 1 < input->size && (input->data[pos] & 0xC0) == 0x80 &&
(input->data[pos + 1] & 0xC0) == 0x80) {
pos += 2;
return ((c & 0x0F) << 12)
| ((input->data[pos - 2] & 0x3F) << 6)
| (input->data[pos - 1] & 0x3F);
}
} else if (c < 0xF8) {
if (pos + 2 < input->size &&
(input->data[pos] & 0xC0) == 0x80 &&
(input->data[pos + 1] & 0xC0) == 0x80 &&
(input->data[pos + 2] & 0xC0) == 0x80) {
int32_t wc = ((c & 0x07) << 18)
| ((input->data[pos] & 0x3F) << 12)
| ((input->data[pos + 1] & 0x3F) << 6)
| (input->data[pos + 2] & 0x3F);
pos += 3;
if (wc <= 0x10FFFF)
return wc;
}
}
return -2;
}
private:
ErlNifBinary *input;
size_t pos;
};
class PreprocessStream {
public:
PreprocessStream(UTF8DecoderStream *source, bool toLower) :
source(source), buf(NULL), pos(0), len(0), toLower(toLower) {
}
int32_t getNext() {
if (pos < len)
return buf[pos++];
loop:
int32_t ch = source->getNext();
if (ch < 0)
return ch;
int info = GetUniCharInfo(ch);
if (!(info & B1Mask)) {
if (toLower) {
if (!(info & MCMask)) {
return ch + GetDelta(info);
} else {
buf = GetMC(info) + 1;
len = buf[-1];
pos = 1;
return buf[0];
}
} else {
return ch;
}
} else
goto loop;
}
private:
UTF8DecoderStream *source;
int32_t *buf;
int pos;
int len;
bool toLower;
};
class DecompositeStream {
public:
DecompositeStream(PreprocessStream *source) : source(source), pos(0), len(0) { }
int32_t getNext() {
if (pos < len)
return decompList[pos++];
int32_t ch = source->getNext();
if (ch < 0)
return ch;
int info = GetUniCharDecompInfo(ch);
if (info >= 0) {
pos = GetDecompShift(info);
len = pos + GetDecompLen(info);
return decompList[pos++];
} else
return ch;
}
private:
PreprocessStream *source;
int pos;
int len;
};
class CanonicalizeStream {
public:
CanonicalizeStream(DecompositeStream *source) : source(source), buf() {
}
int32_t getNext() {
if (buf.pos < buf.len - 1)
return buf[buf.pos++];
int32_t ch, ch2;
if (buf.len > 0) {
ch = buf.init(buf[buf.len - 1]);
} else {
ch = buf.init(source->getNext());
if (ch < 0)
return ch;
}
buf.pos++;
int last = GetUniCharCClass(ch);
while ((ch2 = buf.add(source->getNext())) >= 0) {
int next = GetUniCharCClass(ch2);
if (next != 0 && last > next) {
for (int j = buf.len - 2; j >= 0; j--) {
if (GetUniCharCClass(buf[j]) <= next)
break;
buf.swap(j, j + 1);
}
} else {
return buf[0];
}
}
return buf[0];
}
private:
DecompositeStream *source;
MaybeStaticBuf<int32_t, 8> buf;
};
class ComposeStream {
public:
ComposeStream(CanonicalizeStream *source) : source(source), buf(), lastCh(-1) {
}
int32_t getNext() {
int32_t ch, nch;
if (buf.pos < buf.len)
return buf[buf.pos++];
else
buf.empty();
if (lastCh < 0) {
ch = source->getNext();
if (ch < 0)
return ch;
} else {
ch = lastCh;
}
int cclass1 = GetUniCharCClass(ch);
while ((lastCh = source->getNext()) >= 0) {
int cclass2 = GetUniCharCClass(lastCh);
if ((cclass1 == 0 || cclass2 > cclass1) &&
(nch = compose(ch, lastCh))) {
ch = nch;
} else if (cclass2 == 0) {
return ch;
} else {
buf.add(lastCh);
cclass1 = cclass2;
}
}
if (lastCh >= -1)
return ch;
else
return lastCh;
}
private:
CanonicalizeStream *source;
MaybeStaticBuf<int32_t, 8> buf;
int32_t lastCh;
};
class PrepCheckStream {
public:
PrepCheckStream(ComposeStream *source, int32_t prohibit) :
source(source), prohibit(prohibit), first_ral(-1),
last_ral(0), have_ral(0), have_l(0) {
}
int32_t getNext() {
int32_t ch = source->getNext();
if (ch < 0)
return ch;
int32_t info = GetUniCharInfo(ch);
if (info & prohibit) {
return -2;
}
if (first_ral < 0)
first_ral = (info & D1Mask) != 0;
last_ral = (info & D1Mask) != 0;
have_ral = have_ral || last_ral;
have_l = have_l || (info & D2Mask) != 0;
return ch;
}
bool was_valid() {
return !(have_ral && (!first_ral || !last_ral || have_l));
}
private:
ComposeStream *source;
int32_t prohibit;
char first_ral;
char last_ral;
char have_ral;
char have_l;
};
class UTF8Encoder {
public:
UTF8Encoder(size_t initial_size, UTF8DecoderStream *input) : input(*input), pos(0) {
binary.size = initial_size < 4 ? 4 : initial_size;
binary.data = NULL;
}
~UTF8Encoder() {
if (binary.data)
enif_release_binary(&binary);
}
ErlNifBinary *encode_stream(PrepCheckStream *source) {
int32_t ch, ich;
int idx = 0;
while ((ch = source->getNext()) == (ich = input.getNext()) && ch >= 0) {
idx++;
}
if (ch < -1)
return NULL;
if (ch != ich) {
input.reset();
while (idx-- > 0)
if (put_char(input.getNext()) < 0)
return NULL;
if (ch >= 0) {
do {
if (put_char(ch) < 0)
return NULL;
} while ((ch = source->getNext()) >= 0);
if (ch < -1)
return NULL;
}
} else {
return input.getBinary();
}
if (binary.data) {
if (pos != binary.size && !enif_realloc_binary(&binary, pos))
return NULL;
} else if (!enif_alloc_binary(0, &binary))
return NULL;
return &binary;
}
int put_char(int32_t ch) {
if (ch <= 0x7F) {
if (!buf_size_inc(1)) return -2;
binary.data[pos++] = (unsigned char) ch;
} else if (ch <= 0x7FF) {
if (!buf_size_inc(2)) return -2;
binary.data[pos] = (unsigned char) ((ch >> 6) | 0xC0);
binary.data[pos + 1] = (unsigned char) ((ch | 0x80) & 0xBF);
pos += 2;
} else if (ch <= 0xFFFF) {
if (!buf_size_inc(3)) return -2;
binary.data[pos] = (unsigned char) ((ch >> 12) | 0xE0);
binary.data[pos + 1] = (unsigned char) (((ch >> 6) | 0x80) & 0xBF);
binary.data[pos + 2] = (unsigned char) ((ch | 0x80) & 0xBF);
pos += 3;
} else if (ch <= 0x1FFFFF) {
if (!buf_size_inc(4)) return -2;
binary.data[pos] = (unsigned char) ((ch >> 18) | 0xF0);
binary.data[pos + 1] = (unsigned char) (((ch >> 12) | 0x80) & 0xBF);
binary.data[pos + 2] = (unsigned char) (((ch >> 6) | 0x80) & 0xBF);
binary.data[pos + 3] = (unsigned char) ((ch | 0x80) & 0xBF);
pos += 4;
} else
return -2;
return 0;
}
private:
int buf_size_inc(int inc) {
int res = 1;
if (!binary.data)
res = enif_alloc_binary(binary.size, &binary);
if (pos + inc > binary.size)
res = enif_realloc_binary(&binary, binary.size * 2);
return res;
}
UTF8DecoderStream input;
ErlNifBinary binary;
size_t pos;
};
static int load(ErlNifEnv *env, void **priv, ERL_NIF_TERM load_info) {
return 0;
}
static ERL_NIF_TERM prep(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[],
int prohibit, bool toLower) {
ErlNifBinary input;
if (argc != 1)
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[0], &input))
return enif_make_badarg(env);
UTF8DecoderStream decoder(&input);
PreprocessStream normalize(&decoder, toLower);
DecompositeStream decomposite(&normalize);
CanonicalizeStream canonicalize(&decomposite);
ComposeStream compose(&canonicalize);
PrepCheckStream prepCheck(&compose, prohibit);
UTF8Encoder encode(input.size, &decoder);
ErlNifBinary *res = encode.encode_stream(&prepCheck);
if (!res || !prepCheck.was_valid()) {
return enif_make_atom(env, "error");
} else
return enif_make_binary(env, res);
}
static ERL_NIF_TERM nodeprep(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
return prep(env, argc, argv, ACMask | C11Mask | C21Mask | XNPMask, 1);
}
static ERL_NIF_TERM nameprep(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
return prep(env, argc, argv, ACMask, 1);
}
static ERL_NIF_TERM resourceprep(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
return prep(env, argc, argv, ACMask | C21Mask, 0);
}
static ERL_NIF_TERM to_lower(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
return prep(env, argc, argv, ACMask, 1);
}
static ERL_NIF_TERM to_lower_no_filter(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
return prep(env, argc, argv, 0, 1);
}
static ErlNifFunc nif_funcs[] =
{
{"nodeprep", 1, nodeprep},
{"nameprep", 1, nameprep},
{"resourceprep", 1, resourceprep},
{"tolower", 1, to_lower},
{"tolower_nofilter", 1, to_lower_no_filter}
};
ERL_NIF_INIT(stringprep, nif_funcs, load, NULL, NULL, NULL)