* Improved false-positive detection in str_search This commit adds the following features: - Extend the false-positive check on ASCII frequencies to all UTF strings - Add a global option to activate/deactivate di check - Improve the false-positive heuristic by adding a special case for extended-ASCII strings
535 lines
16 KiB
C
535 lines
16 KiB
C
// SPDX-FileCopyrightText: 2008-2020 nibble <nibble.ds@gmail.com>
|
|
// SPDX-FileCopyrightText: 2008-2020 pancake <pancake@nopcode.org>
|
|
// SPDX-FileCopyrightText: 2008-2020 thestr4ng3r <info@florianmaerkl.de>
|
|
// SPDX-FileCopyrightText: 2021 Anton Kochkov <anton.kochkov@gmail.com>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include <rz_core.h>
|
|
|
|
RZ_IPI void rz_core_spaces_print(RzCore *core, RzSpaces *spaces, RzCmdStateOutput *state) {
|
|
const RzSpace *cur = rz_spaces_current(spaces);
|
|
rz_cmd_state_output_array_start(state);
|
|
RzSpace *s;
|
|
PJ *pj = state->d.pj;
|
|
RzSpaceIter it;
|
|
rz_spaces_foreach(spaces, it, s) {
|
|
int count = rz_spaces_count(spaces, s->name);
|
|
switch (state->mode) {
|
|
case RZ_OUTPUT_MODE_JSON:
|
|
pj_o(pj);
|
|
pj_ks(pj, "name", s->name);
|
|
pj_ki(pj, "count", count);
|
|
pj_kb(pj, "selected", cur == s);
|
|
pj_end(pj);
|
|
break;
|
|
case RZ_OUTPUT_MODE_QUIET:
|
|
rz_cons_printf("%s\n", s->name);
|
|
break;
|
|
case RZ_OUTPUT_MODE_RIZIN:
|
|
rz_cons_printf("%s %s\n", spaces->name, s->name);
|
|
break;
|
|
case RZ_OUTPUT_MODE_STANDARD:
|
|
rz_cons_printf("%5d %c %s\n", count,
|
|
(!cur || cur == s) ? '*' : '.', s->name);
|
|
break;
|
|
default:
|
|
rz_warn_if_reached();
|
|
break;
|
|
}
|
|
}
|
|
rz_cmd_state_output_array_end(state);
|
|
if (state->mode == RZ_OUTPUT_MODE_RIZIN && rz_spaces_current(spaces)) {
|
|
rz_cons_printf("%s %s # current\n", spaces->name, rz_spaces_current_name(spaces));
|
|
}
|
|
}
|
|
|
|
static char *meta_string_escape(RzCore *core, RzAnalysisMetaItem *mi) {
|
|
char *esc_str = NULL;
|
|
RzStrEscOptions opt = { 0 };
|
|
opt.show_asciidot = false;
|
|
opt.esc_bslash = core->print->esc_bslash;
|
|
switch (mi->subtype) {
|
|
case RZ_STRING_ENC_UTF16LE:
|
|
case RZ_STRING_ENC_UTF16BE:
|
|
case RZ_STRING_ENC_UTF32LE:
|
|
case RZ_STRING_ENC_UTF32BE:
|
|
case RZ_STRING_ENC_UTF8:
|
|
// All strings that are put into the metadata are already converted
|
|
esc_str = rz_str_escape_utf8(mi->str, &opt);
|
|
break;
|
|
case RZ_STRING_ENC_IBM037:
|
|
case RZ_STRING_ENC_IBM290:
|
|
case RZ_STRING_ENC_EBCDIC_UK:
|
|
case RZ_STRING_ENC_EBCDIC_US:
|
|
case RZ_STRING_ENC_EBCDIC_ES:
|
|
case RZ_STRING_ENC_8BIT:
|
|
esc_str = rz_str_escape_8bit(mi->str, false, &opt);
|
|
break;
|
|
default:
|
|
rz_warn_if_reached();
|
|
}
|
|
return esc_str;
|
|
}
|
|
|
|
RZ_IPI void rz_core_meta_print(RzCore *core, RzAnalysisMetaItem *d, ut64 start, ut64 size, bool show_full, RzCmdStateOutput *state) {
|
|
if (rz_spaces_current(&core->analysis->meta_spaces) &&
|
|
rz_spaces_current(&core->analysis->meta_spaces) != d->space) {
|
|
return;
|
|
}
|
|
PJ *pj = state->d.pj;
|
|
RzOutputMode mode = state->mode;
|
|
char *pstr, *base64_str;
|
|
char *str = NULL;
|
|
if (d->str) {
|
|
if (d->type == RZ_META_TYPE_STRING) {
|
|
str = meta_string_escape(core, d);
|
|
} else {
|
|
str = rz_str_escape(d->str);
|
|
}
|
|
}
|
|
if (str || d->type == RZ_META_TYPE_DATA) {
|
|
if (d->type == RZ_META_TYPE_STRING && !*str) {
|
|
free(str);
|
|
return;
|
|
}
|
|
if (!str) {
|
|
pstr = "";
|
|
} else if (d->type == RZ_META_TYPE_FORMAT) {
|
|
pstr = str;
|
|
} else if (d->type == RZ_META_TYPE_STRING) {
|
|
pstr = str;
|
|
} else if (d->type == RZ_META_TYPE_VARTYPE) {
|
|
// Sanitize (don't escape) Ct comments so we can see "char *", etc.
|
|
free(str);
|
|
str = strdup(d->str);
|
|
rz_str_sanitize(str);
|
|
pstr = str;
|
|
} else if (d->type != RZ_META_TYPE_COMMENT) {
|
|
rz_name_filter(str, 0, true);
|
|
pstr = str;
|
|
} else {
|
|
pstr = d->str;
|
|
}
|
|
switch (mode) {
|
|
case RZ_OUTPUT_MODE_JSON:
|
|
pj_o(pj);
|
|
pj_kn(pj, "offset", start);
|
|
pj_ks(pj, "type", rz_meta_type_to_string(d->type));
|
|
|
|
if (d->type == RZ_META_TYPE_HIGHLIGHT) {
|
|
pj_k(pj, "color");
|
|
ut8 r = 0, g = 0, b = 0, A = 0;
|
|
const char *esc = strchr(d->str, '\x1b');
|
|
if (esc) {
|
|
rz_cons_rgb_parse(esc, &r, &g, &b, &A);
|
|
char *rgb_str = rz_cons_rgb_tostring(r, g, b);
|
|
base64_str = rz_base64_encode_dyn((const ut8 *)rgb_str, strlen(rgb_str));
|
|
if (d->type == RZ_META_TYPE_STRING && base64_str) {
|
|
pj_s(pj, base64_str);
|
|
} else {
|
|
pj_s(pj, rgb_str);
|
|
}
|
|
free(base64_str);
|
|
free(rgb_str);
|
|
} else {
|
|
pj_s(pj, str);
|
|
}
|
|
} else {
|
|
pj_k(pj, "name");
|
|
if (d->type == RZ_META_TYPE_STRING && (base64_str = rz_base64_encode_dyn((const ut8 *)d->str, strlen(d->str)))) {
|
|
pj_s(pj, base64_str);
|
|
free(base64_str);
|
|
} else {
|
|
pj_s(pj, rz_str_get(str));
|
|
}
|
|
}
|
|
if (d->type == RZ_META_TYPE_DATA) {
|
|
pj_kn(pj, "size", size);
|
|
} else if (d->type == RZ_META_TYPE_STRING) {
|
|
const char *enc = rz_str_enc_as_string(d->subtype);
|
|
pj_ks(pj, "enc", enc);
|
|
pj_kb(pj, "ascii", rz_str_is_ascii(d->str));
|
|
}
|
|
pj_end(pj);
|
|
break;
|
|
case RZ_OUTPUT_MODE_RIZIN:
|
|
default:
|
|
switch (d->type) {
|
|
case RZ_META_TYPE_COMMENT: {
|
|
const char *type = rz_meta_type_to_string(d->type);
|
|
char *s = sdb_encode((const ut8 *)pstr, -1);
|
|
if (!s) {
|
|
s = strdup(pstr);
|
|
}
|
|
if (mode == RZ_OUTPUT_MODE_RIZIN) {
|
|
if (!strcmp(type, "CCu")) {
|
|
rz_cons_printf("%s base64:%s @ 0x%08" PFMT64x "\n",
|
|
type, s, start);
|
|
} else {
|
|
rz_cons_printf("%s %s @ 0x%08" PFMT64x "\n",
|
|
type, pstr, start);
|
|
}
|
|
} else {
|
|
if (!strcmp(type, "CCu")) {
|
|
char *mys = rz_str_escape(pstr);
|
|
rz_cons_printf("0x%08" PFMT64x " %s \"%s\"\n",
|
|
start, type, mys);
|
|
free(mys);
|
|
} else {
|
|
rz_cons_printf("0x%08" PFMT64x " %s \"%s\"\n",
|
|
start, type, pstr);
|
|
}
|
|
}
|
|
free(s);
|
|
} break;
|
|
case RZ_META_TYPE_STRING:
|
|
if (mode == RZ_OUTPUT_MODE_RIZIN) {
|
|
char cmd[] = "Cs#";
|
|
switch (d->subtype) {
|
|
case RZ_STRING_ENC_8BIT:
|
|
case RZ_STRING_ENC_UTF8:
|
|
cmd[2] = d->subtype;
|
|
break;
|
|
case RZ_STRING_ENC_UTF16LE:
|
|
case RZ_STRING_ENC_UTF16BE:
|
|
cmd[2] = 'w';
|
|
break;
|
|
case RZ_STRING_ENC_UTF32LE:
|
|
case RZ_STRING_ENC_UTF32BE:
|
|
cmd[2] = 'W';
|
|
break;
|
|
default:
|
|
cmd[2] = 0;
|
|
}
|
|
rz_cons_printf("%s %" PFMT64u " @ 0x%08" PFMT64x " # %s\n",
|
|
cmd, size, start, pstr);
|
|
} else {
|
|
const char *enc;
|
|
if (d->subtype == RZ_STRING_ENC_8BIT) {
|
|
enc = rz_str_is_ascii(d->str) ? "ascii" : "8bit";
|
|
} else {
|
|
enc = rz_str_enc_as_string(d->subtype);
|
|
}
|
|
if (show_full || mode == RZ_OUTPUT_MODE_LONG) {
|
|
rz_cons_printf("0x%08" PFMT64x " %s[%" PFMT64u "] \"%s\"\n",
|
|
start, enc, size, pstr);
|
|
} else if (mode == RZ_OUTPUT_MODE_STANDARD) {
|
|
rz_cons_printf("%s[%" PFMT64u "] \"%s\"\n",
|
|
enc, size, pstr);
|
|
} else {
|
|
rz_cons_printf("\"%s\"\n", pstr);
|
|
}
|
|
}
|
|
break;
|
|
case RZ_META_TYPE_HIDE:
|
|
case RZ_META_TYPE_DATA:
|
|
if (mode == RZ_OUTPUT_MODE_RIZIN) {
|
|
rz_cons_printf("%s %" PFMT64u " @ 0x%08" PFMT64x "\n",
|
|
rz_meta_type_to_string(d->type),
|
|
size, start);
|
|
} else {
|
|
if (show_full || mode == RZ_OUTPUT_MODE_LONG) {
|
|
const char *dtype = d->type == RZ_META_TYPE_HIDE ? "hidden" : "data";
|
|
rz_cons_printf("0x%08" PFMT64x " %s %s %" PFMT64u "\n",
|
|
start, dtype,
|
|
rz_meta_type_to_string(d->type), size);
|
|
} else {
|
|
rz_cons_printf("%" PFMT64u "\n", size);
|
|
}
|
|
}
|
|
break;
|
|
case RZ_META_TYPE_MAGIC:
|
|
case RZ_META_TYPE_FORMAT:
|
|
if (mode == RZ_OUTPUT_MODE_RIZIN) {
|
|
rz_cons_printf("%s %" PFMT64u " %s @ 0x%08" PFMT64x "\n",
|
|
rz_meta_type_to_string(d->type),
|
|
size, pstr, start);
|
|
} else {
|
|
if (show_full || mode == RZ_OUTPUT_MODE_LONG) {
|
|
const char *dtype = d->type == RZ_META_TYPE_MAGIC ? "magic" : "format";
|
|
rz_cons_printf("0x%08" PFMT64x " %s %" PFMT64u " %s\n",
|
|
start, dtype, size, pstr);
|
|
} else {
|
|
rz_cons_printf("%" PFMT64u " %s\n", size, pstr);
|
|
}
|
|
}
|
|
break;
|
|
case RZ_META_TYPE_VARTYPE:
|
|
if (mode == RZ_OUTPUT_MODE_RIZIN) {
|
|
rz_cons_printf("%s %s @ 0x%08" PFMT64x "\n",
|
|
rz_meta_type_to_string(d->type), pstr, start);
|
|
} else {
|
|
rz_cons_printf("0x%08" PFMT64x " %s\n", start, pstr);
|
|
}
|
|
break;
|
|
case RZ_META_TYPE_HIGHLIGHT: {
|
|
ut8 r = 0, g = 0, b = 0, A = 0;
|
|
const char *esc = strchr(d->str, '\x1b');
|
|
rz_cons_rgb_parse(esc, &r, &g, &b, &A);
|
|
rz_cons_printf("%s rgb:%02x%02x%02x @ 0x%08" PFMT64x "\n",
|
|
rz_meta_type_to_string(d->type), r, g, b, start);
|
|
// TODO: d->size
|
|
} break;
|
|
default:
|
|
if (mode == RZ_OUTPUT_MODE_RIZIN) {
|
|
rz_cons_printf("%s %" PFMT64u " 0x%08" PFMT64x " # %s\n",
|
|
rz_meta_type_to_string(d->type),
|
|
size, start, pstr);
|
|
} else {
|
|
// TODO: use b64 here
|
|
rz_cons_printf("0x%08" PFMT64x " array[%" PFMT64u "] %s %s\n",
|
|
start, size,
|
|
rz_meta_type_to_string(d->type), pstr);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if (str) {
|
|
free(str);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
RzAnalysisMetaType type;
|
|
const RzSpace *space;
|
|
|
|
RzPVector /*RzIntervalNode*/ *result;
|
|
} CollectCtx;
|
|
|
|
static bool item_matches_filter(RzAnalysisMetaItem *item, RzAnalysisMetaType type, RZ_NULLABLE const RzSpace *space) {
|
|
return (type == RZ_META_TYPE_ANY || item->type == type) && (!space || item->space == space);
|
|
}
|
|
|
|
static bool collect_nodes_cb(RzIntervalNode *node, void *user) {
|
|
CollectCtx *ctx = user;
|
|
if (item_matches_filter(node->data, ctx->type, ctx->space)) {
|
|
rz_pvector_push(ctx->result, node);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static RzPVector *collect_nodes_at(RzAnalysis *analysis, RzAnalysisMetaType type, RZ_NULLABLE const RzSpace *space, ut64 addr) {
|
|
CollectCtx ctx = {
|
|
.type = type,
|
|
.space = space,
|
|
.result = rz_pvector_new(NULL)
|
|
};
|
|
if (!ctx.result) {
|
|
return NULL;
|
|
}
|
|
rz_interval_tree_all_at(&analysis->meta, addr, collect_nodes_cb, &ctx);
|
|
return ctx.result;
|
|
}
|
|
|
|
RZ_IPI void rz_core_meta_print_list_at(RzCore *core, ut64 addr, RzCmdStateOutput *state) {
|
|
RzPVector *nodes = collect_nodes_at(core->analysis, RZ_META_TYPE_ANY,
|
|
rz_spaces_current(&core->analysis->meta_spaces), addr);
|
|
if (!nodes) {
|
|
return;
|
|
}
|
|
rz_cmd_state_output_array_start(state);
|
|
void **it;
|
|
rz_pvector_foreach (nodes, it) {
|
|
RzIntervalNode *node = *it;
|
|
rz_core_meta_print(core, node->data, node->start, rz_meta_node_size(node), true, state);
|
|
}
|
|
rz_pvector_free(nodes);
|
|
rz_cmd_state_output_array_end(state);
|
|
}
|
|
|
|
static void print_meta_list(RzCore *core, RzAnalysisMetaType type, ut64 addr, RzCmdStateOutput *state) {
|
|
RzAnalysisFunction *fcn = NULL;
|
|
if (addr != UT64_MAX) {
|
|
fcn = rz_analysis_get_fcn_in(core->analysis, addr, 0);
|
|
if (!fcn) {
|
|
return;
|
|
}
|
|
}
|
|
RzIntervalTreeIter it;
|
|
RzAnalysisMetaItem *item;
|
|
rz_interval_tree_foreach (&core->analysis->meta, it, item) {
|
|
RzIntervalNode *node = rz_interval_tree_iter_get(&it);
|
|
if (type != RZ_META_TYPE_ANY && item->type != type) {
|
|
continue;
|
|
}
|
|
if (fcn && !rz_analysis_function_contains(fcn, node->start)) {
|
|
continue;
|
|
}
|
|
rz_core_meta_print(core, item, node->start, rz_meta_node_size(node), true, state);
|
|
}
|
|
}
|
|
|
|
RZ_IPI void rz_core_meta_print_list_all(RzCore *core, RzAnalysisMetaType type, RzCmdStateOutput *state) {
|
|
rz_cmd_state_output_array_start(state);
|
|
print_meta_list(core, type, UT64_MAX, state);
|
|
rz_cmd_state_output_array_end(state);
|
|
}
|
|
|
|
RZ_IPI void rz_core_meta_print_list_in_function(RzCore *core, RzAnalysisMetaType type, ut64 addr, RzCmdStateOutput *state) {
|
|
rz_cmd_state_output_array_start(state);
|
|
print_meta_list(core, type, addr, state);
|
|
rz_cmd_state_output_array_end(state);
|
|
}
|
|
|
|
RZ_IPI void rz_core_meta_append(RzCore *core, const char *newcomment, RzAnalysisMetaType mtype, ut64 addr) {
|
|
const char *comment = rz_meta_get_string(core->analysis, mtype, addr);
|
|
char *nc = strdup(newcomment);
|
|
rz_str_unescape(nc);
|
|
if (comment) {
|
|
char *text = rz_str_newf("%s %s", comment, nc);
|
|
if (text) {
|
|
rz_meta_set_string(core->analysis, mtype, addr, text);
|
|
free(text);
|
|
} else {
|
|
rz_sys_perror("malloc");
|
|
}
|
|
} else {
|
|
rz_meta_set_string(core->analysis, mtype, addr, nc);
|
|
}
|
|
free(nc);
|
|
}
|
|
|
|
RZ_IPI void rz_core_meta_editor(RzCore *core, RzAnalysisMetaType mtype, ut64 addr) {
|
|
const char *comment = rz_meta_get_string(core->analysis, mtype, addr);
|
|
char *out = rz_core_editor(core, NULL, comment);
|
|
if (out) {
|
|
rz_meta_del(core->analysis, mtype, addr, 1);
|
|
rz_meta_set_string(core->analysis, mtype, addr, out);
|
|
free(out);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* RzCoreMetaString
|
|
* @{
|
|
*/
|
|
|
|
static bool meta_string_8bit_add(RzCore *core, ut64 addr, size_t limit, ut8 **name, size_t *name_len) {
|
|
rz_return_val_if_fail(limit && name && name_len, false);
|
|
*name = malloc(limit + 1);
|
|
if (!*name) {
|
|
return false;
|
|
}
|
|
if (!rz_io_read_at(core->io, addr, *name, limit)) {
|
|
RZ_FREE(*name);
|
|
return false;
|
|
}
|
|
(*name)[limit] = '\0';
|
|
*name_len = strlen((char *)*name);
|
|
return true;
|
|
}
|
|
|
|
static bool meta_string_guess_add(RzCore *core, ut64 addr, size_t limit, char **name_out, size_t *name_len, RzDetectedString **ds, RzStrEnc encoding) {
|
|
rz_return_val_if_fail(limit && name_out && name_len && ds, false);
|
|
char *name = malloc(limit + 1);
|
|
if (!name) {
|
|
return false;
|
|
}
|
|
RzBinFile *bf = rz_bin_cur(core->bin);
|
|
RzBinObject *obj = rz_bin_cur_object(core->bin);
|
|
if (!bf || !obj) {
|
|
free(name);
|
|
return false;
|
|
}
|
|
bool big_endian = obj ? rz_bin_object_is_big_endian(obj) : RZ_SYS_ENDIAN;
|
|
RzUtilStrScanOptions scan_opt = {
|
|
.buf_size = 2048,
|
|
.max_uni_blocks = 4,
|
|
.min_str_length = 4,
|
|
.prefer_big_endian = big_endian,
|
|
.check_ascii_freq = bf->rbin->strseach_check_ascii_freq
|
|
};
|
|
RzList *str_list = rz_list_new();
|
|
if (!str_list) {
|
|
free(name);
|
|
return false;
|
|
}
|
|
ut64 paddr = rz_io_v2p(core->io, addr);
|
|
int count = rz_scan_strings(bf->buf, str_list, &scan_opt, paddr, paddr + limit, encoding);
|
|
if (count <= 0) {
|
|
rz_list_free(str_list);
|
|
free(name);
|
|
return false;
|
|
}
|
|
*ds = rz_list_first(str_list);
|
|
rz_list_free(str_list);
|
|
rz_str_ncpy(name, (*ds)->string, limit);
|
|
name[limit] = '\0';
|
|
*name_out = name;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief add a string to RzCore
|
|
*
|
|
* \param core RzCore of core that will be add to
|
|
* \param addr string's address
|
|
* \param size string's max size
|
|
* \param encoding string's encoding
|
|
* \param name string's value, or null that will be autodetect at \p addr
|
|
* \return is add successful?
|
|
*/
|
|
RZ_API bool rz_core_meta_string_add(RzCore *core, ut64 addr, ut64 size, RzStrEnc encoding, RZ_NULLABLE const char *name) {
|
|
char *guessname = NULL;
|
|
size_t name_len = 0;
|
|
ut64 limit = size ? size : core->blocksize;
|
|
size_t n = 0;
|
|
bool result = false;
|
|
if (encoding == RZ_STRING_ENC_8BIT || encoding == RZ_STRING_ENC_UTF8) {
|
|
if (!meta_string_8bit_add(core, addr, limit, (ut8 **)&guessname, &name_len)) {
|
|
goto out;
|
|
}
|
|
n = size == 0 ? name_len + 1 : size;
|
|
} else {
|
|
RzDetectedString *ds = NULL;
|
|
if (!meta_string_guess_add(core, addr, limit, &guessname, &name_len, &ds, encoding)) {
|
|
return false;
|
|
}
|
|
if (!ds) {
|
|
goto out;
|
|
}
|
|
encoding = ds->type;
|
|
n = ds->size;
|
|
}
|
|
if (!name) {
|
|
result = rz_meta_set_with_subtype(core->analysis, RZ_META_TYPE_STRING, encoding, addr, n, guessname);
|
|
} else {
|
|
result = rz_meta_set_with_subtype(core->analysis, RZ_META_TYPE_STRING, encoding, addr, n, name);
|
|
}
|
|
out:
|
|
free(guessname);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* \brief add a pascal string to RzCore
|
|
*
|
|
* \param core RzCore of core that will be add to
|
|
* \param addr string's address
|
|
* \param size string's max size
|
|
* \param encoding string's encoding
|
|
* \param name string's value, or null that will be autodetect at \p addr
|
|
* \return is add successful?
|
|
*/
|
|
RZ_API bool rz_core_meta_pascal_string_add(RzCore *core, ut64 addr, RzStrEnc encoding, RZ_NULLABLE const char *name) {
|
|
rz_return_val_if_fail(encoding == RZ_STRING_ENC_8BIT || encoding == RZ_STRING_ENC_UTF8, false);
|
|
// We shall read the first byte and it will be the size of the 8-bit or UTF-8 string
|
|
RzBinFile *bf = rz_bin_cur(core->bin);
|
|
if (!bf) {
|
|
return false;
|
|
}
|
|
ut8 size;
|
|
ut64 paddr = rz_io_v2p(core->io, addr);
|
|
if (!rz_buf_read8_at(bf->buf, paddr, &size)) {
|
|
return false;
|
|
}
|
|
// Note the offset is off by one since the first byte was the size of the string
|
|
if (!rz_core_meta_string_add(core, core->offset + 1, size, encoding, NULL)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**@{*/
|