2007-12-24 14:57:53 +01:00
|
|
|
/*
|
2010-01-12 17:11:32 +01:00
|
|
|
* ejabberd, Copyright (C) 2002-2010 ProcessOne
|
2007-12-24 14:57:53 +01:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License as
|
|
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* General Public License for more details.
|
2009-06-09 12:56:14 +02:00
|
|
|
*
|
2007-12-24 14:57:53 +01:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
|
|
* 02111-1307 USA
|
|
|
|
*
|
|
|
|
*/
|
2003-09-26 20:55:01 +02:00
|
|
|
|
|
|
|
#include <stdio.h>
|
2004-03-21 21:27:09 +01:00
|
|
|
#include <string.h>
|
2003-09-26 20:55:01 +02:00
|
|
|
#include <erl_driver.h>
|
|
|
|
#include <ei.h>
|
|
|
|
|
|
|
|
#include "uni_data.c"
|
2003-10-06 22:12:11 +02:00
|
|
|
#include "uni_norm.c"
|
2003-09-26 20:55:01 +02:00
|
|
|
|
2003-09-28 20:27:55 +02:00
|
|
|
#define NAMEPREP_COMMAND 1
|
|
|
|
#define NODEPREP_COMMAND 2
|
|
|
|
#define RESOURCEPREP_COMMAND 3
|
|
|
|
|
2003-09-26 20:55:01 +02:00
|
|
|
typedef struct {
|
|
|
|
ErlDrvPort port;
|
|
|
|
} stringprep_data;
|
|
|
|
|
|
|
|
|
|
|
|
static ErlDrvData stringprep_erl_start(ErlDrvPort port, char *buff)
|
|
|
|
{
|
|
|
|
stringprep_data* d = (stringprep_data*)driver_alloc(sizeof(stringprep_data));
|
|
|
|
d->port = port;
|
|
|
|
|
2003-09-28 20:27:55 +02:00
|
|
|
//set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
|
2003-09-26 20:55:01 +02:00
|
|
|
|
|
|
|
return (ErlDrvData)d;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void stringprep_erl_stop(ErlDrvData handle)
|
|
|
|
{
|
|
|
|
driver_free((char*)handle);
|
|
|
|
}
|
|
|
|
|
2005-05-06 03:38:05 +02:00
|
|
|
|
|
|
|
/* 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)
|
|
|
|
|
2003-10-07 22:31:44 +02:00
|
|
|
/*
|
|
|
|
* "canonical_ordering" and "compose" functions are based on nfkc.c from Gnome
|
|
|
|
* library
|
|
|
|
*/
|
2003-10-06 22:12:11 +02:00
|
|
|
|
2003-10-09 20:09:05 +02:00
|
|
|
static void canonical_ordering(int *str, int len)
|
2003-10-07 22:31:44 +02:00
|
|
|
{
|
|
|
|
int i, j, t;
|
|
|
|
int last, next;
|
|
|
|
|
|
|
|
last = GetUniCharCClass(str[0]);
|
|
|
|
for (i = 0; i < len - 1; i++)
|
|
|
|
{
|
|
|
|
next = GetUniCharCClass(str[i + 1]);
|
|
|
|
if (next != 0 && last > next)
|
|
|
|
{
|
2005-05-07 03:21:39 +02:00
|
|
|
for (j = i; j >= 0; j--)
|
2003-10-07 22:31:44 +02:00
|
|
|
{
|
|
|
|
if (GetUniCharCClass(str[j]) <= next)
|
|
|
|
break;
|
|
|
|
t = str[j + 1];
|
|
|
|
str[j + 1] = str[j];
|
|
|
|
str[j] = t;
|
|
|
|
}
|
|
|
|
next = last;
|
|
|
|
}
|
|
|
|
last = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int compose(int ch1, int ch2)
|
2003-10-06 22:12:11 +02:00
|
|
|
{
|
|
|
|
int info1, info2;
|
|
|
|
|
2005-05-06 03:38:05 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2003-10-06 22:12:11 +02:00
|
|
|
info1 = GetUniCharCompInfo(ch1);
|
2005-05-19 01:47:33 +02:00
|
|
|
if (info1 != -1 && info1 & CompSingleMask) {
|
|
|
|
if (!(info1 & CompSecondMask) &&
|
|
|
|
ch2 == compFirstList[info1 & CompMask][0]) {
|
|
|
|
return compFirstList[info1 & CompMask][1];
|
|
|
|
} else
|
|
|
|
return 0;
|
2005-05-06 03:38:05 +02:00
|
|
|
}
|
|
|
|
|
2003-10-06 22:12:11 +02:00
|
|
|
info2 = GetUniCharCompInfo(ch2);
|
2005-05-19 01:47:33 +02:00
|
|
|
if (info2 != -1 && info2 & CompSingleMask) {
|
|
|
|
if ((info2 & CompSecondMask) &&
|
|
|
|
ch1 == compSecondList[info2 & CompMask][0]) {
|
|
|
|
return compSecondList[info2 & CompMask][1];
|
|
|
|
} else
|
|
|
|
return 0;
|
2005-05-06 03:38:05 +02:00
|
|
|
}
|
2003-10-06 22:12:11 +02:00
|
|
|
|
2005-05-06 03:38:05 +02:00
|
|
|
if (info1 != -1 && info2 != -1 &&
|
|
|
|
!(info1 & CompSecondMask) && (info2 & CompSecondMask))
|
|
|
|
return compBothList[info1][info2 & CompMask];
|
|
|
|
else
|
|
|
|
return 0;
|
2003-10-06 22:12:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define ADD_UCHAR(ruc) \
|
2005-05-07 03:21:39 +02:00
|
|
|
if (ruc <= 0x7F) { \
|
|
|
|
if (pos >= size) { \
|
2003-10-06 22:12:11 +02:00
|
|
|
size = 2*size + 1; \
|
|
|
|
rstring = driver_realloc(rstring, size); \
|
|
|
|
} \
|
|
|
|
rstring[pos] = (char) ruc; \
|
|
|
|
pos++; \
|
2005-05-07 03:21:39 +02:00
|
|
|
} else if (ruc <= 0x7FF) { \
|
|
|
|
if (pos + 1 >= size) { \
|
2003-10-06 22:12:11 +02:00
|
|
|
size = 2*size + 2; \
|
|
|
|
rstring = driver_realloc(rstring, size); \
|
|
|
|
} \
|
|
|
|
rstring[pos] = (char) ((ruc >> 6) | 0xC0); \
|
|
|
|
rstring[pos+1] = (char) ((ruc | 0x80) & 0xBF); \
|
|
|
|
pos += 2; \
|
2005-05-07 03:21:39 +02:00
|
|
|
} else if (ruc <= 0xFFFF) { \
|
|
|
|
if (pos + 2 >= size) { \
|
2003-10-06 22:12:11 +02:00
|
|
|
size = 2*size + 3; \
|
|
|
|
rstring = driver_realloc(rstring, size); \
|
|
|
|
} \
|
|
|
|
rstring[pos] = (char) ((ruc >> 12) | 0xE0); \
|
|
|
|
rstring[pos+1] = (char) (((ruc >> 6) | 0x80) & 0xBF); \
|
|
|
|
rstring[pos+2] = (char) ((ruc | 0x80) & 0xBF); \
|
|
|
|
pos += 3; \
|
2005-05-07 03:21:39 +02:00
|
|
|
} else if (ruc <= 0x1FFFFF) { \
|
2005-05-17 14:51:47 +02:00
|
|
|
if (pos + 3 >= size) { \
|
2005-05-07 03:21:39 +02:00
|
|
|
size = 2*size + 4; \
|
|
|
|
rstring = driver_realloc(rstring, size); \
|
|
|
|
} \
|
|
|
|
rstring[pos] = (char) ((ruc >> 18) | 0xF0); \
|
|
|
|
rstring[pos+1] = (char) (((ruc >> 12) | 0x80) & 0xBF); \
|
|
|
|
rstring[pos+2] = (char) (((ruc >> 6) | 0x80) & 0xBF); \
|
|
|
|
rstring[pos+3] = (char) ((ruc | 0x80) & 0xBF); \
|
|
|
|
pos += 4; \
|
2003-10-06 22:12:11 +02:00
|
|
|
}
|
|
|
|
|
2003-10-07 22:31:44 +02:00
|
|
|
#define ADD_UCHAR32(str, pos, len, ch) \
|
2005-05-07 03:21:39 +02:00
|
|
|
if (pos >= len) { \
|
2003-10-07 22:31:44 +02:00
|
|
|
len = 2*len + 1; \
|
|
|
|
str = driver_realloc(str, len * sizeof(int)); \
|
|
|
|
} \
|
|
|
|
str[pos] = ch; \
|
|
|
|
pos++;
|
|
|
|
|
|
|
|
|
|
|
|
#define ADD_DECOMP(ruc) \
|
|
|
|
info = GetUniCharDecompInfo(ruc); \
|
2005-05-07 03:21:39 +02:00
|
|
|
if (info >= 0) { \
|
2003-10-07 22:31:44 +02:00
|
|
|
decomp_len = GetDecompLen(info); \
|
|
|
|
decomp_shift = GetDecompShift(info); \
|
2005-05-07 03:21:39 +02:00
|
|
|
for (j = 0; j < decomp_len; j++) { \
|
2003-10-07 22:31:44 +02:00
|
|
|
ADD_UCHAR32(str32, str32pos, str32len, \
|
|
|
|
decompList[decomp_shift + j]); \
|
|
|
|
} \
|
|
|
|
} else { \
|
|
|
|
ADD_UCHAR32(str32, str32pos, str32len, ruc); \
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-10-06 22:12:11 +02:00
|
|
|
|
2003-09-26 20:55:01 +02:00
|
|
|
static int stringprep_erl_control(ErlDrvData drv_data,
|
|
|
|
unsigned int command,
|
|
|
|
char *buf, int len,
|
|
|
|
char **rbuf, int rlen)
|
|
|
|
{
|
2003-10-06 22:12:11 +02:00
|
|
|
int i, j, pos=1;
|
2003-09-26 20:55:01 +02:00
|
|
|
unsigned char c;
|
|
|
|
int bad = 0;
|
2003-10-08 20:27:48 +02:00
|
|
|
int uc = 0, ruc;
|
2003-09-26 20:55:01 +02:00
|
|
|
int size;
|
|
|
|
int info;
|
2003-10-08 20:27:48 +02:00
|
|
|
int prohibit = 0, tolower = 0;
|
2003-09-26 20:55:01 +02:00
|
|
|
char *rstring;
|
2003-10-06 22:12:11 +02:00
|
|
|
int *mc;
|
2003-10-07 22:31:44 +02:00
|
|
|
int *str32;
|
|
|
|
int str32len, str32pos = 0;
|
|
|
|
int decomp_len, decomp_shift;
|
2003-10-08 20:27:48 +02:00
|
|
|
int comp_pos, comp_starter_pos;
|
2005-05-06 03:38:05 +02:00
|
|
|
int cclass_prev, cclass2;
|
2003-10-07 22:31:44 +02:00
|
|
|
int ch1, ch2;
|
2005-05-07 03:21:39 +02:00
|
|
|
int first_ral, last_ral, have_ral, have_l;
|
2003-10-07 22:31:44 +02:00
|
|
|
|
2003-09-28 20:27:55 +02:00
|
|
|
size = len + 1;
|
|
|
|
|
|
|
|
rstring = driver_alloc(size);
|
|
|
|
rstring[0] = 0;
|
2003-09-26 20:55:01 +02:00
|
|
|
|
2003-10-07 22:31:44 +02:00
|
|
|
str32len = len + 1;
|
|
|
|
|
|
|
|
str32 = driver_alloc(str32len * sizeof(int));
|
|
|
|
|
2003-09-28 20:27:55 +02:00
|
|
|
switch (command)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
prohibit = ACMask;
|
|
|
|
tolower = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NAMEPREP_COMMAND:
|
|
|
|
prohibit = ACMask;
|
|
|
|
tolower = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NODEPREP_COMMAND:
|
|
|
|
prohibit = ACMask | C11Mask | C21Mask | XNPMask;
|
|
|
|
tolower = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RESOURCEPREP_COMMAND:
|
|
|
|
prohibit = ACMask | C21Mask;
|
|
|
|
tolower = 0;
|
|
|
|
break;
|
|
|
|
}
|
2003-09-26 20:55:01 +02:00
|
|
|
|
2005-05-07 03:21:39 +02:00
|
|
|
for (i = 0; i < len; i++)
|
2003-09-26 20:55:01 +02:00
|
|
|
{
|
|
|
|
c = buf[i];
|
2005-05-07 03:21:39 +02:00
|
|
|
if (c < 0x80) {
|
2003-09-26 20:55:01 +02:00
|
|
|
uc = c;
|
2005-05-07 03:21:39 +02:00
|
|
|
} else if (c < 0xC0) {
|
2003-09-26 20:55:01 +02:00
|
|
|
bad = 1;
|
2005-05-07 03:21:39 +02:00
|
|
|
} else if (c < 0xE0) {
|
|
|
|
if (i+1 < len && (buf[i+1] & 0xC0) == 0x80) {
|
2003-09-26 20:55:01 +02:00
|
|
|
uc = ((c & 0x1F) << 6) | (buf[i+1] & 0x3F);
|
|
|
|
i++;
|
|
|
|
} else {
|
|
|
|
bad = 1;
|
|
|
|
}
|
2005-05-07 03:21:39 +02:00
|
|
|
} else if (c < 0xF0) {
|
|
|
|
if (i+2 < len && (buf[i+1] & 0xC0) == 0x80 &&
|
|
|
|
(buf[i+2] & 0xC0) == 0x80) {
|
|
|
|
uc = ((c & 0x0F) << 12)
|
|
|
|
| ((buf[i+1] & 0x3F) << 6)
|
2003-09-26 20:55:01 +02:00
|
|
|
| (buf[i+2] & 0x3F);
|
|
|
|
i += 2;
|
|
|
|
} else {
|
|
|
|
bad = 1;
|
|
|
|
}
|
2005-05-07 03:21:39 +02:00
|
|
|
} else if (c < 0xF8) {
|
|
|
|
if (i+3 < len &&
|
|
|
|
(buf[i+1] & 0xC0) == 0x80 &&
|
|
|
|
(buf[i+2] & 0xC0) == 0x80 &&
|
|
|
|
(buf[i+3] & 0xC0) == 0x80) {
|
|
|
|
uc = ((c & 0x07) << 18)
|
|
|
|
| ((buf[i+1] & 0x3F) << 12)
|
|
|
|
| ((buf[i+2] & 0x3F) << 6)
|
|
|
|
| (buf[i+3] & 0x3F);
|
|
|
|
i += 3;
|
|
|
|
if (uc > 0x10FFFF)
|
|
|
|
bad = 1;
|
|
|
|
} else {
|
|
|
|
bad = 1;
|
|
|
|
}
|
2003-09-26 20:55:01 +02:00
|
|
|
} else {
|
|
|
|
bad = 1;
|
|
|
|
}
|
|
|
|
|
2005-05-07 03:21:39 +02:00
|
|
|
if (bad) {
|
2003-09-28 20:27:55 +02:00
|
|
|
*rbuf = rstring;
|
2003-10-07 22:31:44 +02:00
|
|
|
driver_free(str32);
|
2003-09-26 20:55:01 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
info = GetUniCharInfo(uc);
|
2003-09-28 20:27:55 +02:00
|
|
|
|
2005-05-07 03:21:39 +02:00
|
|
|
if (!(info & B1Mask))
|
2003-09-28 20:27:55 +02:00
|
|
|
{
|
2005-05-07 03:21:39 +02:00
|
|
|
if (tolower) {
|
|
|
|
if (!(info & MCMask))
|
2003-10-06 22:12:11 +02:00
|
|
|
{
|
|
|
|
ruc = uc + GetDelta(info);
|
2003-10-07 22:31:44 +02:00
|
|
|
ADD_DECOMP(ruc);
|
2003-10-06 22:12:11 +02:00
|
|
|
} else {
|
|
|
|
mc = GetMC(info);
|
2005-05-07 03:21:39 +02:00
|
|
|
for (j = 1; j <= mc[0]; j++) {
|
2003-10-06 22:12:11 +02:00
|
|
|
ruc = mc[j];
|
2003-10-07 22:31:44 +02:00
|
|
|
ADD_DECOMP(ruc);
|
2003-10-06 22:12:11 +02:00
|
|
|
}
|
|
|
|
}
|
2003-09-28 20:27:55 +02:00
|
|
|
} else {
|
|
|
|
ruc = uc;
|
2003-10-07 22:31:44 +02:00
|
|
|
ADD_DECOMP(ruc);
|
2003-09-26 20:55:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2003-10-06 22:12:11 +02:00
|
|
|
|
2003-10-07 22:31:44 +02:00
|
|
|
if (str32pos == 0) {
|
|
|
|
rstring[0] = 1;
|
|
|
|
*rbuf = rstring;
|
|
|
|
driver_free(str32);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
canonical_ordering(str32, str32pos);
|
|
|
|
|
2003-10-08 20:27:48 +02:00
|
|
|
comp_pos = 1;
|
|
|
|
comp_starter_pos = 0;
|
2003-10-07 22:31:44 +02:00
|
|
|
ch1 = str32[0];
|
2005-05-06 03:38:05 +02:00
|
|
|
cclass_prev = GetUniCharCClass(ch1);
|
|
|
|
for (i = 1; i < str32pos; i++)
|
2003-10-07 22:31:44 +02:00
|
|
|
{
|
|
|
|
ch2 = str32[i];
|
|
|
|
cclass2 = GetUniCharCClass(ch2);
|
2005-05-06 03:38:05 +02:00
|
|
|
if ((cclass_prev == 0 || cclass2 > cclass_prev) &&
|
|
|
|
(ruc = compose(ch1, ch2))) {
|
2003-10-07 22:31:44 +02:00
|
|
|
ch1 = ruc;
|
|
|
|
} else {
|
2005-05-06 03:38:05 +02:00
|
|
|
if (cclass2 == 0) {
|
2003-10-08 20:27:48 +02:00
|
|
|
str32[comp_starter_pos] = ch1;
|
|
|
|
comp_starter_pos = comp_pos++;
|
|
|
|
ch1 = ch2;
|
2005-05-06 03:38:05 +02:00
|
|
|
cclass_prev = 0;
|
2003-10-08 20:27:48 +02:00
|
|
|
} else {
|
|
|
|
str32[comp_pos++] = ch2;
|
|
|
|
cclass_prev = cclass2;
|
|
|
|
}
|
2003-10-07 22:31:44 +02:00
|
|
|
}
|
|
|
|
}
|
2003-10-08 20:27:48 +02:00
|
|
|
str32[comp_starter_pos] = ch1;
|
|
|
|
str32pos = comp_pos;
|
2003-10-07 22:31:44 +02:00
|
|
|
|
2005-05-07 03:21:39 +02:00
|
|
|
last_ral = have_ral = have_l = 0;
|
|
|
|
info = GetUniCharInfo(str32[0]);
|
|
|
|
first_ral = info & D1Mask;
|
|
|
|
for (i = 0; i < str32pos; i++)
|
2003-10-07 22:31:44 +02:00
|
|
|
{
|
|
|
|
ruc = str32[i];
|
2003-10-08 20:27:48 +02:00
|
|
|
info = GetUniCharInfo(ruc);
|
2005-05-07 03:21:39 +02:00
|
|
|
if (info & prohibit) {
|
2003-10-08 20:27:48 +02:00
|
|
|
*rbuf = rstring;
|
|
|
|
driver_free(str32);
|
|
|
|
return 1;
|
|
|
|
}
|
2005-05-07 03:21:39 +02:00
|
|
|
last_ral = info & D1Mask;
|
|
|
|
have_ral = have_ral || last_ral;
|
2009-11-01 13:15:34 +01:00
|
|
|
have_l |= info & D2Mask;
|
2003-10-07 22:31:44 +02:00
|
|
|
ADD_UCHAR(ruc);
|
|
|
|
}
|
2005-05-07 03:21:39 +02:00
|
|
|
|
|
|
|
if (have_ral && (!first_ral || !last_ral || have_l)) {
|
|
|
|
*rbuf = rstring;
|
|
|
|
driver_free(str32);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2003-09-28 20:27:55 +02:00
|
|
|
rstring[0] = 1;
|
|
|
|
*rbuf = rstring;
|
2003-10-07 22:31:44 +02:00
|
|
|
driver_free(str32);
|
2003-09-26 20:55:01 +02:00
|
|
|
|
2003-10-06 22:12:11 +02:00
|
|
|
return pos;
|
2003-09-26 20:55:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ErlDrvEntry stringprep_driver_entry = {
|
2003-09-28 20:27:55 +02:00
|
|
|
NULL, /* F_PTR init, N/A */
|
|
|
|
stringprep_erl_start, /* L_PTR start, called when port is opened */
|
|
|
|
stringprep_erl_stop, /* F_PTR stop, called when port is closed */
|
|
|
|
NULL, /* F_PTR output, called when erlang has sent */
|
|
|
|
NULL, /* F_PTR ready_input, called when input descriptor ready */
|
|
|
|
NULL, /* F_PTR ready_output, called when output descriptor ready */
|
|
|
|
"stringprep_drv", /* char *driver_name, the argument to open_port */
|
|
|
|
NULL, /* F_PTR finish, called when unloaded */
|
|
|
|
NULL, /* handle */
|
|
|
|
stringprep_erl_control, /* F_PTR control, port_command callback */
|
|
|
|
NULL, /* F_PTR timeout, reserved */
|
|
|
|
NULL /* F_PTR outputv, reserved */
|
2003-09-26 20:55:01 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
DRIVER_INIT(stringprep_erl) /* must match name in driver_entry */
|
|
|
|
{
|
2004-04-15 21:55:38 +02:00
|
|
|
return &stringprep_driver_entry;
|
2003-09-26 20:55:01 +02:00
|
|
|
}
|
|
|
|
|