Convert-YText-v0.1.2000755001750001750 011525753637 14660 5ustar00bremnerbremner000000000000Convert-YText-v0.1.2/SIGNATURE000644001750001750 302411525753637 16302 0ustar00bremnerbremner000000000000This file contains message digests of all files listed in MANIFEST, signed via the Module::Signature module, version 0.63. To verify the content in this distribution, first make sure you have Module::Signature installed, then type: % cpansign -v It will check each file's integrity, as well as the signature's validity. If "==> Signature verified OK! <==" is not displayed, the distribution may already have been compromised, and you should not run its Makefile.PL or Build.PL. -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 SHA1 5a9301fec583b8ea049492af3ac01f82ea6236ac Build.PL SHA1 759020f166016adeb60009dbe4af9252aab736bd Changes SHA1 16e7df19c4d89c14603adaf3cefb568c880f2825 MANIFEST SHA1 76adda32b4402abfe014be995b50ebe28610ab00 MANIFEST.SKIP SHA1 1d94d1de97449e2ac5773cee16fab7ec2476c28d META.yml SHA1 8e45e0d09484bed53bf5439d8f824193e9978d25 README SHA1 afe8218ef4c0a1fefd8c51d53bfaea53b39d9543 lib/Convert/YText.pm SHA1 3ec6171779122b0bdc69937c283be11b2a15dd89 t/00.signature.t SHA1 3c47635f0402e6d7d03b1cf8a00caf709b327ee5 t/10.roundtrip.t SHA1 3ea0653a1c0a77ed97786bca50c9dfcd7ef91f07 t/20.keep-titles.t SHA1 fb280ec75590962c7fab672acef00d5a462c4a5c t/corpus.pl SHA1 a640c0b6244a137ee592753ac2ae55f65702b8df t/titles.pl -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.10 (GNU/Linux) iJwEAQECAAYFAk1X158ACgkQTiiN/0Um85lbUQP/f6+Es3y7v9zOec0tHiizhoem nUmzWkB5yziTShuprcTOFiTgkV3PCthZFsF9KGmJon8m7A0BWPAad7M6P9oCxh8p bhKyvsGrOHVOYtHHv3MaR5kQb+tSMSQQhF815YChD0TH5QKZOg3NISZ8CBMB8YXx EaWIQGMxgK8bBvV8Mt0= =PAzT -----END PGP SIGNATURE----- Convert-YText-v0.1.2/Build.PL000444001750001750 70511525753637 16273 0ustar00bremnerbremner000000000000use strict; use warnings; use Module::Build; my $build = Module::Build->new ( module_name => 'Convert::YText', dist_author => [ 'David Bremner '], (sign => 1), requires => { 'Test::More'=>0, }, license => 'unknown', create_readme => 0, meta_merge => { resources => { homepage => 'http://pivot.cs.unb.ca/git/?p=convert-ytext.git;a=summary' } } ); $build->create_build_script; Convert-YText-v0.1.2/README000444001750001750 246711525753637 15706 0ustar00bremnerbremner000000000000NAME Convert::YText - Quotes strings suitably for rfc2822 local part Convert::YText converts strings to and from "YText", a format inspired by xtext defined in RFC1894, the MIME base64 and quoted-printable types (RFC 1394). The main goal is encode a UTF8 string into something safe for use as the local part in an internet email address (RFC2822). By default spaces are replaced with "+", "/" with "~", the characters "A-Za-z0-9_.-" encode as themselves, and everything else is written "=USTR=" where USTR is the base64 (using "A-Za-z0-9_." as digits) encoding of the unicode character code. The encoding is configurable; perldoc Convert::YText for more information. AUTHOR David Bremner, REPOSITORY git://gitolite.debian.net/convert-ytext.git COPYRIGHT Copyright (C) 2011 David Bremner. All Rights Reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself, namely: a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License". ACKNOWLEDGEMENTS Thanks all the folks on #debian-perl for their help, and to especially jawnsy for encouraging me to OO-ize the module. Convert-YText-v0.1.2/MANIFEST.SKIP000444001750001750 142711525753637 16717 0ustar00bremnerbremner000000000000 #!start included /usr/share/perl/5.10/ExtUtils/MANIFEST.SKIP # Avoid version control files. \bRCS\b \bCVS\b \bSCCS\b ,v$ \B\.svn\b \B\.git\b \B\.gitignore\b \b_darcs\b # Avoid Makemaker generated and utility files. \bMANIFEST\.bak \bMakefile$ \bblib/ \bMakeMaker-\d \bpm_to_blib\.ts$ \bpm_to_blib$ \bblibdirs\.ts$ # 6.18 through 6.25 generated this # Avoid Module::Build generated and utility files. \bBuild$ \b_build/ # Avoid temp and backup files. ~$ \.old$ \#$ \b\.# \.bak$ # Avoid Devel::Cover files. \bcover_db\b #!end included /usr/share/perl/5.10/ExtUtils/MANIFEST.SKIP # Avoid Module::Build generated and utility files. \bBuild$ \bBuild.bat$ \b_build \bBuild.COM$ \bBUILD.COM$ \bbuild.com$ # Avoid archives of this distribution \bConvert-YText-[\d\.\_]+ ^MYMETA.yml$ Convert-YText-v0.1.2/Changes000444001750001750 27011525753637 16267 0ustar00bremnerbremner000000000000Revision History for perl module Convert::YText Author: David Bremner 0.1.2 2011-02-13 - Enable tests 0.1.1 2011-02-12 - First CPAN Upload Convert-YText-v0.1.2/META.yml000444001750001750 101111525753637 16257 0ustar00bremnerbremner000000000000--- abstract: 'Quotes strings suitably for rfc2822 local part' author: - 'David Bremner ' configure_requires: Module::Build: 0.36 generated_by: 'Module::Build version 0.3607' license: unknown meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: Convert-YText provides: Convert::YText: file: lib/Convert/YText.pm version: v0.1.2 requires: Test::More: 0 resources: homepage: http://pivot.cs.unb.ca/git/?p=convert-ytext.git;a=summary version: v0.1.2 Convert-YText-v0.1.2/MANIFEST000444001750001750 33011525753637 16122 0ustar00bremnerbremner000000000000Build.PL Changes lib/Convert/YText.pm MANIFEST This list of files MANIFEST.SKIP META.yml README t/00.signature.t t/10.roundtrip.t t/20.keep-titles.t t/corpus.pl t/titles.pl SIGNATURE Added here by Module::Build Convert-YText-v0.1.2/t000755001750001750 011525753637 15123 5ustar00bremnerbremner000000000000Convert-YText-v0.1.2/t/00.signature.t000444001750001750 172111525753637 17665 0ustar00bremnerbremner000000000000#!/usr/bin/perl use strict; use Test::More; if (!$ENV{TEST_SIGNATURE}) { plan skip_all => "Set the environment variable TEST_SIGNATURE to enable this test."; } elsif (!eval { require Module::Signature; 1 }) { plan skip_all => "Next time around, consider installing Module::Signature, ". "so you can verify the integrity of this distribution."; } elsif ( !-e 'SIGNATURE' ) { plan skip_all => "SIGNATURE not found"; } elsif ( -s 'SIGNATURE' == 0 ) { plan skip_all => "SIGNATURE file empty"; } elsif (!eval { require Socket; Socket::inet_aton('pool.sks-keyservers.net') }) { plan skip_all => "Cannot connect to the keyserver to check module ". "signature"; } else { plan tests => 1; } my $ret = Module::Signature::verify(); SKIP: { skip "Module::Signature cannot verify", 1 if $ret eq Module::Signature::CANNOT_VERIFY(); cmp_ok $ret, '==', Module::Signature::SIGNATURE_OK(), "Valid signature"; } Convert-YText-v0.1.2/t/20.keep-titles.t000444001750001750 50411525753637 20072 0ustar00bremnerbremner000000000000use Test::More; use strict; use warnings; require 't/titles.pl'; plan tests => 1+2*(scalar (keys %titles::hash)); use_ok 'Convert::YText', qw(encode_ytext validate_ytext); foreach my $key (keys %titles::hash){ ok(validate_ytext($titles::hash{$key})); is(encode_ytext($key), $titles::hash{$key}, "conserve: ".$key); } Convert-YText-v0.1.2/t/titles.pl000444001750001750 1112411525753637 17140 0ustar00bremnerbremner000000000000package titles; %hash = ( 'So your topgit patch was merged upstream' => 'So+your+topgit+patch+was+merged+upstream', 'Yet another git+quilt Debian packaging workflow' => 'Yet+another+git=r=quilt+Debian+packaging+workflow', 'user level NetworkManager dispatcher' => 'user+level+NetworkManager+dispatcher', 'Beamer overlays in highlighted source code' => 'Beamer+overlays+in+highlighted+source+code', 'Fetching git bundles' => 'Fetching+git+bundles', 'wifi 4965agn' => 'wifi+4965agn', 'wanderlust whitelisting' => 'wanderlust+whitelisting', 'Tunnel versus MUX: a race to the git' => 'Tunnel+versus+MUX=6=+a+race+to+the+git', 'A topgit testimonial' => 'A+topgit+testimonial', 'The little train that couldn\'t' => 'The+little+train+that+couldn=n=t', 'digikam and raw part I (software)' => 'digikam+and+raw+part+I+=o=software=p=', 'blorg bugs?' => 'blorg+bugs=.=', 'A music download service that doesn\'t suck (mostly)' => 'A+music+download+service+that+doesn=n=t+suck+=o=mostly=p=', 'Functional programming on the JVM' => 'Functional+programming+on+the+JVM', 'Prolegomenon to any future tg-buildpackage' => 'Prolegomenon+to+any+future+tg-buildpackage', 'Which git commits should I send to upstream?' => 'Which+git+commits+should+I+send+to+upstream=.=', 'Tags are notmuch the point' => 'Tags+are+notmuch+the+point', 'Housing prices in Ulaanbaatar' => 'Housing+prices+in+Ulaanbaatar', 'Laptop Ikiwiki extended' => 'Laptop+Ikiwiki+extended', 'managing many git repos' => 'managing+many+git+repos', 'Copyright board inspired Dickens?' => 'Copyright+board+inspired+Dickens=.=', 'Batch processing mails from caff' => 'Batch+processing+mails+from+caff', 'lenovo x61 under debian unstable' => 'lenovo+x61+under+debian+unstable', 'Welcome to IkiWiki' => 'Welcome+to+IkiWiki', 'Encoding arbitrary strings into email addresses' => 'Encoding+arbitrary+strings+into+email+addresses', 'filling in forms with pdftk' => 'filling+in+forms+with+pdftk', 'Can I haz a distributed news reader?' => 'Can+I+haz+a+distributed+news+reader=.=', 'America\'s funniest error messages' => 'America=n=s+funniest+error+messages', 'suspend to disk key' => 'suspend+to+disk+key', 'pushmi and ssh-agent' => 'pushmi+and+ssh-agent', 'Mirroring a gitolite collection' => 'Mirroring+a+gitolite+collection', 'alsa for the AD1984' => 'alsa+for+the+AD1984', 'Printing fancier, or just different keysigining slips' => 'Printing+fancier=s=+or+just+different+keysigining+slips', 'I hate logrotate' => 'I+hate+logrotate', 'Converting META.yml to META.json' => 'Converting+META.yml+to+META.json', 'The mailbox plugin for ikiwiki' => 'The+mailbox+plugin+for+ikiwiki', 'Counting symbols from a debian symbols file' => 'Counting+symbols+from+a+debian+symbols+file', 'Comfortable Editing of Literate Haskell' => 'Comfortable+Editing+of+Literate+Haskell', 'Fixing flac metadata' => 'Fixing+flac+metadata', 'I\'m root dammit, connect to the xserver.' => 'I=n=m+root+dammit=s=+connect+to+the+xserver.', 'Using GLPK from C++' => 'Using+GLPK+from+C=r==r=', 'Which netbook to buy' => 'Which+netbook+to+buy', 'Using Org Mode as a time tracker' => 'Using+Org+Mode+as+a+time+tracker', 'Converting svn-buildpackage type 2 to git' => 'Converting+svn-buildpackage+type+2+to+git', 'Extracting text from pdf with pdfedit' => 'Extracting+text+from+pdf+with+pdfedit', 'Converting svn-buildpackage /debian only to git' => 'Converting+svn-buildpackage+~debian+only+to+git', 'Remote invocation of sbuild' => 'Remote+invocation+of+sbuild', 'getting blorg working' => 'getting+blorg+working', 'Distributed Issue Tracking with Git' => 'Distributed+Issue+Tracking+with+Git', 'Audio Player Fail' => 'Audio+Player+Fail', 'source-highlight and oz' => 'source-highlight+and+oz', 'managing many git repos II' => 'managing+many+git+repos+II', 'Trivial example using python to hack ical' => 'Trivial+example+using+python+to+hack+ical', 'The dim screen after wakeup problem' => 'The+dim+screen+after+wakeup+problem', 'Yet another tale of converting Debian packaging to Git' => 'Yet+another+tale+of+converting+Debian+packaging+to+Git', 'transcoding to M4A in Amarok 1.4.7' => 'transcoding+to+M4A+in+Amarok+1.4.7' ); 1; Convert-YText-v0.1.2/t/10.roundtrip.t000444001750001750 235411525753637 17716 0ustar00bremnerbremner000000000000use Test::More; use strict; use warnings; require 't/corpus.pl'; my @list=corpus(); plan tests => 1+5*(scalar @list); use_ok 'Convert::YText', qw(encode_ytext decode_ytext); foreach my $addr (@list){ is(decode_ytext(encode_ytext($addr)), $addr, "roundtrip: ".$addr); } { my $enc = Convert::YText->new(SPACE_CHAR=>'', SLASH_CHAR=>''); foreach my $addr (@list){ is($enc->decode($enc->encode($addr)), $addr, "roundtrip 2: ".$addr); } } { my $enc = Convert::YText->new(ESCAPE_CHAR=>'@', SLASH_CHAR=>''); foreach my $addr (@list){ is($enc->decode($enc->encode($addr)), $addr, "roundtrip 3: ".$addr); } } { my $enc = Convert::YText->new(ESCAPE_CHAR=>'z', DIGIT_STRING=>'01234567890!@#$%^&*()'. 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopq'); foreach my $addr (@list){ is($enc->decode($enc->encode($addr)), $addr, "roundtrip 4: ".$addr); } } { my $enc = Convert::YText->new( SPACE_CHAR=>'', SLASH_CHAR=>'', DIGIT_STRING=>"ABCDEFGHIJKLMNOPQRSTUVWXYZ" . "abcdefghijklmnopqrstuvwxyz0123456789-.", ESCAPE_CHAR=>'_', EXTRA_CHARS=>''); foreach my $addr (@list){ is($enc->decode($enc->encode($addr)), $addr, "roundtrip 4: ".$addr); } } Convert-YText-v0.1.2/t/corpus.pl000444001750001750 1015311525753637 17150 0ustar00bremnerbremner000000000000# Data from t/tests.t in Email::Address 1.892. # License is the same as this module. sub corpus { return ( q{"Edwards, Darryl" }, q{"Chris Devers" }, q{"Greg Norris" (humble visionary genius)}, q{"Frank Schmuck, CFO" }, q{regn@ExamPle.com}, q{"'abigail@foad.example.biz'" }, q{"'advocacy@p.example.org'" }, q{"Horsley Tom" }, q{"'Tom Christiansen'" }, q{"Chris Nandor" }, q{"Andy Wardley" }, q{"Nicholas Clark" }, q{chip@valinux.com}, q{perl5-porters@p.example.org}, q{"'advocacy@p.example.org '" }, q{"Tom Christiansen" }, q{"'Jon Orwant'" }, q{"brian d foy" }, q{"Brent Michalski" }, q{"'Elaine -HFB- Ashton '" }, q{"'Elaine -HFB- Ashton'" }, q{"Gabor Szabo" }, q{"'David E. Wheeler'" }, q{"Chip Salzenberg" }, q{"Elaine -HFB- Ashton" }, q{"Peter Scott" }, q{"'Ask Bjoern Hansen'" }, q{"'Larry Wall'" }, q{"Helton, Brandon" }, q{"Greg Norris \(humble visionary genius\)" }, q{advocacy@p.example.org}, q{"Greg Norris humble visionary genius\\"" }, q{"perl-advocacy" }, q{"'Madeline Schnapp '" }, q{"Michael G Schwern" }, q{"'Andy Lester'" }, q{perl6-language@p.example.org}, q{"'Rich Bowen'" }, q{tidbit@sri.net}, q{"Calvin Lee" }, q{"David H. Adler" }, q{"'Adam Turoff'" }, q{"'Pamela Carter'" }, q{"Rich Bowen" }, q{"Jan Dubois" }, q{marsneedswomen@happyfunball.pm.org}, q{"Madeline Schnapp" }, q{"Jason W. May" }, q{"David E. Wheeler" }, q{"Turoff, Adam" }, q{"'bwarnock@capita.com'" }, q{"Bas A. Schulte" }, q{"Michael R. Wolf" }, q{"G. Wade Johnson" }, q{"Piers Cawley" }, q{"Bradley M. Kuhn" }, q{"Nathan Torkington" }, q{Ben_Tilly@trepp.com}, q{"'John Porter'" }, q{"'. Jerry a'" }, q{"Paul Prescod" }, q{"Adam Turoff" }, q{"Uri Guttman" }, q{"'David H. Adler '" }, q{"Jason W. May" }, q{betsy@oreilly.com}, q{"Andreas J. Koenig" }, q{"'Doucette, Bob'" }, q{"'''advocacy@p.example.org ' ' '" }, q{madeline@oreilly.com}, q{"'Mark Mielke'" }, q{"Brian Wilson" }, q{simon@brecon.co.uk}, q{"Dave Cross" }, q{"Curtis Poe" }, q{"'Shlomi Fish'" }, q{"'Chris Nandor'" }, q{"David Grove" }, q{"Betsy Waliszewski" }, q{"Clinton A. Pierce" }, q{admin+=E6=96=B0=E5=8A=A0=E5=9D=A1_Weblog@test.sxt.example.info}, q{pudge@x.example.com}, q{"'Steve Lane'" }, q{"''advocacy@p.example.org ' '" }, q{"'london-list@happyfunball.pm.org'" }, q{"Alan Olsen" }, q{"'Gabor Szabo'" }, q{"Bas A.Schulte" }, q{"'perl-hackers@stlouis.pm.org'" }, q{"Brammer, Phil" }, q{"'duff@x.example.com'" }, q{"advocacy" }); }; 1; Convert-YText-v0.1.2/lib000755001750001750 011525753637 15426 5ustar00bremnerbremner000000000000Convert-YText-v0.1.2/lib/Convert000755001750001750 011525753637 17046 5ustar00bremnerbremner000000000000Convert-YText-v0.1.2/lib/Convert/YText.pm000444001750001750 1306411525753637 20642 0ustar00bremnerbremner000000000000package Convert::YText; use strict; use warnings; use Carp; use vars qw/$VERSION @ISA @EXPORT_OK/; @ISA = 'Exporter'; @EXPORT_OK = qw( encode_ytext decode_ytext validate_ytext); use encoding "utf-8"; $VERSION="0.1.2"; =head1 NAME Convert::YText - Quotes strings suitably for rfc2822 local part =head1 VERSION Version 0.1 =head1 SYNOPSIS use Convert::YText qw(encode_ytext decode_ytext); $encoded=encode_ytext($string); $decoded=decode_ytext($encoded); ($decoded eq $string) || die "this should never happen!"; =head1 DESCRIPTION Convert::YText converts strings to and from "YText", a format inspired by xtext defined in RFC1894, the MIME base64 and quoted-printable types (RFC 1394). The main goal is encode a UTF8 string into something safe for use as the local part in an internet email address (RFC2822). By default spaces are replaced with "+", "/" with "~", the characters "A-Za-z0-9_.-" encode as themselves, and everything else is written "=USTR=" where USTR is the base64 (using "A-Za-z0-9_." as digits) encoding of the unicode character code. The encoding is configurable (see below). =head1 PROCEDURAL INTERFACE The module can can export C which converts arbitrary unicode string into a "safe" form, and C which recovers the original text. C is a heuristic which returns 0 for bad input. =cut sub encode_ytext{ my $str=shift; my $object = Convert::YText->new(); return $object->encode($str); } sub decode_ytext{ my $str=shift; my $object = Convert::YText->new(); return $object->decode($str); } sub validate_ytext{ my $str=shift; my $object = Convert::YText->new(); return $object->valid($str); } =head1 OBJECT ORIENTED INTERFACE. For more control, you will need to use the OO interface. =head2 new Create a new encoding object. =head3 Arguments Arguments are by name (i.e. a hash). =over =item DIGIT_STRING ("A-Za-z0-9_.") Must be 64 characters long =item ESCAPE_CHAR ('=') Must not be in digit string. =item SPACE_CHAR ('+') Non digit to replace space. Can be the empty string. =item SLASH_CHAR ( '~') Non digit to replace slash. Can be the empty string. =item EXTRA_CHARS ('._\-') Other characters to leave unencoded. =back =cut sub new { my $class = shift; my %params=@_; my $self = { ESCAPE_CHAR=>'=', SPACE_CHAR=>'+', SLASH_CHAR=>'~', EXTRA_CHARS=>'-', DIGIT_STRING=> "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_." }; while (my ($key,$val) = each %params){ $self->{$key} = $val; }; croak("DIGIT_STRING must have 64 characters got: ".$self->{DIGIT_STRING}) if (length($self->{DIGIT_STRING})!=64); # computed values. Setting directly is probably a bad idea. $self->{DIGITS}=[split "",$self->{DIGIT_STRING}]; $self->{NO_ESCAPE}= $self->{DIGIT_STRING}.$self->{EXTRA_CHARS}.( length($self->{SPACE_CHAR}) ? ' ' : '' ) . (length($self->{SLASH_CHAR}) ? '/' : ''); $self->{ESCRX}=qr{\Q$self->{ESCAPE_CHAR}\E([\Q$self->{DIGIT_STRING}\E]+)\Q$self->{ESCAPE_CHAR}\E}; $self->{MUST64}=qr{[^\Q$self->{NO_ESCAPE}\E]}; $self->{VALIDRX}=qr{[\Q$self->{ESCAPE_CHAR}$self->{NO_ESCAPE}\E]+}; bless ($self, $class); return $self; } sub encode_num{ my $self=shift; my $num=shift; my $str=""; while ($num>0){ my $remainder=$num % 64; $num=$num >> 6; $str = $self->{DIGITS}->[$remainder].$str; } return $str; } sub decode_str{ my $self=shift; my $str=shift; my @chars=split "",$str; my $num=0; while (scalar(@chars)>0){ my $remainder=index $self->{DIGIT_STRING},$chars[0]; croak("not a digit: ".$chars[0]. " in \"$str\"") if ($remainder <0); $num=$num << 6; $num+=$remainder; shift @chars; } return chr($num); } =head2 encode =head3 Arguments a string to encode. =head3 Returns encoded string =cut sub encode{ my $self=shift; my $str=shift; $str=~ s/($self->{MUST64})/"$self->{ESCAPE_CHAR}".encode_num($self,ord($1))."$self->{ESCAPE_CHAR}"/ge; $str=~ s|/|$self->{SLASH_CHAR}|g if (length($self->{SLASH_CHAR})); $str=~ s/ /$self->{SPACE_CHAR}/g; return $str; }; =head2 decode =head3 Arguments a string to decode. =head3 Returns encoded string =cut sub decode{ my $self=shift; my $str = shift; $str=~ s/\Q$self->{SPACE_CHAR}\E/ /g if (length($self->{SPACE_CHAR})); $str=~ s|\Q$self->{SLASH_CHAR}\E|/|g if (length($self->{SLASH_CHAR})); $str=~ s/$self->{ESCRX}/ decode_str($self,$1)/eg; return $str; } =head2 valid Simple necessary but not sufficient test for validity. =cut sub valid{ my $self=shift; my $str = shift; return $str =~ m/$self->{VALIDRX}/; } =head1 DISCUSSION According to RFC 2822, the following non-alphanumerics are OK for the local part of an address: "!#$%&'*+-/=?^_`{|}~". On the other hand, it seems common in practice to block addresses having "%!/|`#&?" in the local part. The idea is to restrict ourselves to basic ASCII alphanumerics, plus a small set of printable ASCII, namely "=_+-~.". The characters '+' and '-' are pretty widely used to attach suffixes (although usually only one works on a given mail host). It seems ok to use '+-', since the first marks the beginning of a suffix, and then is a regular character. The character '.' also seems mostly permissable. =head1 AUTHOR David Bremner, Eddb@cpan.org =head1 COPYRIGHT Copyright (C) 2011 David Bremner. All Rights Reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L, L. =cut 1;