Initial revision
This commit is contained in:
parent
713e8e60dc
commit
0909ac418c
66
libmpools/Makefile
Normal file
66
libmpools/Makefile
Normal file
@ -0,0 +1,66 @@
|
||||
#
|
||||
# libmpools Makefile
|
||||
#
|
||||
# $Header$
|
||||
#
|
||||
|
||||
# Make Rules:
|
||||
# ===========
|
||||
#
|
||||
.c.o:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
|
||||
|
||||
|
||||
# Compiler Flags:
|
||||
# ===============
|
||||
#
|
||||
CFLAGS = -Wall
|
||||
CPPFLAGS=
|
||||
|
||||
|
||||
#
|
||||
# Labels:
|
||||
# =======
|
||||
#
|
||||
SRCS = mpools.c
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
MANFILES= $(SRCS:.c=.3)
|
||||
|
||||
|
||||
#
|
||||
# Targets
|
||||
#
|
||||
.PHONY: all man clean realclean distclean depend
|
||||
|
||||
all: libmpools.a
|
||||
|
||||
man: mp_malloc.3
|
||||
|
||||
clean:
|
||||
rm -f libmpools.a *.o *.3 *.core
|
||||
|
||||
realclean: clean
|
||||
rm -rf man3
|
||||
|
||||
distclean: realclean
|
||||
|
||||
depend:
|
||||
makedepend -Y /usr/include $(SRCS)
|
||||
@rm -f Makefile.bak
|
||||
|
||||
mp_malloc.3: mpools.c
|
||||
c2man -impools.h -g mpools.c;
|
||||
|
||||
|
||||
#
|
||||
# Actions
|
||||
#
|
||||
libmpools.a: $(OBJS)
|
||||
rm -f $@
|
||||
$(AR) cr $@ $(OBJS)
|
||||
$(RANLIB) $@
|
||||
|
||||
|
||||
#
|
||||
# Dependencies
|
||||
#
|
||||
339
libmpools/mpools.c
Normal file
339
libmpools/mpools.c
Normal file
@ -0,0 +1,339 @@
|
||||
/*
|
||||
* $Source$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* Copyright (C) 1997 by CyberSolutions GmbH.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include "../liblists/lists.h"
|
||||
#include "mpools.h"
|
||||
|
||||
static List mpools_list = NULL;
|
||||
|
||||
/********** Internal routines **********/
|
||||
|
||||
static int
|
||||
GetMemoryPool(const char * pool_name,
|
||||
Node * node_ptr,
|
||||
struct mp_list_entry ** mpool_entry_ptr)
|
||||
{
|
||||
Node node;
|
||||
|
||||
/* Sanity checks. */
|
||||
|
||||
assert(mpools_list != NULL);
|
||||
assert(pool_name != NULL);
|
||||
if (pool_name == NULL)
|
||||
return -1;
|
||||
|
||||
/* Find the pool of the right name. */
|
||||
|
||||
node = FindNodeByKey(mpools_list, (const void *) pool_name);
|
||||
if (node != NULL) {
|
||||
if (node_ptr != NULL)
|
||||
*node_ptr = node;
|
||||
if (mpool_entry_ptr != NULL)
|
||||
*mpool_entry_ptr = (struct mp_list_entry *) getNodeData(node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* No entry available. So we create one. */
|
||||
|
||||
pool_name = strdup(pool_name);
|
||||
if (pool_name == NULL)
|
||||
return -1;
|
||||
|
||||
node = AppendNode(mpools_list, (const void *) pool_name, NULL);
|
||||
if (node == NULL) {
|
||||
free((char *) pool_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Return result to caller. */
|
||||
|
||||
if (node_ptr != NULL)
|
||||
*node_ptr = node;
|
||||
if (mpool_entry_ptr != NULL)
|
||||
*mpool_entry_ptr = (struct mp_list_entry *) getNodeData(node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/********** User interface **********/
|
||||
|
||||
/* mp_malloc() allocates a block of memory using malloc(3), adds the
|
||||
pointer and the size of the block to the memory pool as identified
|
||||
by the 'pool_name' argument and returns the address of the memory
|
||||
block to the caller.
|
||||
|
||||
Memory pools are identified by a byte string, terminated with a
|
||||
zero ('\\0') byte. The names are compared using strcmp(3), which is
|
||||
case-sensitive.
|
||||
|
||||
RETURNS: If successfull, mp_malloc() returns a pointer to the newly
|
||||
allocated memory block. In case of failure, NULL is returned
|
||||
instead.
|
||||
*/
|
||||
|
||||
void *
|
||||
mp_malloc(const char * pool_name /* ID-String of the memory pool. */,
|
||||
size_t block_size /* Size of the requested memory block. */
|
||||
)
|
||||
{
|
||||
struct mp_list_entry * mpool;
|
||||
Node node;
|
||||
void * block;
|
||||
|
||||
/* Sanity checks. */
|
||||
|
||||
assert(pool_name != NULL);
|
||||
if (pool_name == NULL || block_size <= 0)
|
||||
return NULL;
|
||||
|
||||
/* Init the internal list structure, if it isn't already. */
|
||||
|
||||
if (mpools_list == NULL) {
|
||||
mpools_list = InitList((int (*)(const void *, const void *)) strcmp);
|
||||
if (mpools_list == NULL)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get the pool structure. */
|
||||
|
||||
if (GetMemoryPool(pool_name, &node, NULL) != 0)
|
||||
return NULL;
|
||||
|
||||
/* Allocate the memory. */
|
||||
|
||||
mpool = malloc(sizeof(struct mp_list_entry));
|
||||
if (mpool == NULL)
|
||||
return NULL;
|
||||
|
||||
block = malloc(block_size);
|
||||
if (block == NULL) {
|
||||
free(mpool);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Init the mpool structure. */
|
||||
|
||||
mpool->next = NULL;
|
||||
mpool->block = block;
|
||||
mpool->size = block_size;
|
||||
|
||||
/* Now append the structure to our list. */
|
||||
|
||||
if (getNodeData(node) != NULL)
|
||||
mpool->next = (struct mp_list_entry *) getNodeData(node);
|
||||
setNodeData(node, (const void *) mpool);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
/* mp_free() will return the previously allocated memory block to the
|
||||
system and remove the entry from the memory pool. The memory block
|
||||
has to be previously allocated by mp_malloc(), or mp_free() won't
|
||||
do anything.
|
||||
|
||||
It is safe to call mp_free() several times for the same memory
|
||||
block, or for an invalid memory block, that hasn't been allocated
|
||||
by mp_malloc(). mp_free will detect this and return without doing
|
||||
anything.
|
||||
*/
|
||||
|
||||
void
|
||||
mp_free(const char * pool_name /* ID-String of the memory pool. */,
|
||||
void * block /* Pointer to a memory block
|
||||
previously allocated by mp_malloc(). */
|
||||
)
|
||||
{
|
||||
struct mp_list_entry * mpool,
|
||||
* prev_mpool;
|
||||
Node node;
|
||||
|
||||
/* Sanity checks. */
|
||||
|
||||
assert(pool_name != NULL);
|
||||
if (pool_name == NULL)
|
||||
return;
|
||||
|
||||
/* Init the internal list structure, if it isn't already. */
|
||||
|
||||
if (mpools_list == NULL) {
|
||||
mpools_list = InitList(NULL);
|
||||
if (mpools_list == NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get the pool structure. */
|
||||
|
||||
if (GetMemoryPool(pool_name, &node, &mpool) != 0)
|
||||
return;
|
||||
|
||||
/* Find the block we should free. */
|
||||
|
||||
for (prev_mpool = NULL;
|
||||
mpool != NULL && mpool->block != block;
|
||||
prev_mpool = mpool, mpool = mpool->next)
|
||||
;
|
||||
|
||||
if (mpool == NULL) { /* block not found */
|
||||
printf("Warning\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove the node from the linked list. */
|
||||
|
||||
if (prev_mpool == NULL)
|
||||
setNodeData(node, mpool->next);
|
||||
else
|
||||
prev_mpool->next = mpool->next;
|
||||
|
||||
/* And free it plus our own structure stuff. */
|
||||
|
||||
free(mpool->block);
|
||||
free(mpool);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Remove the provided memory block from the memory pool, without
|
||||
freeing the memory itself.
|
||||
|
||||
It is safe to call mp_remove_block_from_pool() with invalid data.
|
||||
*/
|
||||
|
||||
void
|
||||
mp_remove_block_from_pool(const char * pool_name /* ID-String of the memory pool. */,
|
||||
void * block /* Pointer to a memory block
|
||||
previously allocated by mp_malloc(). */
|
||||
)
|
||||
{
|
||||
struct mp_list_entry * mpool,
|
||||
* prev_mpool;
|
||||
Node node;
|
||||
|
||||
/* Sanity checks. */
|
||||
|
||||
assert(pool_name != NULL);
|
||||
assert(block != NULL);
|
||||
if (!pool_name || !block)
|
||||
return;
|
||||
|
||||
/* Init the internal list structure, if it isn't already. */
|
||||
|
||||
if (mpools_list == NULL) {
|
||||
mpools_list = InitList(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get the pool structure. */
|
||||
|
||||
if (GetMemoryPool(pool_name, &node, &mpool) != 0)
|
||||
return;
|
||||
|
||||
/* Find the block we should free. */
|
||||
|
||||
for (prev_mpool = NULL;
|
||||
mpool != NULL && mpool->block != block;
|
||||
prev_mpool = mpool, mpool = mpool->next)
|
||||
;
|
||||
|
||||
if (mpool == NULL) /* block not found */
|
||||
return;
|
||||
|
||||
/* Remove the node from the linked list. */
|
||||
|
||||
if (prev_mpool == NULL)
|
||||
setNodeData(node, mpool->next);
|
||||
else
|
||||
prev_mpool->next = mpool->next;
|
||||
|
||||
/* Free the structure stuff. */
|
||||
|
||||
free(mpool);
|
||||
}
|
||||
|
||||
/* This routine will return all allocated memory blocks contained in
|
||||
pool 'pool_name' to the system.
|
||||
*/
|
||||
|
||||
void
|
||||
mp_free_memory_pool(const char * pool_name /* ID-String of the memory pool. */
|
||||
)
|
||||
{
|
||||
struct mp_list_entry * mpool_entry,
|
||||
* next_mpool_entry;
|
||||
Node node;
|
||||
|
||||
/* Sanity checks. */
|
||||
|
||||
assert(pool_name != NULL);
|
||||
if (pool_name == NULL)
|
||||
return;
|
||||
|
||||
if (mpools_list == NULL)
|
||||
return;
|
||||
|
||||
/* Find our memory pool. */
|
||||
|
||||
node = FindNodeByKey(mpools_list, pool_name);
|
||||
if (node == NULL)
|
||||
return;
|
||||
mpool_entry = (struct mp_list_entry *) getNodeData(node);
|
||||
|
||||
/* And now we move the pool completely. */
|
||||
|
||||
RemoveNode(node);
|
||||
free((void *) getNodeKey(node)); /* kill the pool name buffer */
|
||||
FreeNode(node);
|
||||
|
||||
for ( ; mpool_entry != NULL; mpool_entry = next_mpool_entry) {
|
||||
next_mpool_entry = mpool_entry->next;
|
||||
free(mpool_entry->block);
|
||||
free(mpool_entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* This routine is experimental and for debugging purposes only. In
|
||||
the current version, it will dump the contents of pool 'pool_name'
|
||||
to the screen, using printf(3).
|
||||
*/
|
||||
|
||||
void
|
||||
mp_dump_memory_pool(const char * pool_name /* ID-String of the memory pool. */
|
||||
)
|
||||
{
|
||||
struct mp_list_entry * mpool_entry;
|
||||
unsigned int total;
|
||||
|
||||
/* Sanity checks. */
|
||||
|
||||
assert(pool_name != NULL);
|
||||
if (pool_name == NULL)
|
||||
return;
|
||||
|
||||
/* Dump the specified pool. */
|
||||
|
||||
if (GetMemoryPool(pool_name, NULL, &mpool_entry) != 0)
|
||||
return;
|
||||
|
||||
if (mpool_entry == NULL) {
|
||||
printf("mpool \"%s\" is empty.\n", pool_name);
|
||||
return;
|
||||
}
|
||||
|
||||
for (total = 0; mpool_entry != NULL; mpool_entry = mpool_entry->next) {
|
||||
printf("\"%s\": %d byte block at $%08x.\n", pool_name, mpool_entry->size,
|
||||
(unsigned int) mpool_entry->block);
|
||||
total += mpool_entry->size;
|
||||
}
|
||||
printf("Total size of mpool \"%s\" is %u byte.\n", pool_name, total);
|
||||
}
|
||||
30
libmpools/mpools.h
Normal file
30
libmpools/mpools.h
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* $Source$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* Copyright (C) 1996,97 by CyberSolutions GmbH.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __LIB_MPOOLS_H__
|
||||
#define __LIB_MPOOLS_H__ 1
|
||||
|
||||
#include <stdlib.h>
|
||||
#ifdef DEBUG_DMALLOC
|
||||
# include <dmalloc.h>
|
||||
#endif
|
||||
|
||||
struct mp_list_entry {
|
||||
struct mp_list_entry * next;
|
||||
void * block;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
void * mp_malloc(const char *, size_t);
|
||||
void mp_free(const char * pool_name, void *);
|
||||
void mp_free_memory_pool(const char *);
|
||||
void mp_dump_memory_pool(const char *);
|
||||
void mp_remove_block_from_pool(const char *, void *);
|
||||
|
||||
#endif /* !__LIB_MPOOLS_H__ */
|
||||
97
librfc822/Makefile
Normal file
97
librfc822/Makefile
Normal file
@ -0,0 +1,97 @@
|
||||
#
|
||||
# RFC Parse Library
|
||||
#
|
||||
# $Header$
|
||||
#
|
||||
|
||||
# Make Rules:
|
||||
# ===========
|
||||
#
|
||||
.c.o:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
|
||||
|
||||
|
||||
# Compiler flags:
|
||||
# ===============
|
||||
#
|
||||
CFLAGS = -Wall
|
||||
CPPFLAGS=
|
||||
|
||||
# Linker flags:
|
||||
# =============
|
||||
#
|
||||
LDFLAGS =
|
||||
LIBS = ../libmpools/libmpools.a ../liblists/liblists.a
|
||||
|
||||
|
||||
#
|
||||
# Labels:
|
||||
# =======
|
||||
#
|
||||
SRCS = address.c address_scan.c decomment.c address_sep.c
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
|
||||
|
||||
#
|
||||
# Targets
|
||||
#
|
||||
.PHONY: all man clean realclean depend
|
||||
|
||||
all: librfc822.a
|
||||
|
||||
man: rfc822_decomment.3 rfc822_parse_address.3 rfc822_address_sep.3
|
||||
|
||||
test: test.o librfc822.a rfc822.h $(LIBS)
|
||||
$(CC) test.o librfc822.a -o $@ $(LDFLAGS) $(LIBS)
|
||||
|
||||
clean:
|
||||
rm -f librfc822.a test *.o *.3 *.core *.bak
|
||||
|
||||
realclean: clean
|
||||
rm -f address.c address_scan.c address_scan.h
|
||||
|
||||
depend:
|
||||
makedepend -Y /usr/include $(SRCS)
|
||||
@rm -f Makefile.bak
|
||||
|
||||
|
||||
#
|
||||
# Actions:
|
||||
#=========
|
||||
#
|
||||
librfc822.a: $(OBJS)
|
||||
$(AR) cr $@ $(OBJS)
|
||||
$(RANLIB) $@
|
||||
|
||||
address.c address_scan.h: address.y rfc822.h parse_address.c
|
||||
$(YACC) -d -p "rfc822_" address.y
|
||||
mv y.tab.c address.c
|
||||
mv y.tab.h address_scan.h
|
||||
|
||||
address_scan.c: address_scan.l rfc822.h
|
||||
$(LEX) -Prfc822_ address_scan.l
|
||||
mv lex.rfc822_.c $@
|
||||
|
||||
rfc822_decomment.3: decomment.c
|
||||
c2man -irfc822.h decomment.c
|
||||
|
||||
rfc822_parse_address.3: parse_address.c
|
||||
c2man -irfc822.h parse_address.c
|
||||
|
||||
rfc822_address_sep.3: address_sep.c
|
||||
c2man -irfc822.h address_sep.c
|
||||
|
||||
../libmpools/libmpools.a:
|
||||
(cd ../libmpools;$(MAKE))
|
||||
|
||||
../liblists/liblists.a:
|
||||
(cd ../liblists;$(MAKE))
|
||||
|
||||
#
|
||||
# Dependencies
|
||||
#
|
||||
|
||||
address.o: address.c address_scan.h address_scan.c parse_address.c
|
||||
address_scan.c: address_scan.l
|
||||
decomment.c: rfc822.h
|
||||
address_sep.c: rfc822.h
|
||||
1114
librfc822/address.c
Normal file
1114
librfc822/address.c
Normal file
File diff suppressed because it is too large
Load Diff
180
librfc822/address.y
Normal file
180
librfc822/address.y
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* $Source$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* Copyright (C) 1997 by CyberSolutions GmbH.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
%{
|
||||
/* Definitions we need in the parser. */
|
||||
|
||||
#include <errno.h>
|
||||
#ifdef DEBUG_DMALLOC
|
||||
# include <dmalloc.h>
|
||||
#endif
|
||||
|
||||
#include "../libmpools/mpools.h"
|
||||
#define yytext rfc822_text
|
||||
#define yyley rfc822_lex
|
||||
|
||||
/* Variables in our lexer. */
|
||||
|
||||
extern char * rfc822_address_buffer;
|
||||
extern int rfc822_address_buffer_pos;
|
||||
extern char * yytext;
|
||||
|
||||
/* Our static/global variables. */
|
||||
|
||||
static int no_memory_flag;
|
||||
static char * poolname,
|
||||
* result_hostpart,
|
||||
* result_localpart,
|
||||
* result_address;
|
||||
|
||||
/* Prototypes for internal functions. */
|
||||
|
||||
static int yyparse(void);
|
||||
static int yyerror(char *);
|
||||
static char * pool_strdup(char *);
|
||||
static char * pool_strjoin(char *, char *);
|
||||
int yylex(void);
|
||||
|
||||
/* These macros call the routines we define later in a fail safe
|
||||
way. */
|
||||
|
||||
#define str_dup(target,a) { \
|
||||
target = (YYSTYPE) pool_strdup((char *) a); \
|
||||
if (target == NULL) { \
|
||||
no_memory_flag++; \
|
||||
YYABORT; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define str_join(target,a,b) { \
|
||||
target = (YYSTYPE) pool_strjoin((char *) a, (char *) b); \
|
||||
if (target == NULL) { \
|
||||
no_memory_flag++; \
|
||||
YYABORT; \
|
||||
} \
|
||||
}
|
||||
|
||||
%}
|
||||
%token TOK_ATOM TOK_ILLEGAL
|
||||
%left ':'
|
||||
%left '@'
|
||||
%%
|
||||
|
||||
address: local {
|
||||
result_localpart = (char *)$1;
|
||||
str_dup($$,$1);
|
||||
result_address = (char *)$$;
|
||||
}
|
||||
| local at domain {
|
||||
result_localpart = (char *)$1;
|
||||
result_hostpart = (char *)$3;
|
||||
str_join($$,$1,$2); str_join($$,$$,$3);
|
||||
result_address = (char *)$$;
|
||||
}
|
||||
| at domain colon sourceroutings local {
|
||||
result_hostpart = (char *)$2;
|
||||
str_join($$,$4,$5);
|
||||
result_localpart = (char *)$$;
|
||||
str_join($$,$3,$$); str_join($$,$2,$$);
|
||||
str_join($$,$1,$$);
|
||||
result_address = (char *)$$;
|
||||
}
|
||||
| at domain colon sourceroutings local at domain {
|
||||
result_hostpart = (char *)$2;
|
||||
str_join($$,$4,$5); str_join($$,$$,$6);
|
||||
str_join($$,$$,$7);
|
||||
result_localpart = (char *)$$;
|
||||
str_join($$,$3,$$); str_join($$,$2,$$);
|
||||
str_join($$,$1,$$);
|
||||
result_address = (char *)$$;
|
||||
}
|
||||
;
|
||||
|
||||
sourceroutings: /* empty */ { $$ = (YYSTYPE) NULL; }
|
||||
| at domain colon sourceroutings {
|
||||
str_join($$,$1,$2); str_join($$,$$,$3);
|
||||
str_join($$,$$,$4);
|
||||
}
|
||||
|
||||
local: atom { $$ = $1; }
|
||||
| atom local { str_join($$,$1,$2); }
|
||||
| dot { $$ = $1; }
|
||||
| dot local { str_join($$,$1,$2); }
|
||||
;
|
||||
|
||||
domain: atom { $$ = $1; }
|
||||
| atom dot domain { str_join($$,$2,$3); str_join($$,$1,$$); }
|
||||
;
|
||||
|
||||
atom: TOK_ATOM { str_dup($$,yytext); }
|
||||
dot: '.' { $$ = (YYSTYPE) "."; }
|
||||
at: '@' { $$ = (YYSTYPE) "@"; }
|
||||
colon: ':' { $$ = (YYSTYPE) ":"; }
|
||||
|
||||
%%
|
||||
/***** internal routines *****/
|
||||
|
||||
static int
|
||||
yyerror(char * string)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Do the same as strdup(3) but use the memory pool to allocate the
|
||||
memory, so that we don't lose memory. */
|
||||
|
||||
static char *
|
||||
pool_strdup(char * string)
|
||||
{
|
||||
char * p;
|
||||
|
||||
if (string == NULL)
|
||||
return NULL;
|
||||
|
||||
p = mp_malloc(poolname, strlen(string) + 1);
|
||||
if (p == NULL)
|
||||
return NULL;
|
||||
|
||||
strcpy(p, string);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Allocate a new buffer (using the memory pool) and join the strings
|
||||
'a' and 'b' into it. */
|
||||
|
||||
static char *
|
||||
pool_strjoin(char * a, char * b)
|
||||
{
|
||||
char * p;
|
||||
int length = 0;
|
||||
|
||||
if (a != NULL)
|
||||
length += strlen(a);
|
||||
if (b != NULL)
|
||||
length += strlen(b);
|
||||
if (length == 0)
|
||||
return NULL;
|
||||
|
||||
p = mp_malloc(poolname, length + 2);
|
||||
if (p == NULL)
|
||||
return NULL;
|
||||
else
|
||||
*p = '\0';
|
||||
|
||||
if (a)
|
||||
strcpy(p, a);
|
||||
if (b)
|
||||
strcpy(&(p[strlen(p)]), b);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/****** public routines ******/
|
||||
|
||||
#include "parse_address.c"
|
||||
1660
librfc822/address_scan.c
Normal file
1660
librfc822/address_scan.c
Normal file
File diff suppressed because it is too large
Load Diff
8
librfc822/address_scan.h
Normal file
8
librfc822/address_scan.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef YYSTYPE
|
||||
#define YYSTYPE int
|
||||
#endif
|
||||
#define TOK_ATOM 257
|
||||
#define TOK_ILLEGAL 258
|
||||
|
||||
|
||||
extern YYSTYPE rfc822_lval;
|
||||
56
librfc822/address_scan.l
Normal file
56
librfc822/address_scan.l
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* $Source$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* Copyright (C) 1997 by CyberSolutions GmbH.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
%{
|
||||
#ifdef DEBUG_DMALLOC
|
||||
# include <dmalloc.h>
|
||||
#endif
|
||||
|
||||
#include "rfc822.h"
|
||||
#include "address_scan.h"
|
||||
#define yylval rfc822_lval
|
||||
|
||||
extern int yylval;
|
||||
|
||||
char * rfc822_address_buffer;
|
||||
int rfc822_address_buffer_pos;
|
||||
|
||||
#define YY_INPUT(buf,result,max_size) { \
|
||||
buf[0] = rfc822_address_buffer[rfc822_address_buffer_pos++]; \
|
||||
result = ((buf[0] != '\0') ? 1 : YY_NULL); \
|
||||
}
|
||||
%}
|
||||
%x quoted escaped escaped2
|
||||
%%
|
||||
([^@\.\(\) \t<>\":,\\]*\\)/(.[@\.:]) { BEGIN(escaped2); yymore(); }
|
||||
([^@\.\(\) \t<>\":,\\]*\\)/(.) { BEGIN(escaped2); yymore(); }
|
||||
([^@\.\(\) \t<>\":,\\]*\\)/(.[^@\.:]) { BEGIN(escaped); yymore(); }
|
||||
([^@\.\(\) \t<>\":,\\]*\\) { return TOK_ILLEGAL; }
|
||||
[^@\.\(\) \t<>\":,\\]+ { return TOK_ATOM; }
|
||||
|
||||
[ \t]+ /* eat up whitespace */
|
||||
[@\.:] { yylval = yytext[0]; return yylval; }
|
||||
[<>\(\),:] { return TOK_ILLEGAL; }
|
||||
|
||||
<escaped>. { BEGIN(INITIAL); yymore(); }
|
||||
<escaped2>. { BEGIN(INITIAL); return TOK_ATOM; }
|
||||
|
||||
\" { BEGIN(quoted); yymore(); }
|
||||
<quoted>[^\"\\]+ { yymore(); }
|
||||
<quoted>\\\" { yymore(); }
|
||||
<quoted>\" { BEGIN(INITIAL); return TOK_ATOM; }
|
||||
<quoted><<EOF>> { BEGIN(INITIAL); return TOK_ILLEGAL; }
|
||||
%%
|
||||
/* Internal routines. */
|
||||
|
||||
int
|
||||
yywrap(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
152
librfc822/address_sep.c
Normal file
152
librfc822/address_sep.c
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* $Source$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* Copyright (C) 1997 by CyberSolutions GmbH.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#ifdef DEBUG_DMALLOC
|
||||
# include <dmalloc.h>
|
||||
#endif
|
||||
|
||||
#include "rfc822.h"
|
||||
|
||||
static char *
|
||||
read_until_next_quote(char * p)
|
||||
{
|
||||
while (*p) {
|
||||
if (*p == '"') {
|
||||
p++;
|
||||
break;
|
||||
}
|
||||
if (*p == '\\' && p[1] != '\0') {
|
||||
p += 2;
|
||||
continue;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static char *
|
||||
read_until_close_bracket(char * p)
|
||||
{
|
||||
while (*p) {
|
||||
if (*p == ')') {
|
||||
p++;
|
||||
break;
|
||||
}
|
||||
if (*p == '(') {
|
||||
p = read_until_close_bracket(p+1);
|
||||
continue;
|
||||
}
|
||||
else if (*p == '\\' && p[1] != '\0') {
|
||||
p += 2;
|
||||
continue;
|
||||
}
|
||||
else if (*p == '"') {
|
||||
p = read_until_next_quote(p+1);
|
||||
continue;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static int
|
||||
is_source_routing(char * p)
|
||||
{
|
||||
while (*p) {
|
||||
if (*p == '(')
|
||||
p = read_until_close_bracket(p+1);
|
||||
|
||||
else if (*p == ' ' || *p == '\t')
|
||||
p++;
|
||||
|
||||
else if (*p == '@' || *p == '<')
|
||||
return 1;
|
||||
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Split an RFC822 address line.
|
||||
|
||||
This routine breaks an address line, as specified by RFC822, up
|
||||
into separate addresses. The used delimiter is the comma (",").
|
||||
|
||||
The usage of the routine is very similar to strsep(3). More text to
|
||||
come.
|
||||
|
||||
AUTHOR: Peter Simons <simons@rhein.de>
|
||||
|
||||
*/
|
||||
|
||||
char *
|
||||
rfc822_address_sep(struct rfc822_address_sep_state * state)
|
||||
{
|
||||
char * p,
|
||||
* old;
|
||||
int allow_groups;
|
||||
|
||||
/* Sanity checks. */
|
||||
|
||||
assert(state != NULL);
|
||||
if (!state) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (*(state->address_line) == '\0')
|
||||
return NULL;
|
||||
|
||||
old = p = state->address_line;
|
||||
allow_groups = !is_source_routing(p);
|
||||
|
||||
while(*p) {
|
||||
if (*p == ',') {
|
||||
*p = '\0';
|
||||
state->address_line = p+1;
|
||||
return old;
|
||||
}
|
||||
else if (*p == ':' && allow_groups) {
|
||||
old = p+1;
|
||||
state->group_nest++;
|
||||
allow_groups = !is_source_routing(p+1);
|
||||
}
|
||||
else if (*p == ';') {
|
||||
|
||||
/* If we are inside an address group, the ';' character is
|
||||
interpreted like a comma. */
|
||||
|
||||
if (state->group_nest > 0) {
|
||||
state->group_nest--;
|
||||
*p = ',';
|
||||
continue;
|
||||
}
|
||||
else
|
||||
/* do nothing */;
|
||||
}
|
||||
else if (*p == '(') {
|
||||
p = read_until_close_bracket(p+1);
|
||||
continue;
|
||||
}
|
||||
else if (*p == '\\' && p[1] != '\0')
|
||||
p++;
|
||||
else if (*p == '"') {
|
||||
p = read_until_next_quote(p+1);
|
||||
continue;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
state->address_line = p;
|
||||
return old;
|
||||
}
|
||||
217
librfc822/decomment.c
Normal file
217
librfc822/decomment.c
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* $Source$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* Copyright (C) 1997 by CyberSolutions GmbH.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#ifdef DEBUG_DMALLOC
|
||||
# include <dmalloc.h>
|
||||
#endif
|
||||
|
||||
#include "rfc822.h"
|
||||
|
||||
static const char *
|
||||
read_until_next_quote(const char * p)
|
||||
{
|
||||
while (*p) {
|
||||
if (*p == '"') {
|
||||
break;
|
||||
}
|
||||
if (*p == '\\' && p[1] != '\0') {
|
||||
p += 2;
|
||||
continue;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static const char *
|
||||
read_until_close_bracket(const char * p)
|
||||
{
|
||||
while (*p) {
|
||||
if (*p == ')') {
|
||||
break;
|
||||
}
|
||||
if (*p == '(') {
|
||||
p = read_until_close_bracket(p+1);
|
||||
if (*p == '\0')
|
||||
break;
|
||||
else {
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (*p == '\\' && p[1] != '\0') {
|
||||
p += 2;
|
||||
continue;
|
||||
}
|
||||
else if (*p == '"') {
|
||||
p = read_until_next_quote(p+1);
|
||||
if (*p == '\0')
|
||||
break;
|
||||
else {
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/* remove all comments from an rfc822 address
|
||||
|
||||
This routine takes as argument an address string conforming to the
|
||||
RFC822 syntax and removes all comments from it, returning the
|
||||
result in the pointer specified as second argument.
|
||||
|
||||
This is very useful if you want to parse the address with a context
|
||||
free grammer parser, like yacc, because recognizing the various
|
||||
forms of commenting with a BNF grammar is very complicated.
|
||||
|
||||
RETURNS: The routine returns an integer describing whether the
|
||||
decommenting process succeeded or not. In case of an error, the
|
||||
return code described the reason for failure.
|
||||
|
||||
RFC822_OK: Success.
|
||||
|
||||
RFC822_FATAL_ERROR: A fatal system error occured, like malloc()
|
||||
failed. The global errno variable will be set accordingly.
|
||||
|
||||
RFC822_UNCLOSED_COMMENT: A "(comment)"-like expression was not
|
||||
closed properly.
|
||||
|
||||
RFC822_UNMATCHED_CLOSE_BRACKET: A close bracket (")") was
|
||||
encountered outside the comment context.
|
||||
|
||||
RFC822_UNCLOSED_QUOTE: A quoted `"string"'-like expression was not
|
||||
closed properly.
|
||||
|
||||
RFC822_UNCLOSED_ANGEL_BRACKET: An "<address>"-like expression was
|
||||
not closed properly.
|
||||
|
||||
RFC822_NESTED_ANGEL_BRACKET: An open angel bracket ("<") was
|
||||
encountered inside the "<address>" context.
|
||||
|
||||
RFC822_UNMATCHED_CLOSE_ANGEL_BRACKET: A close angel bracket (">") was
|
||||
encountered outside the "<address>" context.
|
||||
|
||||
RFC822_SYNTAX_ERROR: The address does not follow the rfc822 syntax.
|
||||
|
||||
NOTE: The returned by rfc822_decomment() has been allocated with
|
||||
malloc(3) and should be freed when not needed anymore.
|
||||
|
||||
AUTHOR: Peter Simons <simons@rhein.de>
|
||||
|
||||
*/
|
||||
|
||||
int
|
||||
rfc822_decomment(const char * source /* String containing a formatted rfc822 address. */,
|
||||
char ** destination /* Location where to store the parsed string. */)
|
||||
{
|
||||
char * buffer,
|
||||
* address_start,
|
||||
* p;
|
||||
const char * s;
|
||||
int in_quote,
|
||||
angel_bracket_count;
|
||||
|
||||
|
||||
/* Sanity checks. */
|
||||
|
||||
assert(source != NULL);
|
||||
if (!source || *source == '\0') {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocate buffer. */
|
||||
|
||||
buffer = malloc(strlen(source)+1);
|
||||
if (buffer == NULL) {
|
||||
errno = ENOMEM;
|
||||
return RFC822_FATAL_ERROR;
|
||||
}
|
||||
|
||||
/* Let's parse it. */
|
||||
|
||||
address_start = p = buffer;
|
||||
s = source;
|
||||
in_quote = 0;
|
||||
angel_bracket_count = 0;
|
||||
|
||||
while(*s) {
|
||||
|
||||
if (*s == '"') {
|
||||
in_quote = !in_quote;
|
||||
}
|
||||
else if (!in_quote && *s == '(') {
|
||||
s = read_until_close_bracket(s+1);
|
||||
if (*s == '\0') {
|
||||
free(buffer);
|
||||
return RFC822_UNCLOSED_COMMENT;
|
||||
}
|
||||
s++;
|
||||
*p++ = ' ';
|
||||
continue;
|
||||
}
|
||||
else if (!in_quote && *s == ')') {
|
||||
free(buffer);
|
||||
return RFC822_UNMATCHED_CLOSE_BRACKET;
|
||||
}
|
||||
else if (!in_quote && *s == '<') {
|
||||
if (angel_bracket_count > 0) {
|
||||
free(buffer);
|
||||
return RFC822_NESTED_ANGEL_BRACKET;
|
||||
}
|
||||
s++;
|
||||
p = address_start;
|
||||
*p = '\0';
|
||||
angel_bracket_count++;
|
||||
continue;
|
||||
}
|
||||
else if (!in_quote && *s == '>') {
|
||||
if (angel_bracket_count == 0) {
|
||||
free(buffer);
|
||||
return RFC822_UNMATCHED_CLOSE_ANGEL_BRACKET;
|
||||
}
|
||||
*p = '\0';
|
||||
angel_bracket_count--;
|
||||
break;
|
||||
}
|
||||
else if (!in_quote && *s == '\\') {
|
||||
if (s[1] != '\0') {
|
||||
*p++ = *s++;
|
||||
}
|
||||
else {
|
||||
free(buffer);
|
||||
return RFC822_SYNTAX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
*p++ = *s++;
|
||||
}
|
||||
*p = '\0';
|
||||
if (in_quote) {
|
||||
free(buffer);
|
||||
return RFC822_UNCLOSED_QUOTE;
|
||||
}
|
||||
|
||||
if (angel_bracket_count > 0) {
|
||||
free(buffer);
|
||||
return RFC822_UNCLOSED_ANGEL_BRACKET;
|
||||
}
|
||||
|
||||
|
||||
*destination = buffer;
|
||||
return RFC822_OK;
|
||||
}
|
||||
127
librfc822/parse_address.c
Normal file
127
librfc822/parse_address.c
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* $Source$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* Copyright (C) 1997 by CyberSolutions GmbH.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#ifdef DEBUG_DMALLOC
|
||||
# include <dmalloc.h>
|
||||
#endif
|
||||
|
||||
#include "rfc822.h"
|
||||
|
||||
/* parse an rfc822 address
|
||||
|
||||
This routine is absolutely magnificent. It understands the full
|
||||
variety of Internet addresses as specified by RFC822 and parses
|
||||
them. "Parsing" means that it will check the address to be
|
||||
syntactically corrent and then returns the two parts an address
|
||||
consists of: localpart@hostpart. This may not sound like being a
|
||||
big thing, but you're invited to write a parser yourself to get an
|
||||
idea. :-)
|
||||
|
||||
All kind of comments and delimeters are understood, due to
|
||||
sophisticated memory pooling hopefully no memory is leaking and the
|
||||
speed is absolutely untested.
|
||||
|
||||
RETURNS: If the parsing process was successful, the local- and
|
||||
hostpart of the address will be stored in the location specified by
|
||||
the parameters. It will also store the parsed, canonic address
|
||||
(without any blanks, comments, etc...) in the provided location. If
|
||||
the address is syntactically incorrect, or if parsing failed due to
|
||||
an error, NULL will be stored in all these locations. The hostpart
|
||||
may also be NULL, if the address is a local address, e.g.
|
||||
"username".
|
||||
|
||||
If either the "address", "localpart" or "hostpart" parameters are
|
||||
NULL, no result will be stored, what allows you to check an address
|
||||
for syntax errors.
|
||||
|
||||
If the "string" parameter is NULL, the routine will return with a
|
||||
RFC822_FATAL_ERROR and set the global errno variable to EINVAL.
|
||||
|
||||
RFC822_OK: Success.
|
||||
|
||||
RFC822_FATAL_ERROR: A fatal system error occured, like malloc()
|
||||
failed. The global errno variable will be set accordingly.
|
||||
|
||||
RFC822_UNCLOSED_COMMENT: A "(comment)"-like expression was not
|
||||
closed properly.
|
||||
|
||||
RFC822_UNMATCHED_CLOSE_BRACKET: A close bracket (")") was
|
||||
encountered outside the comment context.
|
||||
|
||||
RFC822_UNCLOSED_QUOTE: A quoted `"string"'-like expression was not
|
||||
closed properly.
|
||||
|
||||
RFC822_UNCLOSED_ANGEL_BRACKET: An "<address>"-like expression was
|
||||
not closed properly.
|
||||
|
||||
RFC822_NESTED_ANGEL_BRACKET: An open angel bracket ("<") was
|
||||
encountered inside the "<address>" context.
|
||||
|
||||
RFC822_UNMATCHED_CLOSE_ANGEL_BRACKET: A close angel bracket (">") was
|
||||
encountered outside the "<address>" context.
|
||||
|
||||
RFC822_SYNTAX_ERROR: The address does not follow the rfc822 syntax.
|
||||
|
||||
NOTE: All buffers returned by the routine are allocated by
|
||||
malloc(3) and should be freed by the user when they're not needed
|
||||
anymore.
|
||||
|
||||
AUTHOR: Peter Simons <simons@rhein.de>
|
||||
|
||||
*/
|
||||
|
||||
int
|
||||
rfc822_parse_address(const char * string /* String containing the address. */,
|
||||
char ** address /* Where to store the canonic address. */,
|
||||
char ** localpart /* Where to store the local part. */,
|
||||
char ** hostpart /* Where to store the host part. */)
|
||||
{
|
||||
int rc;
|
||||
|
||||
assert(string != NULL);
|
||||
if (!string) {
|
||||
errno = EINVAL;
|
||||
return RFC822_FATAL_ERROR;
|
||||
}
|
||||
|
||||
rc = rfc822_decomment(string, &rfc822_address_buffer);
|
||||
if (rc != RFC822_OK)
|
||||
return rc;
|
||||
else
|
||||
rfc822_address_buffer_pos = 0;
|
||||
poolname = rfc822_address_buffer;
|
||||
|
||||
no_memory_flag = 0;
|
||||
result_address = NULL;
|
||||
result_hostpart = NULL;
|
||||
result_localpart = NULL;
|
||||
|
||||
rc = yyparse();
|
||||
if (rc == RFC822_OK) {
|
||||
if (address && (*address = result_address))
|
||||
mp_remove_block_from_pool(poolname, *address);
|
||||
if (localpart && (*localpart = result_localpart))
|
||||
mp_remove_block_from_pool(poolname, *localpart);
|
||||
if (hostpart && (*hostpart = result_hostpart))
|
||||
mp_remove_block_from_pool(poolname, *hostpart);
|
||||
}
|
||||
else if (no_memory_flag != 0) {
|
||||
errno = ENOMEM;
|
||||
rc = RFC822_FATAL_ERROR;
|
||||
}
|
||||
else
|
||||
rc = RFC822_SYNTAX_ERROR;
|
||||
|
||||
mp_free_memory_pool(poolname);
|
||||
free(rfc822_address_buffer);
|
||||
|
||||
return rc;
|
||||
}
|
||||
35
librfc822/rfc822.h
Normal file
35
librfc822/rfc822.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* $Source$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* Copyright (C) 1997 by CyberSolutions GmbH.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __LIB_RFC822_H__
|
||||
#define __LIB_RFC822_H__ 1
|
||||
|
||||
struct rfc822_address_sep_state {
|
||||
char * address_line;
|
||||
unsigned int group_nest;
|
||||
};
|
||||
|
||||
enum {
|
||||
RFC822_FATAL_ERROR = -1,
|
||||
RFC822_OK,
|
||||
RFC822_UNCLOSED_COMMENT,
|
||||
RFC822_UNMATCHED_CLOSE_BRACKET,
|
||||
RFC822_UNCLOSED_QUOTE,
|
||||
RFC822_UNCLOSED_ANGEL_BRACKET,
|
||||
RFC822_NESTED_ANGEL_BRACKET,
|
||||
RFC822_UNMATCHED_CLOSE_ANGEL_BRACKET,
|
||||
RFC822_SYNTAX_ERROR
|
||||
};
|
||||
|
||||
int rfc822_decomment(const char *, char **);
|
||||
int rfc822_parse_address(const char *, char **, char **, char **);
|
||||
char * rfc822_address_sep(struct rfc822_address_sep_state *);
|
||||
|
||||
#endif /* !__LIB_RFC822_H__ */
|
||||
|
||||
55
librfc822/test.c
Normal file
55
librfc822/test.c
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* $Source$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* Copyright (C) 1997 by CyberSolutions GmbH.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#ifdef DEBUG_DMALLOC
|
||||
# include <dmalloc.h>
|
||||
#endif
|
||||
|
||||
#include "rfc822.h"
|
||||
#define safe_free(x) if (x) free(x)
|
||||
|
||||
int
|
||||
main(int argc, char ** argv)
|
||||
{
|
||||
struct rfc822_address_sep_state sep_state;
|
||||
char buffer[4096];
|
||||
char * p,
|
||||
* address,
|
||||
* local,
|
||||
* host;
|
||||
int rc;
|
||||
|
||||
while(gets(buffer)) {
|
||||
|
||||
printf("Read line: '%s'\n", buffer);
|
||||
sep_state.address_line = buffer;
|
||||
sep_state.group_nest = 0;
|
||||
while((p = rfc822_address_sep(&sep_state))) {
|
||||
|
||||
if (*p == '\0')
|
||||
continue;
|
||||
else
|
||||
printf("One part is: '%s'\n", p);
|
||||
|
||||
rc = rfc822_parse_address(p, &address, &local, &host);
|
||||
if (rc == RFC822_OK) {
|
||||
printf("Address: '%s'\nLocal: '%s'\nHost: '%s'\n", address, local, host);
|
||||
safe_free(address);
|
||||
safe_free(local);
|
||||
safe_free(host);
|
||||
}
|
||||
else
|
||||
printf("Syntax error: %d\n", rc);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
15
librfc822/testaddresslines
Normal file
15
librfc822/testaddresslines
Normal file
@ -0,0 +1,15 @@
|
||||
simons
|
||||
simons@rhein.de
|
||||
peter.simons@rhein.de
|
||||
|
||||
simons (Peter Simons)
|
||||
peter (peti) . simons
|
||||
......
|
||||
|
||||
peter(simons)...(hallo)@rhein.de (,,,), cd@phoenix."rhein,,(,)".de
|
||||
|
||||
@im.net:@gmd.de:simons@rhein.de
|
||||
|
||||
group: @rhein.de:simons, group2: @im.net:@rhein.de:simons@petium, cd@phoenix; abcd;
|
||||
|
||||
group: @rhein.("hallo<bla>")de:simons, group2: @im.net:@rhein.de:simons@petium <bla>, cd@phoenix; abcd;
|
||||
Loading…
Reference in New Issue
Block a user